请问 C 中的空数组怎么理解?

2016-07-29 17:21:37 +08:00
 wangxn

比如这个程序:

#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却不是合法的。请问怎么理解呢?有哪里可以看到相关的语法说明呢? 谢谢!

3096 次点击
所在节点    C
40 条回复
zhicheng
2016-07-29 19:49:13 +08:00
另外在结构体中,如果要求比较严格的话,不会定义成空。会定义成

struct {
size_t len;
char buf[1];
};

至于为什么,楼主可以自己琢磨一下。
jeffersonpig
2016-07-29 20:26:49 +08:00
这是 C99 标准增加的结构体柔性数组成员,结构体的最后一个成员可以是不指定长度的数组,用 sizeof 操作符取得的这种结构体的大小将不包括柔性数组成员的大小。包含柔性数组成员的结构体只能用动态内存分配进行构造,且分配的内存大小应大于结构体大小,否则数组将无法包含任何数据。数组的大小由动态分配的内存决定
jeffersonpig
2016-07-29 20:28:21 +08:00
话说 LZ 这个 redis 的代码版本有点旧吧
wangxn
2016-07-29 21:20:34 +08:00
@jeffersonpig 是的,好几年历史了,拿个老点的版本看起,不然新版本太多东西看得都眼花缭乱了。
kingddc314
2016-07-29 21:31:15 +08:00
特别用于结构体后面,是柔性数组,不占据空间,但是却可以根据指针进行寻址结构体后面的数据, C99 以前写法是 char buf[0],一般是在结构体后面附带数组时使用,用来节省指针的 4 个或 8 个字节。
sinxccc
2016-07-29 22:03:02 +08:00
@kingddc314 主要目的不是节约字节,而是解析一段内存的时候可以直接套上去用。
kingddc314
2016-07-29 22:40:39 +08:00
@sinxccc 我感觉主要还是节省内存吧,特别是 redis 缓存这样内存很珍贵的,不然何不加一个指针成员更直观
sinxccc
2016-07-29 23:41:11 +08:00
@kingddc314 嗯,我对 redis 的源代码不熟…不过我指的是这样定义的结构体,在解析一段内存的时候可以直接把内存 buffer 强转成结构体指针,然后按结构体读取。指针成员是做不到的。
SIGEV13
2016-07-29 23:58:15 +08:00
这个 struct 内部的空数组只能出现在末尾, (sdshdr *) var_name->buf[n] 方便地指向那个 struct 末尾地址 + n * sizeof(char).

一般用在 header/metadata + data 这种结构里: 预先划分一大块内存,然后在这段内存里填上这种结构,方便解析。

从名字看,这个可能用来管理内存, len 这个结构 buf 的长度, free 标记这个块有没有被占用吧。
zhujinliang
2016-07-30 00:01:58 +08:00
这个跟指针有区别么?
hsyu53
2016-07-30 00:02:59 +08:00
个人理解,应该是为了让整个结构体占用一段连续的内存区域。
kingddc314
2016-07-30 00:33:02 +08:00
@sinxccc 才想到确实有你说的这方面原因,指针成员的话,不太方便保证指针内存和结构体内存连续,而这个柔性数组处理只需分配更大的空间就很自然了。在 Redis 里面 antirez 的 sds 是 C 语言的字符串封装,其中的 sdshdr 结构是保存头信息,结构体后面的堆空间才是保存字符串的,这样的情况就只能是连续空间。
kingddc314
2016-07-30 00:36:10 +08:00
kohnv
2016-07-30 00:38:54 +08:00
这个叫柔性数组, 实现可变长 struct 的一种常用技巧.
xpol
2016-07-30 10:20:03 +08:00
结构体中变长数组,是一个惯用法。
结构体的最后一个成员可以这么用。 malloc 的时候会 malloc 实际要的数组长度的内存。结构体和数组一次性 malloc 。
如果你用指针的话,需要 malloc 两次,一次结构体,一次数组。另外就是访问方便,不用通过指针间接一次。
可惜有些编译器会报 warning 。可以在结构体中把长度设置为 1 来解决。
xpol
2016-07-30 10:22:08 +08:00
补充一下,不是 c99 (误?)中的 vla 。
cwlmxwb
2016-07-30 11:15:25 +08:00
#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;
}
jeffersonpig
2016-07-30 13:30:10 +08:00
@zhujinliang 有,区别在于 buf 保存的字符串跟其它结构体成员的内存空间是否在一起。 redis 用这样的结构体封装了自己的 sds 字符串类型,一个 sds 实际上就是 sdshdr.buf ,可以通过结构体成员的地址偏移来获取该 sds 的类型和实际内容长度
franklinyu
2016-07-31 01:44:01 +08:00
@cwlmxwb 你這用的是 C90 的語法,樓主用的是 C99 裡面的「柔性數組( flexible array )」特性。
jasonlz
2016-08-03 16:42:30 +08:00
其正确区分声明和定义的区别,声明一个空数组并不违法,使用一个未定义的变量就是违法行为。不管是结构体里的 buf 还是 main 函数里的 buf ,都知识声明而未定义,即没有分配内存,未定义变量访问就会报错。同样的你如果使用一个未定义的结构体里的 buf ,也是会报错。而上面讨论的 zero-length array 是结构体在定义的时候自动给可变数组一个定义。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/295830

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX