c语言柔性数组

234 阅读3分钟

今天在看 redis 源码的时候了解到一个不熟悉的名词 柔性数组

本着遇到问题解决问题,遇到疑惑解决疑惑的态度动手试试看看是不是和我想的那样。

在看到 SDS 动态字符串的时候,刚开始看到这个结构,就直接认为这个 buf 想到了 一个 char 类型的指针。

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[];
};

然后就写 demo 测试.

#include "stdlib.h"
#include "stdio.h"

typedef unsigned short uint16_t;

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

int age = 12;
int buf[4] = {1, 2, 3, 4};
int main() {
    struct sdshdr16 s;
    s.len = 1;
    s.alloc = 2;
    s.flags = 0b11;
    // 这里有问题
    s.buf = (char *) malloc(sizeof(char) * 200);
    return 0;
}

这里一直被卡在 s.buf = (char *) malloc(sizeof(char) * 200); 这里。

...

中途省略 n 多字

...

一直不知道原来,再回头看书,发现我忽视了最重要的一个关键词, 柔性数组

image-20210927170335473

终于了解了。

在 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。在使用柔性数组成员时,需要注意如下几点:

  • 结构中的柔性数组成员前面至少包含一个其他成员。
  • 柔性数组成员允许结构中包含一个大小可变的数组。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用 malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

也就是说在给这个结构体分配内存的时候,不管内存分配多大,除了前面需要用到的,剩下的空间都是这个数组的。

验证一下

#include "stdlib.h"
#include "stdio.h"

typedef unsigned short uint16_t;

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

int main() {
    //    这个时候 s1->buf 应该有200个长度
    struct sdshdr16 *s1 = (struct sdshdr16 *) malloc(sizeof(struct sdshdr16) + 27);
    // 32
    // 32
    struct sdshdr16 *s2 = (struct sdshdr16 *) malloc(sizeof(struct sdshdr16) + 27);
    printf("%d", sizeof(struct sdshdr16));
    s1->len = 0;
    s1->alloc = 200;
    s1->flags = 0b11;
    for (int i = 0; i < 40; i++) {
        s1->buf[i] = 'a' + i % 26;
    }
    printf("\ns1->buf[40]: %d", &s1->buf[27]);
    printf("\ns2: %d", s2);
    // s1->buf[40]
    s1->buf[43] = 31;
    printf("\ns2->len: %d", s2->len);
    return 0;
}

这里验证了一下确实是这样的,目前给 s1 分配了 32 个字节,除了前面用到的 5 个字节,后面的 27 个字节都是给 buf

所以 buf[27] 的地址与 s2 的地址一样。因为 s1, s2 是一起分配的。所以在空间上是连续的。

这里需要注意的是,这里会在分配内存的时候会进行字节对齐,如果我这边给 buf 分配的不是 27 个字节,而是其他的,比如 40 个字节,加上结构体前面的 5 个字节,共 45 个字节,字节对齐的话,s2 应该从第 48 个字节起分配内存,也就是 buf[43]的地址为 s2 的地址。