C struct 中字节对齐问题

规则:

1. 其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了对齐处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

2. 自然对界:(natural alignment)即默认对齐方式,是指按结构体的成员中size最大的成员对齐。

3. 指定对界:一般地,可以通过下面的方法来改变缺省的对界条件:

· 使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

注意:如果#pragma pack (n)中指定的n大于结构体中最大成员的size,则其不起作用,结构体仍然按照size最大的成员进行对界。

VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须满足为n的倍数

VC中下面几个结构体大小分别是多少呢
struct MyStruct
{
double m4;
char m1;
int m3
};

struct MyStruct{
char m1;
double m4;
int m3;
};

#pragmapack(push)//保存对齐状态
#pragma pack(16)
//设置为16字节对齐
struct test
{
char m1;
int m3;
doublem4;
};

#pragma pack(pop)//
恢复对齐状态
如果你的答案不是162416,相信下面的内容对你很有帮助。

1 sizeof应用在结构上的情况
请看下面的结构:
struct MyStruct
{
double dda1;
char dda;
int type
};
对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)16。你知道为什么在VC中会得出这样一个结果吗?
其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了对齐处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的

对齐方式(vc6.0,32位系统)
类型对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
char
偏移量必须为sizeof(char)1的倍数
short
偏移量必须为sizeof(short)2的倍数
int
偏移量必须为sizeof(int)4的倍数
float
偏移量必须为sizeof(float)4的倍数
double
偏移量必须为sizeof(double)8的倍数
各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
下面用前面的例子来说明VC到底怎么样来存放结构的。
struct MyStruct
{
double dda1;
char dda;
int type
}

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:
struct MyStruct
{
char dda;
double dda1;
int type
}

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)
struct MyStruct
{
char dda; //
偏移量为0,满足对齐方式,dda占用1个字节;
double dda1; //
下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8(满足对齐方式),因此VC自动填充7个字节,dda1存放在偏移量为8的地址上,它占用8个字节。
int type
//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍数,满足int的对齐方式,所以不需要VC自动填充,type存放在偏移量为16的地址上,它占用4个字节。
}

所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为sizeof(double)=8的倍数。
所以该结构总的大小为:sizeof(MyStruc)1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
VC
对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC
中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况

:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法。
#pragma pack(push) //
保存对齐状态
#pragma pack(4)//
设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//
恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n,m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24
2
sizeof用法总结
VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。
A
参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。
B
参数为数组或指针。下面举例说明.
int a[50]; //sizeof(a)=4*50=200;
求数组所占的空间大小
int *a=new int[50];// sizeof(a)=4; a
为一个指针,sizeof(a)是求指针
//
的大小,32位系统中,当然是占4个字节。
C
参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。
第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一
个实例在内存中都有唯一的地址。
下面举例说明,
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,s
为一个指针。
Class test1{ };//sizeof(test1)=1;
D
参数为其他。下面举例说明。
int func(char s[5]);
{
cout< //
数的参数在传递的时候系统处理为一个指针,所
//
sizeof(s)实际上为求指针的大小。
return 1;
}
sizeof(func(“1234”))=4//
因为func的返回类型为int,所以相当于
//
sizeof(int).
以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略.


在网络协议、通信控制、嵌入式系统的C/C++编程中,我们经常要传送的不是简单的字节流(char型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。

  经验不足的开发人员往往将所有需要传送的内容依顺序保存在char型数组中,通过指针偏移的方法传送网络报文等信息。这样做编程复杂,易出错,而且一旦控制方式及通信协议有所变化,程序就要进行非常细致的修改。

  一个有经验的开发者则灵活运用结构体,举一个例子,假设网络或控制协议中需要传送三种报文,其格式分别为packetApacketBpacketC

struct structA
{
int a;
char b;
};

struct structB
{
char a;
short b;
};

struct structC
{
int a;
char b;
float c;
}

  优秀的程序设计者这样设计传送的报文:

structCommuPacket
{
int iPacketType;
//报文类型标志
union
//每次传送的是三种报文中的一种,使用union
{
struct structA packetA;
struct structB packetB;
struct structC packetC;
}
};

  在进行报文传送时,直接传送structCommuPacket一个整体。

  假设发送函数的原形如下:

// pSendData:发送字节流的首地址,iLen:要发送的长度
Send(char * pSendData, unsigned int
iLen);
发送方可以直接进行如下调用发送struct CommuPacket的一个实例sendCommuPacket
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
假设接收函数的原形如下:
// pRecvData
:发送字节流的首地址,iLen:要接收的长度
//
返回值:实际接收到的字节数
unsigned int Recv(char * pRecvData, unsigned int
iLen)

  接收方可以直接进行如下调用将接收到的数据保存在structCommuPacket的一个实例recvCommuPacket中:

Recv( (char*)&recvCommuPacket , sizeof(CommuPacket) );

  接着判断报文类型进行相应处理:

switch(recvCommuPacket.iPacketType)
{
case PACKET_A:
//A类报文处理
break;
case PACKET_B:
//B类报文处理
break;
case PACKET_C:
//C类报文处理
break;
}

  以上程序中最值得注意的是

Send( (char*)&sendCommuPacket , sizeof(CommuPacket) );
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );

  中的强制类型转换:(char*)&sendCommuPacket(char*)&recvCommuPacket,先取地址,再转化为char型指针,这样就可以直接利用处理字节流的函数。

  利用这种强制类型转化,我们还可以方便程序的编写,例如要对sendCommuPacket所处内存初始化为0,可以这样调用标准库函数memset()

memset((char*)&sendCommuPacket,0, sizeof(CommuPacket));

2. struct的成员对齐

Intel、微软等公司曾经出过一道类似的面试题:

1. #include <iostream.h>

2. #pragma pack(8)
3. struct example1
4. {
5. short a;
6. long b;
7. };

8. struct example2
9. {
10. char c;
11. example1 struct1;

//structexample2中包含了struct example1,其本身包含的简单数据成员的最大size2short变量e),但是因为其包含了struct example1,而struct example1中的最大成员size4struct example2也应以4对界,#pragma pack (8)中指定的对界对struct example2也不起作用,故19行的输出结果为16

12. shorte;
13. };
14. #pragma pack()

15. int main(int argc, char* argv[])
16. {
17. example2 struct2;

18. cout << sizeof(example1) << endl;
19. cout << sizeof(example2) << endl;
20. cout << (unsigned int)(&struct2.struct1) - (unsignedint)(&struct2)
<< endl;

21. return 0;
22. }

问程序的输入结果是什么?

答案是:

8
16
4

  不明白?还是不明白?下面一一道来:


2.1自然对界

struct是一种复合数据类型,其构成元素既可以是基本数据类型(如 intlongfloat等)的变量,也可以是一些复合数据类型(如arraystructunion等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。缺省情况下,编译器为结构体的每个成员按其自然对界(naturalalignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

  自然对界(naturalalignment)即默认对齐方式,是指按结构体的成员中size最大的成员对齐。例如:

structnaturalalign
{
char a;
short b;
char c;
};

  在上述结构体中,size最大的是short,其长度为2字节,因而结构体中的char成员ac都以2为单位对齐,sizeof(naturalalign)的结果等于6

  如果改为:

structnaturalalign
{
char a;
int b;
char c;
};

  其结果显然为12

2.2指定对界

  一般地,可以通过下面的方法来改变缺省的对界条件:

· 使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

  注意:如果#pragma pack (n)中指定的n大于结构体中最大成员的size,则其不起作用,结构体仍然按照size最大的成员进行对界。

  例如:

#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()

  当n4816时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n2时,其发挥了作用,使得sizeof(naturalalign)的结果为8

  在VC++ 6.0编译器中,我们可以指定其对界方式(见图1),其操作方式为依次选择projetct >setting > C/C++菜单,在struct memberalignment中指定你要的对界方式。

1VC++ 6.0中指定对界方式

2.3面试题的解答

  至此,我们可以对Intel、微软的面试题进行全面的解答。

  程序中第2#pragma pack (8)虽然指定了对界为8,但是由于struct example1中的成员最大size4long变量size4),故struct example1仍然按4字节对界,struct example1size8,即第18行的输出结果;

struct example2中包含了struct example1,其本身包含的简单数据成员的最大size2short变量e),但是因为其包含了struct example1,而struct example1中的最大成员size4struct example2也应以4对界,#pragma pack (8)中指定的对界对struct example2也不起作用,故19行的输出结果为16

  由于struct example2中的成员以4为单位对界,故其char变量c后应补充3个空,其后才是成员struct1的内存空间,20行的输出结果为4

3. CC++struct的深层区别

  在C++语言中struct具有了 的功能,其与关键字class的区别在于struct中成员变量和函数的默认访问权限为public,而class的为private

  例如,定义struct类和class类:

struct structA
{
char a;

}
class classB
{
char a;

}

  则:

struct A a;
a.a = 'a';
//访问public成员,合法
classB b;
b.a = 'a';
//访问private成员,不合法

  许多文献写到这里就认为已经给出了C++structclass的全部区别,实则不然,另外一点需要注意的是:

C++中的struct保持了对Cstruct的全面兼容(这符合C++的初衷——“a better c”),因而,下面的操作是合法的:

//定义struct
struct structA
{
char a;
char b;
int c;
};
structA a = {'a' , 'a' ,1};
// 定义时直接赋初值

  即struct可以在定义的时候直接以{ }对其成员变量赋初值,而class则不能,在经典书目《thinking C++ 2ndedition》中作者对此点进行了强调。

4. struct编程注意事项

  看看下面的程序:

1. #include<iostream.h>

2. struct structA
3. {
4. int iMember;
5. char *cMember;
6. };

7. int main(int argc, char* argv[])
8. {
9. structA instant1,instant2;
10.char c = 'a';

11. instant1.iMember = 1;
12. instant1.cMember = &c;

13.instant2 = instant1;

14.cout << *(instant1.cMember) << endl;

15.*(instant2.cMember) = 'b';

16. cout << *(instant1.cMember) << endl;

17. return 0;
}

14行的输出结果是:a
16行的输出结果是:b

Why?我们在15行对instant2的修改改变了instant1中成员的值!

  原因在于13行的instant2 =instant1赋值语句采用的是变量逐个拷贝,这使得instant1instant2中的cMember指向了同一片内存,因而对instant2的修改也是对instant1的修改。

  在C语言中,当结构体中存在指针型成员时,一定要注意在采用赋值语句时是否将2个实例中的指针型成员指向了同一片内存。

  在C++语言中,当结构体中存在指针型成员时,我们需要重写struct的拷贝构造函数并进行“=”操作符重载。


:

默认对齐方式:
struct
name1
{
char str;

short x;
int num;
double xx;
};

sizeof(struct name1)=16个字节

内在存放地址: &name1.str= 1310576; &name1.x =1310578;

&name1.num =1310580; &name1.xx =1310584;
struct
name2
{
char str;
int num;
short x;
double xx;
};

struct name224个字节

内存存放地址: &name1.str= 1310552; &name1.num = 1310556;

&name1.x =1310560; &name1.xx = 1310568;



这个问题应该跟编译器有关系。
对于结构体中的某一成员item,它相对于结构首地址的实际字节对齐数目X应该满足
以下规则:
X = min(n, sizeof(item))n 是编译器设定的最大对齐边界数。
如果n = 8 .
struct name1
{
char str; 偏移为0 ,从第一个字节位置存储,占1个字节
short x; 偏移为2 从第三个字节位置开始存储,占2个字节
int num; 偏移为4,由于前两个占了4个字节,所以从第五个字节开始存储。占4个字节
double xx; 偏移为8,由于前两个占了8个字节,所以从第9个字节开始存储。占8个字节
};
一共占16个字节。
struct name2
{
char str; 偏移为0 ,从第一个字节位置存储,占1个字节
int num; 偏移为4由于前两个占了1个字节,所以从第五个字节开始存储。占4个字
short x; 偏移为2,由于前两个占了8个字节从第九个字节位置开始存储,占2个字节
double xx;偏移为8,由于前两个占了10个字节,所以从第17个字节开始存储占8个字节
};
一共占24个字节