前段时间在阅读nginx的源代码时,碰到一个之前从未碰到的问题,nginx定义的hash表中的元素的结构体:
typedef struct { void *value; u_short len; u_char name[1]; } ngx_hash_elt_t;
暂且不论各个成员的含义,u_short就是unsigned short,u_char就是unsigned char,那么u_char name[1];是个什么结构?为什么要这么定义它?在网上查了一下,才了解一二。这么定义它有两个好处:
1. 这个结构体是变长的,不仅限于ngx_hash_elt_t本身的大小。
2. 分配大小大于sizeof(ngx_hash_elt_t)的空间时,后续的空间与ngx_hash_elt_t的空间是连续的。
在查询的过程中,碰到另一个更奇怪的问题:
typedef struct { short len; char data[0]; } demo_t;
char data[0];是个什么定义?再查了下这个东西,才知道它叫做柔性数组,常用于变长结构体,节省空间和提供便利的访问性。sizeof(demo_t)时,它本身不占空间,但是,如果像上述那样给demo_t后面分配了额外的空间后,那么我们就可以通过成员名访问它了。例子如下:
#include <stdio.h> #include <malloc.h> #include <string.h> typedef struct { short len; char data[0]; } demo_t; int main() { const char *str = "hello world!"; demo_t *p = (demo_t *)malloc(sizeof(demo_t) + strlen(str) + 1); memset(p->data, 0, strlen(str) + 1); memmove(p->data, str, strlen(str)); printf("sizeof(demo_t) = %d, p->data = %s\r\n", sizeof(demo_t), p->data); free(p); return 0; }
上述的输出是:sizeof(demo_t) = 2; p->data = hello world!
最后,看网上说只有GCC支持这种用法,但是上述的例子使用GCC 4.6.3和VS 2010都测试通过,这说明GCC和VS 2010都支持这种用法了。
现在回过头来看看ngx_hash_elt_t中的u_char name[1];,在不使用它时,它占用一个字节,要使用它时,上述的例子中malloc一句不需要+1,因为它已经占用了一个字节,而memset一句不变,因为strlen(str)这么大的空间刚好可以放下str的内容,再加上末尾的'\0'就是strlen(str)+1了,相当于name[0]的空间放下了'\0'(只是比喻,非实际情况)。