这个结构体对齐输出有意思
共 4119字,需浏览 9分钟
·
2020-08-03 08:04
这个题目是我在群里看到大家讨论的,既然是讨论的了,那我就拿出来说说,因为笔试面试的时候,可能就会遇到这样的题目。
实例代码
#include "stdio.h"
#include "stdint.h"
struct Obj {
char a; //1
uint32_t b;//4
uint8_t c;//1
uint64_t d[0];//8
};
int main()
{
struct Obj Op;
printf("%d %d\n",sizeof(Op),sizeof(Op.d));
return (0);
}
程序输出
16 0
--------------------------------
Process exited after 0.03048 seconds with return value 0
请按任意键继续. . .
这里比较令我们疑惑的是,d 的大小明明是 0,为甚结构体的大小会是 16呢?
调戏一下
看看下面代码的输出
#include "stdio.h"
#include "stdint.h"
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
#pragma pack(1)
struct Obj {
char a;
uint32_t b;
uint8_t c;
uint64_t d[0];
};
int main()
{
struct Obj Op;
PRINT_D(OFFSET(struct Obj,a));
PRINT_D(OFFSET(struct Obj,b));
PRINT_D(OFFSET(struct Obj,c));
PRINT_D(OFFSET(struct Obj,d));
printf("%d %d\n",sizeof(Op),sizeof(Op.d));
return (0);
}
程序输出
OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 1
OFFSET(struct Obj,c) is 5
OFFSET(struct Obj,d) is 6
6 0
--------------------------------
Process exited after 0.0108 seconds with return value 0
请按任意键继续. . .
这里的输出刚好是我们的结构体里内容的大小
解释下这段调试代码的作用
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
前面哪个比较简单了,就是使用 「#」这个符号把字符串带过来打印。下面的OFFSET 比较有意思,先是把 0 这个地址强制转换成我们需要的strunct ,然后呢,再读取地址减去0地址,这样就可以得出它的偏移大小了。
调戏一下2
我们改一下代码
#include "stdio.h"
#include "stdint.h"
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
#pragma pack(4)
struct Obj {
char a;
uint32_t b;
uint8_t c;
uint64_t d[0];
};
int main()
{
struct Obj Op;
PRINT_D(OFFSET(struct Obj,a));
PRINT_D(OFFSET(struct Obj,b));
PRINT_D(OFFSET(struct Obj,c));
PRINT_D(OFFSET(struct Obj,d));
printf("%d %d\n",sizeof(Op),sizeof(Op.d));
return (0);
}
程序输出
OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 12
12 0
--------------------------------
Process exited after 0.01165 seconds with return value 0
请按任意键继续. . .
调戏代码3
#include "stdio.h"
#include "stdint.h"
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
#pragma pack(8)
struct Obj {
char a;
uint32_t b;
uint8_t c;
uint64_t d[0];
};
int main()
{
struct Obj Op;
PRINT_D(OFFSET(struct Obj,a));
PRINT_D(OFFSET(struct Obj,b));
PRINT_D(OFFSET(struct Obj,c));
PRINT_D(OFFSET(struct Obj,d));
printf("%d %d\n",sizeof(Op),sizeof(Op.d));
return (0);
}
程序输出
OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 16
16 0
--------------------------------
Process exited after 0.01219 seconds with return value 0
请按任意键继续. . .
结构体对齐大小的方式,这个背下,不背下就存下
原则A:struct或者union的成员,第一个成员在偏移0的位置,之后的每个成员的起始位置必须是当前成员大小的整数倍;
原则B:如果结构体A含有结构体成员B,那么B的起始位置必须是B中最大元素大小整数倍地址;
原则C:结构体的总大小,必须是内部最大成员的整数倍;
这几个原则是在 没有 #pragma pack 的时候才起作用的,有了 #pragma pack,就按照 #pragma pack 的方式去对齐。
分析一下
基于上面的实验和理论,我们可以知道,这个笔试题的输出结果是因为 uint64_t d[] 这个搞鬼了,就是因为这个搞鬼了,我们结构体的最终大小才是 16。因为 uint64_t d 是 8个字节,这样结构体就是以 8 字节的方式对齐了。
虽然d 的不占用内存的,但是这个家伙的存在让结构体的对齐方式产生了改变,就是这么神奇。
为了验证我们的想法,我们改下程序
实例代码
#include "stdio.h"
#include "stdint.h"
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define OFFSET(struct,member) ((char *)&((struct *)0)->member - (char *)0)
struct Obj {
char a;
uint32_t b;
uint8_t c;
uint32_t d[0];
};
int main()
{
struct Obj Op;
PRINT_D(OFFSET(struct Obj,a));
PRINT_D(OFFSET(struct Obj,b));
PRINT_D(OFFSET(struct Obj,c));
PRINT_D(OFFSET(struct Obj,d));
printf("%d %d\n",sizeof(Op),sizeof(Op.d));
return (0);
}
程序输出
OFFSET(struct Obj,a) is 0
OFFSET(struct Obj,b) is 4
OFFSET(struct Obj,c) is 8
OFFSET(struct Obj,d) is 12
12 0
--------------------------------
Process exited after 0.01002 seconds with return value 0
请按任意键继续. . .
看到没?
看到没?
看到没?
这样之后,程序的输出就是 12 了,也就是说,现在的程序是按照 4字节对齐方式来对齐了。