c语言之不再害怕sizeof(struct)
程序员在面试的时候经常会碰到一些题目,给出一个结构体,然后sizeof它一下,问值是多少?比如给出下面这样一个结构体:
[cpp]
struct test{
char a;
short b;
char c;
int d;
};
也许一些刚开始学习c语言的同学就会毫不犹豫的把struct当中每个变量所占用的空间相加,等到的结果是8。之后结果就错了。为什么呢,首先让把他们的地址打印出来看个究竟,这是打印出来的结果:a=0x00000000,b=0x0000002,c=0x00000004,d=0x00000008. 很奇怪吧,不像我们预想的那样,他们是按顺序存储的。这就涉及到一个内存排列的问题:内存对齐。
首先解释一下,为什么要内存对齐呢,这和我们的处理器的特性有关系,总线读取内存总是从偶数字节开始的,如果按照顺序进行排列的话,short b 排列的内存地址应该是:0x00000001,但是它需要占用两个字节的内存空间,如果它要在这个内存地址开始存放的话,需要被读取两次,而且读取完成之后还要进行内存内容拼接,才能完整的得到这个short型的变量。如果它在b=0x0000002这个地址开始存放,那么只需要读取一次而且不需要进行内存内容的拼接。然后变量c开始从b之后存放,地址是0x00000004,但是它只占了一个字节,同样的道理,剩余的空间不能用来存放,但是既然处理器是从偶数字节开始读取的,那么为什么d的开始地址是0x00000008呢。别着急,还有一个规定,就是一个字也就是16bits,双字(32bits)操作数如果跨越了4字节边界,或者一个四字操作数跨越了8字节边界被认为是未对齐的。也就是说,如果d从0x00000006开始存放的话,那他就要跨越0x00000008这个能整除4的边界,故而需要两次的内存读写。可以回头看看short b,它是从0x00000002开始存放的,但是它并没有跨越0x00000004这个边界值。
还可以举出这样一个例子:
[cpp]
#include<iostream>
using namespace std;
struct test{
char a;
char b;
char c;
short d;
int e;
};
int main() {
struct test t;
int x1 = (unsigned int)(void*)&t.a - (unsigned int)(void*)&t;
int x2 = (unsigned int)(void*)&t.b - (unsigned int)(void*)&t;
int x3 = (unsigned int)(void*)&t.c - (unsigned int)(void*)&t;
int x4 = (unsigned int)(void*)&t.d - (unsigned int)(void*)&t;
int x5 = (unsigned int)(void*)&t.e - (unsigned int)(void*)&t;
printf("a=0x%p,b=0x%p,c=0x%p,d=0x%p,e=0x%p", x1, x2, x3, x4, x5);
cin.get();
}
可以看看打印出来的结果, d的其实地址是0x00000004, 因为如果它从0x00000003开始存放的话,那就它跨越了边界。
还有一个比较特殊的情况,那就是char类型的变量比较随和,因为他在32位的处理器中就占有一个字节,因此把它放在哪里它都不介意,也就是说,因为它只占一个字节,无论存放在哪里,都只需要读取一次。
看这样一个例子:
[cpp]
struct test{
short a;
char b;
char c
int d;
}
打印出来的结果是:a=0x00000000,b=0x0000002,c=0x00000003,d=0x00000008.
缺省的情况下,编译器默认把结构,栈中的成员数据进行内存对齐。这样以浪费内存空间的方式节省总线运作的代价。
接下来说一下 #pragma pack(n)这个预处理,主要的功能是改变内存对齐方式的选项,按照给定的n字节进行内存对齐。但是结构体成员对齐的方式有一个很重要的特点,就是最小原则。结构体成员对齐的规则如下: 将自己的本身在内存中实际占用的字节和当前由#pragma pack(n)设定的n进行比较,取其中最下的那个作为结构体当前成员的对齐方式,但是不影响其他结构体成员的对齐方式。
举个例子:
[cpp]
#pragma pack(8)
struct test_st1{
char a;
long b;
};
struct test_st2{
char c;
struct test_st1 st1;
long long d;
};
上面这个例子设定的内存对齐方式是8字节对齐形式。那么我们看看结构体test_st1,其中a所占内存大小为1,和规定的比较,取最小的,故对齐方式为1字节对齐,对于成员b,因为它占用4个字节,而规定的是8,所以取最小的,对齐方式为4字节对齐,就是从内存地址可以整除4紧挨a存放的地址开始存放b,可以得到结构体的大小为8字节。
之后再来看看结构体test_st2这个结构体,c是本身是一个字节,所以对齐方式是1,而st1是一个结构,那么这个结构本身在其他结构体中,对齐的方式是什么呢,是以结构体的大小和给定的对齐方式做比较吗?不对,它的对齐方式是它成员变量中最大的那个成员变量所占的内存空间和给定的值进行比较,继而,st1的对齐方式是4,它的起始地址是可以整除4的地方开始。 对于d,因为它占用8个字节的内存,所以它的对齐方式是8,c和st1用去了12个字节,所以d从内存地址可以整除8的地方开始存放,所以这个test_st2结构体的大小是24.给出一个完整的测试程序:
[cpp]
#include<iostream>
using namespace std;
#pragma pack(8)
struct test_st1{
char a;
long b;
};
struct test_st2{
char c;
struct test_st1 st1;
long long d;
};
int main() {
struct test_st2 t;
int x1 = (unsigned int)(void*)&t.c - (unsigned int)(void*)&t;
int x2 = (unsigned int)(void*)&t.st1.a - (unsigned int)(void*)&t;
int x3 = (unsigned int)(void*)&t.st1.b - (unsigned int)(void*)&t;
int x4 = (unsigned int)(void*)&t.d - (unsigned int)(void*)&t;
printf("a=0x%p,b=0x%p,c=0x%p,d=0x%p", x1, x2, x3, x4);
cin.get();
}
可以自行调试一下,看看内存中他的内存排列。
补充:软件开发 , C语言 ,