比如这个程序:
#include <stdio.h>
struct sdshdr {
long len;
long free;
char buf[];
};
int main(void) {
char buf[];
printf("%d %d %d\n", sizeof(buf), sizeof(long), sizeof(struct sdshdr));
return 0;
}
sdshdr
中的buf
是合法的,但main
中的buf
却不是合法的。请问怎么理解呢?有哪里可以看到相关的语法说明呢?
谢谢!
1
lawlielt 2016-07-29 17:38:15 +08:00 1
main 中变量声明会分配内存的 你这怎么分配? struct 只是一个结构
|
2
suikator 2016-07-29 17:40:26 +08:00 via Android 1
struct 中 buf 执行默认初始化, mian 中 buf 不执行初始化,没初始化自然不合法。
|
4
krizex 2016-07-29 17:44:40 +08:00 1
你的例子里面 struct 中的 buf 指向 free 之后开始的内存,是一种方便的用法,省的你用 sdshdr*(0)->free + sizeof(sdshdr*(0)->free)来找到这个地址了。
|
5
krizex 2016-07-29 17:45:56 +08:00 1
struct sdshdr {
long len; long free; char buf[]; }; 中的 buf 本身不占内存,在可变长度的 struct 中,这个是个常见的用法。 |
6
zts1993 2016-07-29 17:46:02 +08:00
redis sds 嘿嘿嘿
|
9
HarveyDent 2016-07-29 18:01:24 +08:00
这个东西应该是叫变长数组吧。搜一下就晓得了
|
10
rushcheyo 2016-07-29 18:08:05 +08:00
这叫位域……没有人知道它的学名吗?这不是 “惯用法” 而是标准规定的正常语法。
|
12
wangxn OP |
13
Zirconi 2016-07-29 18:20:52 +08:00 1
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html#Zero-Length
C99 中称为 flexible array |
14
shimanooo 2016-07-29 18:27:07 +08:00
auto p = malloc(sizeof(struct sdshdr) + variable_length)
p->buf[index_within_variable_length] 效果就是省一次 malloc ?代价是只能有一个变长数组,且必须放在末尾。 |
16
redsonic 2016-07-29 19:20:08 +08:00
exploit 中这种写法很常见,可能 hacker 们都比较懒。
|
17
nicevar 2016-07-29 19:22:09 +08:00
看文档抽象,会汇编的话,直接 gcc -S 输出汇编代码,把 struct 的 char buf[]改成 char *buf 对照一下就好理解了,你会看到往寄存器 mov 的时候,前者不占空间
|
18
wangxn OP @nicevar 这个和字符指针还是有区别的。这种方式不会额外占空间,但用字符指针的话却是实实在在多 4/8 字节的。
|
20
zhicheng 2016-07-29 19:41:10 +08:00 via Android 2
既不神奇,也不巧妙。不是变长数组,更不是位域。只是指向结构体末尾的指针。这种结构体不能直接定义变量使用,只能强转或动态内存。用不好有安全隐患,在数据库和服务器编程里经常这么用,解析磁盘文件和协议比较省事,因为通常二进制协议设计的时候数据包开始会有一个长度,后面紧跟着数据。
|
21
zhicheng 2016-07-29 19:49:13 +08:00
另外在结构体中,如果要求比较严格的话,不会定义成空。会定义成
struct { size_t len; char buf[1]; }; 至于为什么,楼主可以自己琢磨一下。 |
22
jeffersonpig 2016-07-29 20:26:49 +08:00 3
这是 C99 标准增加的结构体柔性数组成员,结构体的最后一个成员可以是不指定长度的数组,用 sizeof 操作符取得的这种结构体的大小将不包括柔性数组成员的大小。包含柔性数组成员的结构体只能用动态内存分配进行构造,且分配的内存大小应大于结构体大小,否则数组将无法包含任何数据。数组的大小由动态分配的内存决定
|
23
jeffersonpig 2016-07-29 20:28:21 +08:00
话说 LZ 这个 redis 的代码版本有点旧吧
|
24
wangxn OP @jeffersonpig 是的,好几年历史了,拿个老点的版本看起,不然新版本太多东西看得都眼花缭乱了。
|
25
kingddc314 2016-07-29 21:31:15 +08:00 via Android 1
特别用于结构体后面,是柔性数组,不占据空间,但是却可以根据指针进行寻址结构体后面的数据, C99 以前写法是 char buf[0],一般是在结构体后面附带数组时使用,用来节省指针的 4 个或 8 个字节。
|
26
sinxccc 2016-07-29 22:03:02 +08:00
@kingddc314 主要目的不是节约字节,而是解析一段内存的时候可以直接套上去用。
|
27
kingddc314 2016-07-29 22:40:39 +08:00
@sinxccc 我感觉主要还是节省内存吧,特别是 redis 缓存这样内存很珍贵的,不然何不加一个指针成员更直观
|
28
sinxccc 2016-07-29 23:41:11 +08:00
@kingddc314 嗯,我对 redis 的源代码不熟…不过我指的是这样定义的结构体,在解析一段内存的时候可以直接把内存 buffer 强转成结构体指针,然后按结构体读取。指针成员是做不到的。
|
29
SIGEV13 2016-07-29 23:58:15 +08:00 1
这个 struct 内部的空数组只能出现在末尾, (sdshdr *) var_name->buf[n] 方便地指向那个 struct 末尾地址 + n * sizeof(char).
一般用在 header/metadata + data 这种结构里: 预先划分一大块内存,然后在这段内存里填上这种结构,方便解析。 从名字看,这个可能用来管理内存, len 这个结构 buf 的长度, free 标记这个块有没有被占用吧。 |
30
zhujinliang 2016-07-30 00:01:58 +08:00 via iPhone
这个跟指针有区别么?
|
31
hsyu53 2016-07-30 00:02:59 +08:00 1
个人理解,应该是为了让整个结构体占用一段连续的内存区域。
|
32
kingddc314 2016-07-30 00:33:02 +08:00 via Android
@sinxccc 才想到确实有你说的这方面原因,指针成员的话,不太方便保证指针内存和结构体内存连续,而这个柔性数组处理只需分配更大的空间就很自然了。在 Redis 里面 antirez 的 sds 是 C 语言的字符串封装,其中的 sdshdr 结构是保存头信息,结构体后面的堆空间才是保存字符串的,这样的情况就只能是连续空间。
|
33
kingddc314 2016-07-30 00:36:10 +08:00 via Android
|
34
kohnv 2016-07-30 00:38:54 +08:00 1
这个叫柔性数组, 实现可变长 struct 的一种常用技巧.
|
35
xpol 2016-07-30 10:20:03 +08:00 via Android 1
结构体中变长数组,是一个惯用法。
结构体的最后一个成员可以这么用。 malloc 的时候会 malloc 实际要的数组长度的内存。结构体和数组一次性 malloc 。 如果你用指针的话,需要 malloc 两次,一次结构体,一次数组。另外就是访问方便,不用通过指针间接一次。 可惜有些编译器会报 warning 。可以在结构体中把长度设置为 1 来解决。 |
36
xpol 2016-07-30 10:22:08 +08:00 via Android
补充一下,不是 c99 (误?)中的 vla 。
|
37
cwlmxwb 2016-07-30 11:15:25 +08:00 1
#include <stdio.h>
#include <malloc.h> #include <string.h> /* flexiable array */ typedef struct flexiable_array_s{ int a; double b; char s[0]; }flexiable_array_t; int main(int argc, char const *argv[]) { flexiable_array_t *test = (flexiable_array_t *)malloc(sizeof(flexiable_array_t) + 100 * sizeof(char)); char *s = "hello world!"; strcpy((char*)(test + 1), s); //strncpy would be better printf("%s\n", test->s); free(test); return 0; } |
38
jeffersonpig 2016-07-30 13:30:10 +08:00 1
@zhujinliang 有,区别在于 buf 保存的字符串跟其它结构体成员的内存空间是否在一起。 redis 用这样的结构体封装了自己的 sds 字符串类型,一个 sds 实际上就是 sdshdr.buf ,可以通过结构体成员的地址偏移来获取该 sds 的类型和实际内容长度
|
39
franklinyu 2016-07-31 01:44:01 +08:00
@cwlmxwb 你這用的是 C90 的語法,樓主用的是 C99 裡面的「柔性數組( flexible array )」特性。
|
40
jasonlz 2016-08-03 16:42:30 +08:00
其正确区分声明和定义的区别,声明一个空数组并不违法,使用一个未定义的变量就是违法行为。不管是结构体里的 buf 还是 main 函数里的 buf ,都知识声明而未定义,即没有分配内存,未定义变量访问就会报错。同样的你如果使用一个未定义的结构体里的 buf ,也是会报错。而上面讨论的 zero-length array 是结构体在定义的时候自动给可变数组一个定义。
|