请问 C 中的空数组怎么理解? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wangxn
V2EX    C

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

  •  
  •   wangxn 2016-07-29 17:21:37 +08:00 3515 次点击
    这是一个创建于 3384 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如这个程序:

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

    40 条回复    2016-08-03 16:42:30 +08:00
    lawlielt
        1
    lawlielt  
       2016-07-29 17:38:15 +08:00   1
    main 中变量声明会分配内存的 你这怎么分配? struct 只是一个结构
    suikator
        2
    suikator  
       2016-07-29 17:40:26 +08:00 via Android   1
    struct 中 buf 执行默认初始化, mian 中 buf 不执行初始化,没初始化自然不合法。
    wangxn
        3
    wangxn  
    OP
       2016-07-29 17:42:50 +08:00
    @lawlielt 虽然是这个道理,但感觉挺奇怪的。
    krizex
        4
    krizex  
       2016-07-29 17:44:40 +08:00   1
    你的例子里面 struct 中的 buf 指向 free 之后开始的内存,是一种方便的用法,省的你用 sdshdr*(0)->free + sizeof(sdshdr*(0)->free)来找到这个地址了。
    krizex
        5
    krizex  
       2016-07-29 17:45:56 +08:00   1
    struct sdshdr {
    long len;
    long free;
    char buf[];
    };
    中的 buf 本身不占内存,在可变长度的 struct 中,这个是个常见的用法。
    zts1993
        6
    zts1993  
       2016-07-29 17:46:02 +08:00
    redis sds 嘿嘿嘿
    wangxn
        7
    wangxn  
    OP
       2016-07-29 17:48:43 +08:00
    @krizex 多谢详细讲解。这种 C 的惯用法确实很巧妙。
    wangxn
        8
    wangxn  
    OP
       2016-07-29 17:49:25 +08:00
    @zts1993 厉害,一眼就看出了出处。
    HarveyDent
        9
    HarveyDent  
       2016-07-29 18:01:24 +08:00
    这个东西应该是叫变长数组吧。搜一下就晓得了
    rushcheyo
        10
    rushcheyo  
       2016-07-29 18:08:05 +08:00
    这叫位域……没有人知道它的学名吗?这不是 “惯用法” 而是标准规定的正常语法。
    jccg90
        11
    jccg90  
       2016-07-29 18:16:57 +08:00
    @rushcheyo 怎么我搜到的位域不是这个样子的
    wangxn
        12
    wangxn  
    OP
       2016-07-29 18:20:33 +08:00   1
    @rushcheyo 这个不是位域……
    ```C
    // 位域示例
    struct sdshdr {
    long len:2;
    long free:6;
    char buf[];
    };
    ```
    Zirconi
        13
    Zirconi  
       2016-07-29 18:20:52 +08:00   1
    shimanooo
       
    shimanooo  
       2016-07-29 18:27:07 +08:00
    auto p = malloc(sizeof(struct sdshdr) + variable_length)
    p->buf[index_within_variable_length]

    效果就是省一次 malloc ?代价是只能有一个变长数组,且必须放在末尾。
    wangxn
        15
    wangxn  
    OP
       2016-07-29 18:42:22 +08:00
    @Zirconi 谢谢,终于找到了正规的语法说明!
    redsonic
        16
    redsonic  
       2016-07-29 19:20:08 +08:00
    exploit 中这种写法很常见,可能 hacker 们都比较懒。
    nicevar
        17
    nicevar  
       2016-07-29 19:22:09 +08:00
    看文档抽象,会汇编的话,直接 gcc -S 输出汇编代码,把 struct 的 char buf[]改成 char *buf 对照一下就好理解了,你会看到往寄存器 mov 的时候,前者不占空间
    wangxn
        18
    wangxn  
    OP
       2016-07-29 19:28:50 +08:00 via Android
    @nicevar 这个和字符指针还是有区别的。这种方式不会额外占空间,但用字符指针的话却是实实在在多 4/8 字节的。
    wangxn
        19
    wangxn  
    OP
       2016-07-29 19:30:20 +08:00 via Android
    @wangxn 糟糕,我理解错了,那位朋友的说法和我的一样,抱歉。
    zhicheng
        20
    zhicheng  
       2016-07-29 19:41:10 +08:00 via Android   2
    既不神奇,也不巧妙。不是变长数组,更不是位域。只是指向结构体末尾的指针。这种结构体不能直接定义变量使用,只能强转或动态内存。用不好有安全隐患,在数据库和服务器编程里经常这么用,解析磁盘文件和协议比较省事,因为通常二进制协议设计的时候数据包开始会有一个长度,后面紧跟着数据。
    zhicheng
        21
    zhicheng  
       
    另外在结构体中,如果要求比较严格的话,不会定义成空。会定义成

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

    至于为什么,楼主可以自己琢磨一下。
    jeffersonpig
        22
    jeffersonpig  
       2016-07-29 20:26:49 +08:00   3
    这是 C99 标准增加的结构体柔性数组成员,结构体的最后一个成员可以是不指定长度的数组,用 sizeof 操作符取得的这种结构体的大小将不包括柔性数组成员的大小。包含柔性数组成员的结构体只能用动态内存分配进行构造,且分配的内存大小应大于结构体大小,否则数组将无法包含任何数据。数组的大小由动态分配的内存决定
    jeffersonpig
        23
    jeffersonpig  
       2016-07-29 20:28:21 +08:00
    话说 LZ 这个 redis 的代码版本有点旧吧
    wangxn
        24
    wangxn  
    OP
       2016-07-29 21:20:34 +08:00
    @jeffersonpig 是的,好几年历史了,拿个老点的版本看起,不然新版本太多东西看得都眼花缭乱了。
    kingddc314
        25
    kingddc314  
       2016-07-29 21:31:15 +08:00 via Android   1
    特别用于结构体后面,是柔性数组,不占据空间,但是却可以根据指针进行寻址结构体后面的数据, C99 以前写法是 char buf[0],一般是在结构体后面附带数组时使用,用来节省指针的 4 个或 8 个字节。
    sinxccc
        26
    sinxccc  
       2016-07-29 22:03:02 +08:00
    @kingddc314 主要目的不是节约字节,而是解析一段内存的时候可以直接套上去用。
    kingddc314
        27
    kingddc314  
       2016-07-29 22:40:39 +08:00
    @sinxccc 我感觉主要还是节省内存吧,特别是 redis 缓存这样内存很珍贵的,不然何不加一个指针成员更直观
    sinxccc
        28
    sinxccc  
       2016-07-29 23:41:11 +08:00
    @kingddc314 嗯,我对 redis 的源代码不熟…不过我指的是这样定义的结构体,在解析一段内存的时候可以直接把内存 buffer 强转成结构体指针,然后按结构体读取。指针成员是做不到的。
    SIGEV13
        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 标记这个块有没有被占用吧。
    zhujinliang
        30
    zhujinliang  
       2016-07-30 00:01:58 +08:00 via iPhone
    这个跟指针有区别么?
    hsyu53
        31
    hsyu53  
       2016-07-30 00:02:59 +08:00   1
    个人理解,应该是为了让整个结构体占用一段连续的内存区域。
    kingddc314
        32
    kingddc314  
       2016-07-30 00:33:02 +08:00 via Android
    @sinxccc 才想到确实有你说的这方面原因,指针成员的话,不太方便保证指针内存和结构体内存连续,而这个柔性数组处理只需分配更大的空间就很自然了。在 Redis 里面 antirez 的 sds 是 C 语言的字符串封装,其中的 sdshdr 结构是保存头信息,结构体后面的堆空间才是保存字符串的,这样的情况就只能是连续空间。
    kingddc314
        33
    kingddc314  
       2016-07-30 00:36:10 +08:00 via Android
    kohnv
        34
    kohnv  
       2016-07-30 00:38:54 +08:00   1
    这个叫柔性数组, 实现可变长 struct 的一种常用技巧.
    xpol
        35
    xpol  
       2016-07-30 0:20:03 +08:00 via Android   1
    结构体中变长数组,是一个惯用法。
    结构体的最后一个成员可以这么用。 malloc 的时候会 malloc 实际要的数组长度的内存。结构体和数组一次性 malloc 。
    如果你用指针的话,需要 malloc 两次,一次结构体,一次数组。另外就是访问方便,不用通过指针间接一次。
    可惜有些编译器会报 warning 。可以在结构体中把长度设置为 1 来解决。
    xpol
        36
    xpol  
       2016-07-30 10:22:08 +08:00 via Android
    补充一下,不是 c99 (误?)中的 vla 。
    cwlmxwb
        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;
    }
    jeffersonpig
        38
    jeffersonpig  
       2016-07-30 13:30:10 +08:00   1
    @zhujinliang 有,区别在于 buf 保存的字符串跟其它结构体成员的内存空间是否在一起。 redis 用这样的结构体封装了自己的 sds 字符串类型,一个 sds 实际上就是 sdshdr.buf ,可以通过结构体成员的地址偏移来获取该 sds 的类型和实际内容长度
    franklinyu
        39
    franklinyu  
       2016-07-31 01:44:01 +08:00
    @cwlmxwb 你用的是 C90 的法,主用的是 C99 面的「柔性( flexible array )」特性。
    jasonlz
        40
    jasonlz  
       2016-08-03 16:42:30 +08:00
    其正确区分声明和定义的区别,声明一个空数组并不违法,使用一个未定义的变量就是违法行为。不管是结构体里的 buf 还是 main 函数里的 buf ,都知识声明而未定义,即没有分配内存,未定义变量访问就会报错。同样的你如果使用一个未定义的结构体里的 buf ,也是会报错。而上面讨论的 zero-length array 是结构体在定义的时候自动给可变数组一个定义。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     936 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 20:53 PVG 04:53 LAX 12:53 JFK 15:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86