文章目录
测试环境为64位系统,32/64位系统仅部分类型大小不同,结构体大小计算方式没有差别
从一个简单的例子说起
struct{
int v_int;
}s;
s.v_int = 0x12345678;
printf("sizoef(int) is %ld\n", sizeof(int));
printf("sizoef(s) is %ld\n", sizeof(s));
输出结果
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(int) is 4
sizoef(s) is 4
这里,因为结构里里面只有一个变量,所以结构体大小
等于结构体里变量的大小
gdb查看内存布局
在给结构体里的变量赋完值后打断点,然后跑到这一步后,找到结构体首地址,打印内存里的内容。下面会省略前面的步骤
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 struct{
6 int v_int;
7 }s;
8
9 s.v_int = 0x12345678;
10
(gdb) b 10
Breakpoint 1 at 0x400535: file struct.c, line 10.
(gdb) r
Starting program: /home/velscode/struct
Breakpoint 1, main () at struct.c:11
11 printf("sizeof(int) is %ld\n", sizeof(int));
(gdb) p &s
$1 = (struct {...} *) 0x7fffffffde60
(gdb) x/32xb 0x7fffffffde60
0x7fffffffde60: 0x78 0x56 0x34 0x12 0xff 0x7f 0x00 0x00
0x7fffffffde68: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde70: 0x70 0x05 0x40 0x00 0x00 0x00 0x00 0x00
0x7fffffffde78: 0x40 0xd8 0xa2 0xf7 0xff 0x7f 0x00 0x00
这里可以看到,0x12345678
被存储在了0x7fffffffde60
~ 0X7fffffffde63
复杂了一点
让我们在结构体里加入一个新的char
类型的成员变量
struct{
int v_int;
char v_char; // 新增
}s;
s.v_int = 0x12345678;
s.v_char = 0x99;
printf("sizeof(char) is %ld\n", sizeof(char));
printf("sizeof(int) is %ld\n", sizeof(int));
printf("sizoef(s) is %ld\n", sizeof(s));
执行结果
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizeof(int) is 4
sizoef(s) is 8
虽然只多了1个字节
的变量,但是整个结构体的大小却从4字节
上升到了8字节
。为啥呢
这里涉及到一个知识点:字节对齐
打印内存情况
(gdb) p &s
$2 = (struct {...} *) 0x7fffffffde60
(gdb) x /32xb 0x7fffffffde60
0x7fffffffde60: 0x78 0x56 0x34 0x12 0x99 0x7f 0x00 0x00
0x7fffffffde68: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde70: 0x80 0x05 0x40 0x00 0x00 0x00 0x00 0x00
0x7fffffffde78: 0x40 0xd8 0xa2 0xf7 0xff 0x7f 0x00 0x00
蓝色的部分是有效数据。灰色的部分其实是被对齐
而占用的,其中没有存放有效数据。
那么,对齐的原则是什么呢
先说答案:对齐到一个有效值V
有效值V又是怎么计算出来的呢?他是取下列两个值中较小的一个:
- 结构体中最长类型的长度X
- 编译器指定的对齐值Y
即V = min{X, Y}
改变X进行验证
struct{
double v_double;
char v_char;
}s;
s.v_double = 0;
s.v_char = 0x99;
执行结果
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(double) is 8
sizeof(char) is 1
sizoef(s) is 16
分析:double占8字节,虽然char只占1字节,但是需要对齐到结构体中最长类型的整数倍,即8字节
所以最终结构体长度为16字节
查看内存布局
(gdb) p &s
$1 = (struct {...} *) 0x7fffffffde60
(gdb) x /32xb 0x7fffffffde60
0x7fffffffde60: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde68: 0x99 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffde70: 0xa0 0x05 0x40 0x00 0x00 0x00 0x00 0x00
0x7fffffffde78: 0x40 0xd8 0xa2 0xf7 0xff 0x7f 0x00 0x00
改变Y进行测试
可以通过如下面宏编译命令改变Y的值
#pragma pack(y)
这里y必须是2的整数倍。
我们还拿刚才的代码,加一行试一下:
#pragma pack(2) // 对齐到2字节
struct{
double v_double;
char v_char;
}s;
s.v_double = 0;
s.v_char = 0x99;
执行结果
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizeof(double) is 8
sizoef(s) is 10
分析内存布局
这里v_char虽然只占1字节,但是由于需要对齐到2字节,所以整个结构体长度为
8(double)+1(char)+1(对齐) = 10字节
再看个例子巩固一下
#pragma pack(4)
struct{
char v_char1;
char v_char2;
char v_char3;
}s;
做个分析,这里最长类型的长度X
为sizeof(char) 其值是 1
编译器指定的对齐长度Y
为手动设置的4
所以对齐长度
为min{1,4}
,是1
执行结果
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizoef(s) is 3
重要推论
如果想让结构体的大小
等于结构体中各个变量大小的和
,那么只需要让对齐当长度等于1
即可
由于对齐长度
取决于结构体中最长类型的大小
和编译器指定的对齐长度
中较小的一个。我们无法左右前者,所以只需要通过
#pragma pack(1)
将Y设置成1,即可让对齐长度V = min{?,1} = 1
一些习题
e.g 1
#pragma pack(2)
struct{
char v_char;
int v_int;
}s;
gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizeof(int) is 4
sizoef(s) is 6
e.g 2-1 挤一挤
#pragma pack(4)
struct{
char v_char1;
char v_char2;
int v_int;
}s;
velscode@ubuntu:~$ gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizeof(int) is 4
sizoef(s) is 8
说明:如果因为对齐,导致有空余空间没有利用,会先尝试能不能挤一挤,上图v_char1占了一个字节,要求4字节对齐,还剩下3字节,能够挤下v_char2
e.g 2-2 挤不下
#pragma pack(4)
struct{
char v_char1;
int v_int;
char v_char2;
}s;
gcc struct.c -o struct -g; ./struct
sizeof(char) is 1
sizeof(int) is 4
sizoef(s) is 12
说明:v_char1占用1字节,由于要求4字节对齐,还剩3字节。
但是int需要占用4字节,放不下,只能单独占用4字节
然后又要放v_char2占用1字节,剩3字节用来对齐
所以
结构体大小=1(v_char1) + 3(对齐) + 4(int) + 1(v_char2) + 3(对齐) = 12字节
一些有意思的问题
不对齐会带来什么问题
性能问题?字节对齐可能给CPU带来更快的处理速度(待编码验证)
这个问题和一位大神进行了探讨,他给出的结果是,不一定有效。因为字节对齐带来的处理器性能收益,已经远远比不上缓存等其他策略带来的性能收益。
这里又衍生出一个安全问题:处理器为了方便将一段代码提前装入L1 Cache,这段内存失去了OS级别的读写权限控制信息,容易泄漏敏感信息。
对齐会带来什么问题
- 由e.g 2-2可以看出,如果变量排布情况不佳的情况下,可能会造成非常多的内存空洞,浪费内存空间
- 内存空洞没有被清0
为什么不清零?:性能问题
这可能导致攻击者构造一个特殊的结构体,里面存在巨量的内存空洞,把内存中的信息dump出来