【C语言面试题】结构体大小计算相关问题

测试环境为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又是怎么计算出来的呢?他是取下列两个值中较小的一个:

  1. 结构体中最长类型的长度X
  2. 编译器指定的对齐值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;

做个分析,这里最长类型的长度Xsizeof(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级别的读写权限控制信息,容易泄漏敏感信息。

对齐会带来什么问题

  1. 由e.g 2-2可以看出,如果变量排布情况不佳的情况下,可能会造成非常多的内存空洞,浪费内存空间
  2. 内存空洞没有被清0

为什么不清零?:性能问题

这可能导致攻击者构造一个特殊的结构体,里面存在巨量的内存空洞,把内存中的信息dump出来

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中的结构对齐是一个经常被问到的面试。这里我来简单解释一下。 在C语言中,结构是一种用户自定义的数据类型,它允许我们把多个不同类型的变量组合在一起,以便更方便地管理和使用它们。 在内存中,结构的存储空间是按照成员变量的顺序依次分配的。但是,为了提高访问效率,编译器会对结构进行对齐操作,也就是将结构的起始地址调整为成员变量大小的整数倍。 例如,一个包含三个成员变量的结构: ``` struct Test { char a; int b; short c; }; ``` 在32位系统中,char占1个字节,int占4个字节,short占2个字节,所以这个结构大小应该是1 + 4 + 2 = 7字节。但是,如果我们直接按顺序分配,结构的起始地址是一个奇数,这样访问效率会降低。因此,编译器会在结构的成员变量之间插入一些字节,使得结构的起始地址是4的倍数。这样,结构大小就会变成12字节。 具的对齐规则和字节数是由编译器决定的,不同的编译器可能会有不同的规则。但是,大多数编译器都会使用和本例相似的规则。 在面试中,经常会出现一些关于结构对齐的问题,例如: 1. 结构大小是多少? 2. 结构成员变量的顺序会影响结构大小吗? 3. 如何使用#pragma pack指令来控制结构对齐? 4. 等等。 对于这些问题,我们需要对结构对齐有一个深刻的理解,才能够正确回答。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值