定义了结构体类型Student,具体如下:
struct Student {
int num;//0
char name[8];
double score;
} stu;
printf("%d\n", sizeof(stu)); //输出24,这个没有疑问
printf("%d\n", offsetof(student, name)); //输出偏移量为4
printf("%d", offsetof(student, score)); //输出偏移量为16
不理解的是:为什么成员name数组的偏移量为4,根据规则对齐数为成员自身大小与默认对齐数的最小值的整数倍,所以name数组的偏移量不是应该为8吗?
例如下面的例子:
struct Test {
int x;
double y;
} t1;
printf("%d\n", sizeof(t1));//输出16
printf("%d\n", offsetof(Test, y)); //输出8
这个例子中,double类型的成员y的偏移量就为8,而如果把y换成同样长度为8字节的字符数组,偏移量又变为4了,这是为什么呢?
在结构体中,成员的内存布局是需要考虑内存对齐的。
内存对齐是指在内存中,为了让数据读写更快,成员在内存中的布局需要满足某些特定的对齐规则。
通常,一个成员的对齐数等于它的大小,或者是默认对齐数的整数倍。
在上面的代码中,结构体student中的成员num的大小为4个字节,因此其对齐数为4。
而默认对齐数通常是4或8,因此结构体student的对齐数为4。
在结构体student中,成员name是一个字符数组,大小为8个字节,
因此它的对齐数为4(与默认对齐数相同)。因此,成员name的偏移量为4。
同样地,成员score是一个double类型,大小为8个字节,
因此它的对齐数为8(大于默认对齐数)。因此,成员score的偏移量为16,以便它与其他数据对齐。
可以看出,为了满足内存对齐,结构体中的某些成员的偏移量可能比它们的大小更大。
在这种情况下,在结构体的布局中会有一些“空隙”,以满足对齐要求。
这里的这个讲解不错,其中的思路可以作为你的参考建议:C结构体中数据的内存对齐问题,链接:https://www.cnblogs.com/x_wukong/p/5743345.html
我是这样理解的
规则:64位下默认对齐数default=8,那么每一个变量tmp的对齐数real=min{default,tmp}。所以:
struct Student {
int num;//0
char name[8];
double score;
} stu;
这个结构体stu中,第一个成员num为int的对齐数real_num=min{8,4}=4
第二个成员name为char类型,对齐数real_num=min{8,1}=1
第三个成员score为double类型,对齐数real_score=min{8,8}=8
而偏移量offset必须是real的倍数
所以第一个成员num偏移量startid=0*real_num=0*4=0,从startid开始size1=4即4个bytes被用掉即[id=0~3已用]
因为上面size1=4已用4个bytes。第二个成员name偏移量是startid=real_num*倍数=1*倍数,必须>3,故此时倍数取4,所以偏移量startid=4,而num的size2=8,[所以id=4~11已用]
因为上面size1+size2=12已用12个bytes。而第三个成员score的偏移量startid=real_score*倍数=8*倍数,必需>11,所以倍数不能取0或1,只能取2,所以startid=8*2=16,即从16开始放score,而score的大小size3=8,此时id=16~23已用。
所以stu的size等于id=0一直到id=23即size=24
所以按我上面说的继续套
struct Test {
int x;
double y;
} t1;
对x来说 real_x=min{8,4}=4 偏移量startid=real_x * 倍数=4 * 0=0,而size1=4,即id=0~3被使用
对y来说 real_y=min{8,8}=8 偏移量startid=real_y * 倍数=8 * 倍数,同时必需>3,所以倍数就是1,startid=8
当你换成char y[8]后;
对y来说,real_y=min{8,1}=1 偏移量startid=real_y * 倍数=1 * 倍数,同时必需>3,所以倍数就是4,startid=4
按这样去套,清晰明了,无论什么结构体都能立刻知道成员偏移量,自然也就知道了整个结构体的大小。
char 的大小为1字节,比默认对齐小,按默认对齐
double 的大小为8字节,比默认对齐大,按默认对齐的整数倍对齐,且要大于等于8,所以是8对齐
总得来说有两个规则:
成员的起始地址,是按成员类型大小的倍数来对齐
结构体总的大小,按成员类型中最大的倍数来对齐;
// 结构体起始地址为0来说明
struct Student {
int num; // 这里int按4字节
char name[8]; // 这里类型为char,那么起始地址就是挨着上个成员,也就是4
double score; // double为8字节,那么它的起始地址为8的倍数,上面两个成员 num+name = 4+8=12字节,那么这里会有4字节空余,score地址从16起始
} stu;
// 结构体的大小,是按double 8字节倍数来计算,也就是24
// 那下面这个结构体用规则来解析一下
struct Test {
int x; // int占4字节,起始地址为 0
double y; // double 占8字节,起始地址为8的倍数,也就是x成员后面4字节空闲,y地址从8开始;
} t1;
// 结构体大小就是 8的倍数,为16
//这两个对第二条规则没有很明白体现,把成员顺序变动一下就能看出来
struct Test {
double y; // double 占8字节,起始地址为0
int x; // int占4字节,起始地址为 4的倍数,y的结束位置为8,是4的倍数,那么x起始地址就是 8
} t1;
// 结构体大小那么是 8+4 吗? 错了! 结构体大小是8的倍数,应该是16,x后面有4字节是空闲的
明白了吗? 有疑问可以继续交流~
结构体起始地址为0来说明
struct Student {
int num; // 这里int按4字节
char name[8]; // 这里类型为char,那么起始地址就是挨着上个成员,也就是4
double score; // double为8字节,那么它的起始地址为8的倍数,上面两个成员 num+name = 4+8=12字节,那么这里会有4字节空余,score地址从16起始
} stu;
结构体的大小,是按double 8字节倍数来计算,也就是24
https://blog.csdn.net/hola_f/article/details/51829528
这是因为C语言中的内存对齐规则,当结构体中包含字符数组时,编译器会将字符数组的偏移量设置为4,以便提高内存访问效率。而当结构体中包含double类型的成员时,编译器会将double类型的成员的偏移量设置为8,以便提高内存访问效率。