C语言结构体中相关问题
记录一些C语言相关的问题。
弹性数组
最近看redis中的数组sds设计,结构如下:
// sdshdr 结构
struct sdshdr {
// buf 已占用长度
int len;
// buf 剩余可用长度
int free;
// 实际保存字符串数据的地方
// 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址,
// 详情google "flexible array member"
char buf[];
};
结构体sdshdr的实际长度为8,也就是说buf不占用内存空间,它表示的是一个地址的概念。我们定义一个struct sdshdr的变量s,在gdb中可以看见该变量的相关信息。
在这里,buf为弹性数组,关于弹性数组,我们从先从使用说起。
int main() {
int this_len = 10;
struct sdshdr *s = (struct sdshdr*)malloc(sizeof(struct sdshdr) + this_len + 1);
s->len = this_len;
s->free = 0;
memset(s->buf, 'a', this_len);
s->buf[this_len] = '\0';
return 0;
}
从上面可以看出,我们给结构体分配了一个连续的内存。这样有两个好处:
- 方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
- 这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。
用gdb的x命令查看内存地址可以发现它们连续存储。
结构体成员变量地址问题
这个问题来自于coolshell中的一篇文章,对理解c语言中的地址和指针很有帮助。
#include <stdio.h>
struct str {
int len;
char s[0];
};
struct foo {
sturct str *a;
};
int main(int argc, char **argv) {
struct foo f = {0};
if(f.a->s) {
printf(f.a->s);
}
return 0;
}
编译和运行程序,会在printf(f.a->s)这一行出现段错误。而不会在if(f.a->s)这一行挂掉,如果把语句printf(f.a->s)改成printf(“%x\n”, f.a->s),程序也可以顺利通过,输出结果4。这是为什么呢?首先需要知道s是数组名,在c语言中,数组名表示数组的起始地址,它是一个地址,所以if(f.a->s)不会出现错误,而printf(f.a->s)访问的是f.a->s访问的是s对应地址的内容,因为不存在所以造成了段错误。那么,输出结果4又是怎么来的呢?在结构体中,每个成员变量都是相对于结构体其实地址来的,在上面的程序中,f.a的值为0,所以结构体str *a的其实地址为0,而s相对于str *a的偏移量是4,所以输出了结果4。如果把char s[0]改为char *s,那么程序在if(f.a->s)这一行就会挂掉,因为指针的话就会访问指针的内容,因为不存在,所以程序会出现段错误。
结构体对齐的问题
结构体对齐是一个非常重要的概念,涉及到结构体在内存中的存储形式。在结构体中,先看几个概念。
- 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。
- 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
- 指定对齐值:#pragma pack (value)时的指定对齐值value。
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。
其中,有效对齐值N是最终用来决定数据存放地址方式的值。有效对齐N表示“对齐在N上”,即该数据的“存放起始地址%N=0”。而数据结构中的数据变量都是按定义的先后顺序存放。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐存放,结构体本身也要根据自身的有效对齐值圆整(即结构体成员变量占用总长度为结构体有效对齐值的整数倍)。
struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};
通过上面的知识可以知道A的大小为8字节,B的大小为12字节。