从Redis源码看大小端序的转换

目录

1.知识回顾

2.要用到的Redis源码

3.前置知识

snprintf函数

作用

各个参数说明

返回值

代码示例

变式训练1

变式训练2

4.接口的使用

测试函数endianconvTest接口使用

使用宏来调用

大致读c文件开头的注释

调用方法

准备工作

测试代码main.c

其他函数接口测试

5.源码分析

memrev16

memrev32

memrev64


1.知识回顾

之前在E35.【C语言】判断大/小端序文章讲过大小端序判断方法,但讲得有点粗糙,本文将分析Redis项目的部分源码来看大小端序的转换问题

2.要用到的Redis源码

Github链接:https://github.com/redis/redis/tree/unstable/src

要分析的源代码文件:

endianconv.c 点击跳转到Github下载

endianconv.h 点击跳转到Github下载

(endiancov全称为endian convert,即端序转换)

如果Github访问不稳定或者无法访问,百度网盘下载链接:https://pan.baidu.com/s/1fo9XuVBDbqgS1JXkmkWewA?pwd=tkxe 提取码: tkxe

3.前置知识

snprintf函数

函数声明: int snprintf ( char * s, size_t n, const char * format, ... ); ,显然为不定参函数,作用和printf略有不同

作用

向已定大小的缓冲区(sized buffer)写入格式化(formatted)的缓冲区(buffer,其实是数组)

各个参数说明

s为char*类型的指针,指向要存储字符串的buffer数组,注意:buffer数组至少可以存n个字符

n:buffer数组中最大可以使用的字节数,字符串的大小最多n-1字节,因为字符串的结尾要填充\0,n-1+1==n,这样正好填满容量为n个字符的buffer数组

format:和printf函数的format一样,这里不再赘述

...:表示额外的参数,可有可无,是否有额外的参数取决于format指向的字符串,例如"%d",2中2为额外的参数,这一点和printf一样,这里也不再赘述

返回值

如果buffer数组有充足的空间,且成功写入字符串,则返回写入字符的个数(不含\0),成功写入时,返回值必须非负(non-negative)且小于n;如果出现编码错误,返回一个负数(通常是-1)

代码示例

正常返回:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buffer[20];
    int cx;
    cx = snprintf(buffer, 20, "Hello %s","World!");
    if (cx >= 0 && cx < 6)//检查返回值
        puts(buffer);
    else
    {
        printf("解码错误,snprintf返回值为:%d", cx);
        exit(EXIT_FAILURE);
    }
    return 0;
}

运行结果:

变式训练1

修改上方代码为:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buffer[20];
    int cx;
    cx = snprintf(buffer, 6, "Hello %s","World!");
    if (cx >= 0 && cx < 6)//检查返回值
        puts(buffer);
    else
    {
        printf("解码错误,snprintf返回值为:%d", cx);
        exit(EXIT_FAILURE);
    }
    return 0;
}

求运行结果

发现20改成了6,虽然buffer最多可以存储20个字符,但是buffer中最大可使用6个字节,则buffer存储的字符串为"Hello\0"

运行结果:

变式训练2

修改上方代码为:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buffer[3];
    int cx;
    cx = snprintf(buffer, 6, "Hello %s","World!");
    if (cx >= 0 && cx < 20)      // check returned value
        puts(buffer);
    else
    {
        printf("解码错误,snprintf返回值为:%d", cx);
        exit(EXIT_FAILURE);
    }
    return 0;
}

求运行结果

发现sprintf的第二个参数6小于buffer[3]的3,导致缓冲区溢出,在Dev C++上测试结果:

4.接口的使用

测试函数endianconvTest接口使用

 在endianconv.c文件中有一个测试函数

#ifdef REDIS_TEST
#include <stdio.h>

#define UNUSED(x) (void)(x)
int endianconvTest(int argc, char *argv[], int flags) {
    char buf[32];

    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    snprintf(buf,sizeof(buf),"ciaoroma");//写入"ciaoroma"到buf数组
    memrev16(buf);
    printf("%s\n", buf);

    snprintf(buf,sizeof(buf),"ciaoroma");
    memrev32(buf);
    printf("%s\n", buf);

    snprintf(buf,sizeof(buf),"ciaoroma");
    memrev64(buf);
    printf("%s\n", buf);

    return 0;
}
#endif

96.【C语言】解析预处理(4)文章中讲过,如果想调用endianconvTest函数需要手动打开接口,即在#ifdef前面添加一行定义: 

#define REDIS_TEST

 再写一个main函数去调用endianconvTest函数接口

int main()
{
	endianconvTest(0,NULL,0);//随意传参,函数内部并没有使用
	return 0;
}

运行结果:

使用宏来调用

大致读c文件开头的注释

"This functions are never called directly, but always using the macros
 defined into endianconv.h,..."

表明该函数不会直接调用,而是通过定义在endianconv.h中的宏来使用

调用方法

准备工作

在endianconv.h中有#include "config.h",需要手动添加config.h

下载地址 https://github.com/redis/redis/blob/unstable/src/config.hz

直接编译会报错:

在#if !defined(BYTE_ORDER) || \ (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN) =前添加字节序的一行定义即可,如下:

#define BYTE_ORDER BIG_ENDIAN
测试代码main.c
#include "endianconv.h"
#include <stdio.h>
int main()
{
	char buffer[] = { "teststring" };
	memrev16ifbe(buffer);
	printf(buffer);
	return 0;
}

运行结果:\只交换teststring的前两个字符的位置

 

分析:memrev16ifbe其实是宏

,由于定义BYTE_ORDER为BIG_ENDIAN,则 memrev16ifbe(buffer)会被替换为memrev16(buffer),转而去调用memrev16(相邻两字节交换位置)函数,注:((void)(0))其实是无操作(不做任何事)

其他函数接口测试

(交换了"teststring"的前4个字节)

 (交换了"teststring"的前8个字节)

5.源码分析

memrev16

void memrev16(void *p) {
    unsigned char *x = p, t;

    t = x[0];
    x[0] = x[1];
    x[1] = t;
}

16代表16bit,即2个字节,临时指针变量x接收指针p的值,使用中间变量t来交换x[0]和x[1]存储的值

memrev32

void memrev32(void *p) {
    unsigned char *x = p, t;

    t = x[0];
    x[0] = x[3];
    x[3] = t;
    t = x[1];
    x[1] = x[2];
    x[2] = t;
}

 32代表32bit,即4个字节,互换位置

可以画图分析:

memrev64

void memrev64(void *p) {
    unsigned char *x = p, t;

    t = x[0];
    x[0] = x[7];
    x[7] = t;
    t = x[1];
    x[1] = x[6];
    x[6] = t;
    t = x[2];
    x[2] = x[5];
    x[5] = t;
    t = x[3];
    x[3] = x[4];
    x[4] = t;

 64代表64bit,即8个字节,互换位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值