阅读源码学习编码

C语言

最近看redis源码,看到了这样的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef char *sds;

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};


/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}

看到s[-1]我脑袋中第一个想法是C语言也支持向Python那样支持负数索引了?然后想到之前看c语言程序设计时书上说过: 对于c语言中数组的索引其实是指针操作的语法糖,数组名可以被视为指向数组首元素的指针。因此,用数组名加上一个偏移量(即数组索引)可以得到数组中某个元素的地址,这个过程实际上就是一个指针运算。

sdsfree中其实参数s就是指向sdshrd8结构体(其实会依据字符串的长度分为5,8,16,32和64,这里就拿8举例)的buf字段, 因为s所指向的buf所处的sdshdr8结构体是提前分配好空间的,因此s[-1]等价于 *(s - 1)也就是获取到flag字段。 sdsHdrSize(s[-1])在这里就会获取到sdshdr8结构体的大小,然后相减得到sdshdr8结构体的首地址,然后再调用s_free释放内存。

NOTE:因为在C中,内存是由程序员自己管理的,因此你做指针运算然后解引用的时候,一定要确保你的指针是指向了合法的内存地址,否则就是未定义行为。 上面的代码中,因为sdshdr8结构体是提前分配好空间的,因此s[-1]是合法的.

思考题:下面的代码会输出什么?

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
    unsigned char a = 255;
    unsigned char b[] = {0x1, 0x2};
    printf("b[-1]=%hhu, b[0]=%hhu, b[1]=%hhu\n", b[-1], b[0], b[1]);
    return 0;
}

内核中的ipv4的header定义如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
	__u8	ihl:4,
		version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
	__u8	version:4,
  		ihl:4;
#else
#error	"Please fix <asm/byteorder.h>"
#endif
	__u8	tos;
	__be16	tot_len;
	__be16	id;
	__be16	frag_off;
	__u8	ttl;
	__u8	protocol;
	__sum16	check;
	__struct_group(/* no tag */, addrs, /* no attrs */,
		__be32	saddr;
		__be32	daddr;
	);
	/*The options start here. */
};

结合

/posts/studying_py_by_reading_source_code/IPv4_Packet-en.svg.png
ipv4头部 来看,知道version和ihl分别占4位和4位,合起来正好1字节。因此可以用位域来表示,这样可以节省内存空间。