0%

c语言柔性数组

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

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

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

1
2
3
4
5
6
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 测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

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

验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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 的地址。