您的位置:首页 > 编程语言 > C语言/C++

[C++]字节对齐与结构体大小

2012-09-08 18:19 323 查看


在定义一个结构体类型的时候,一定要注意 字节对齐,下面是对齐的形式,可以去掉#pragma试试看,两者的运行结果不同。


自己的测试:

#include <stdio.h>

typedef unsigned char uint8_t;

typedef unsigned short uint16_t;


#pragma pack(push,1)   //默认4个字节对齐,自定义1个字节对齐

typedef struct __sub_setdev

{

uint8_t  x1;

uint16_t x2;

}sub_setdev_s;

typedef struct __setdev_s

{

uint8_t one;

uint16_t two;

uint8_t n1;

uint8_t n2;

uint8_t n3;

uint8_t n4;

sub_setdev_s xx;

}setdev_s;

#pragma pack(pop)//取消自定义


int main(void)

{

setdev_s *m_test;

int len=sizeof(setdev_s);

printf("len is %d\n",len);

uint8_t buffer[20];


buffer[0]=0x10;

buffer[1]=40;

buffer[2]=0;


m_test=(setdev_s *)malloc(sizeof(setdev_s));

memcpy((void *)m_test,&buffer[0],sizeof(setdev_s));



printf("m_test %d %d\n",m_test->one,m_test->two);

return 0;

}







一下转自: http://pppboy.blog.163.com/blog/static/30203796201082494026399/

说明:


结构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题。这些问题在平时编程的时候也确实不怎么用到,但在一些笔试面试题目中出是常常出现,对sizeof我们将在另一篇文章中总结,这篇文章我们只总结结构体的sizeof,报着不到黄河心不死的决心,终于完成了总结,也算是小有收获,拿出来于大家分享,如果有什么错误或者没有理解透的地方还望能得到提点,也不至于误导他人。

一、解释

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。

二、准则

其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:

1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

2. 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;

3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

三、基本概念

字节对齐:计算机存储系统中以Byte为单位存储数据,不同数据类型所占的空间不同,如:整型(int)数据占4个字节,字符型(char)数据占一个字节,短整型(short)数据占两个字节,等等。计算机为了快速的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字符型数据(char)可以存放在任何地址位置(被1整除),短整型(short)数据存储在地址能被2整除的起始位置。这就是默认字节对齐方式。

四、结构体长度求法

1.成员都相同时(或含数组且数组数据类型同结构体其他成员数据类型):

结构体长度=成员数据类型长度×成员个数(各成员长度之和);

结构体中数组长度=数组数据类型长度×数组元素个数;

2.成员不同且不含其它结构体时;

(1).分析各个成员长度;

(2).找出最大长度的成员长度M(结构体的长度一定是该成员的整数倍);

(3).并按最大成员长度出现的位置将结构体分为若干部分;

(4).各个部分长度一次相加,求出大于该和的最小M的整数倍即为该部分长度

(5).将各个部分长度相加之和即为结构体长度

3.含有其他结构体时:

(1).分析各个成员长度;

(2).对是结构体的成员,其长度按b来分析,且不会随着位置的变化而变化;

(3).分析各个成员的长度(成员为结构体的分析其成员长度),求出最大值;

(4).若长度最大成员在为结构体的成员中,则按结构体成员为分界点分界;

其他成员中有最大长度的成员,则该成员为分界点;

求出各段长度,求出大于该和的最小M的整数倍即为该部分长度

(5).将各个部分长度相加之和即为结构体长度

五、空结构体

struct S5 { };

sizeof( S5 ); // 结果为1

“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。

六、有static的结构体

struct S4{

char a;

long b;

static long c; //静态

};

静态变量存放在全局数据区内,而sizeof计算栈中分配的空间的大小,故不计算在内,S4的大小为4+4=8。

七、举例说明

1.举例1

很显然默认对齐方式会浪费很多空间,例如如下结构:

struct student

{

char name[5];

int num;

short score;

}

本来只用了11bytes(5+4+2)的空间,但是由于int型默认4字节对齐,存放在地址能被4整除的起始位置,即:如果name[5]从0开始存放,它占5bytes,而num则从第8(偏移量)个字节开始存放。所以sizeof(student)=16。于是中间空出几个字节闲置着。但这样便于计算机快速读写数据,是一种以空间换取时间的方式。其数据对齐如下图:

|char|char|char|char|

|char|----|----|----|

|--------int--------|

|--short--|----|----|

如果我们将结构体中变量的顺序改变为:

struct student

{

int num;

char name[5];

short score;

}

则,num从0开始存放,而name从第4(偏移量)个字节开始存放,连续5个字节,score从第10(偏移量)开始存放,故sizeof(student)=12。其数据对齐如下图:

|--------int--------|

|char|char|char|char|

|char|----|--short--|

如果我们将结构体中变量的顺序再次改为为:

struct student

{

int num;

short score;

char name[5];

}

则,sizeof(student)=12。其数据对齐如下图:

|--------int--------|

|--short--|char|char|

|char|char|char|----|

2.举例2

(1)

struct test1

{ int a;

int b[4];

};

sizeof(test1)=sizeof(int)+4*sizeof(int)=4+4*4=20;

2)

struct test2

  { char a;

   int b;

   double c;

   bool d;

  };



分析:该结构体最大长度double型,长度是8,因此结构体长度分两部分:

第一部分是a、 b、 c的长度和,长度分别为1,4,8,则该部分长度和为13,取8的大于13的最小倍数为16;

第二部分为d,长度为1,取大于1的8的最小倍数为8,

两部分和为24,故sizeof(test2)=24;

3)

struct test3

{

char a;

test2 bb;//见上题

int cc;

}

分析:该结构体有三个成员,其中第二个bb是类型为test2的结构体,长度为24,且该结构体最大长度成员类型为double型,以后成员中没有double型,所以按bb分界为两部分:

第一部分有a 、bb两部分,a长度为1,bb长度为24,取8的大于25的最小倍数32;

第二部分有cc,长度为4,去8的大于4的最小倍数为8;

两部分之和为40,故sizeof(test3)=40;

(4)

struct test4

{

char a;

int b;

};

struct test5

{ char c;

test4 d;

double e;

bool f;

};

求sizeof(test5)

分析:test5明显含有结构体test4,按例2容易知道sizeof(test4)=8,且其成员最大长度为4;则结构体test5的最大成员长度为8(double 型),考试.大提示e是分界点,分test5为两部分:

第一部分由c 、d、e组成,长度为1、8、8,故和为17,取8的大于17的最小倍数为24;

第二部分由f组成,长度为1,取8的大于1的最小倍数为8,

两部分和为32,故sizeof(test5)=24+8=32;



八、union

union的长度取决于其中的长度最大的那个成员变量的长度。即union中成员变量是重叠摆放的,其开始地址相同。

其实union(共用体)的各个成员是以同一个地址开始存放的,每一个时刻只可以存储一个成员,这样就要求它在分配内存单元时候要满足两点:

1.一般而言,共用体类型实际占用存储空间为其最长的成员所占的存储空间;

2.若是该最长的存储空间对其他成员的元类型(如果是数组,取其类型的数据长度,例int a[5]为4)不满足整除关系,该最大空间自动延伸;

我们来看看这段代码:

union mm{

char a;//元长度1

int b[5];//元长度4

double c;//元长度8

int d[3];

};

本来mm的空间应该是sizeof(int)*5=20;但是如果只是20个单元的话,那可以存几个double型(8位)呢?两个半?当然不可以,所以mm的空间延伸为既要大于20,又要满足其他成员所需空间的整数倍,即24

所以union的存储空间先看它的成员中哪个占的空间最大,拿他与其他成员的元长度比较,如果可以整除就行。



九、指定对界

#pragma pack()命令

如何修改编译器的默认对齐值?

1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。

2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

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

使用伪指令#pragma pack (n),编译器将按照n个字节对齐;

使用伪指令#pragma pack (),取消自定义字节对齐方式。

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

为了节省空间,我们可以在编码时通过#pragma pack()命令指定程序的对齐方式,括号中是对齐的字节数,若该命令括号中的内容为空,则为默认对齐方式。例如,对于上面第一个结构体,如果通过该命令手动设置对齐字节数如下:

#pragma pack(2) //设置2字节对齐

struct strdent

{

char name[5]; //本身1字节对齐,比2字节对齐小,按1字节对齐

int num; //本身4字节对齐,比2字节对齐大,按2字节对齐

short score; //本身也2字节对齐,仍然按2字节对齐

}

#pragma pack() // 恢复先前的pack设置,取消设置的字节对齐方式

则,num从第6(偏移量)个字节开始存放,score从第10(偏移量)个字节开始存放,故sizeof(student)=12,其数据对齐如下图:

|char|char|

|char|char|

|char|----|

|----int--|

|----int--|

|--short--|

这样改变默认的字节对齐方式可以更充分地利用存储空间,但是这会降低计算机读写数据的速度,是一种以时间换取空间的方式。



十、代码验证

代码

#include "stdafx.h"

#include <iostream>

using namespace std;

//空

struct S0{ };

struct S1{

char a;

long b;

};

struct S2{

long b;

char a;

};

struct S3 {

char c;

struct S1 d;//结构体

long e;

};

struct S4{

char a;

long b;

static long c; //静态

};

struct S5{

char a;

long b;

char name[5]; //数组

};

//含有一个数组

struct S6{

char a;

long b;

int name[5]; //数组

};

struct student0

{

char name[5];

int num;

short score;

};

struct student1

{

int num;

char name[5];

short score;

};

struct student2

{

int num;

short score;

char name[5];

};

union union1

{

long a;

double b;

char name[9];

};

union union2{

char a;

int b[5];

double c;

int d[3];

};

int main(int argc, char* argv[])

{

cout << "char: " << sizeof(char) << endl; //1

cout << "long: " << sizeof(long) << endl; //4

cout << "int: " << sizeof(int) << endl; //4

cout << "S0: " << sizeof(S0) << endl; //1

cout << "S1: " << sizeof(S1) << endl; //8

cout << "S2: " << sizeof(S2) << endl; //8

cout << "S3: " << sizeof(S3) << endl; //24

cout << "S4: " << sizeof(S4) << endl; //8

cout << "S5: " << sizeof(S5) << endl; //16

cout << "S6: " << sizeof(S6) << endl; //28

cout << "union1 :" << sizeof(union1) << endl;

cout << "union2 :" << sizeof(union2) << endl;

cout << "student0: " << sizeof(student0) << endl;

cout << "student1: " << sizeof(student1) << endl;

cout << "student2: " << sizeof(student2) << endl;

system("pause");

return 0;

}

输出

//这是默认的结果(8字节对齐)

char: 1

long: 4

int: 4

S0: 1

S1: 8

S2: 8

S3: 16

S4: 8

S5: 16

S6: 28

union1 :16

union2 :24

student0: 16

student1: 12

student2: 12

请按任意键继续. . .

//这是16字节对齐的结果,可以看到当设置16字节对齐时,确实没什么效果,里面最大的是double,也就是8字节,#pragma pack (n)中指定的n大于结构体中最大成员size,则其不起作用。

char: 1

long: 4

int: 4

double:8

S0: 1

S1: 8

S2: 8

S3: 16

S4: 8

S5: 16

S6: 28

union1 :16

union2 :24

student0: 16

student1: 12

student2: 12

请按任意键继续. . .

//这是2字节对齐的结果,可以慢慢参考研究

char: 1

long: 4

int: 4

double:8

S0: 1

S1: 6

S2: 6

S3: 12

S4: 6

S5: 12

S6: 26

union1 :10

union2 :20

student0: 12

student1: 12

student2: 12

请按任意键继续. . .



说明:

(1)默认8字节对齐

(2)分析

S0:空

S1:

|char|----|----|----|

|-------long--------|

S2:

|-------long--------|

|char|----|----|----|

S3:

其中包含的S1中最长的为long,S3中也为long,以最长的为分界,那么为:1+8+4 = 13,那么这个结构体的长度就是8的倍数16。

内存是怎么样的现在还没有弄清楚。。。

S4:

静态变量存放在全局数据区内,而sizeof计算栈中分配的空间的大小,故不计算在内,S4的大小为4+4=8。

S5,S6,Student见上面例子。

union1:

最长double=8,但char c[9]用9个不够,再加一倍到16.

union2:

类型最长的是long=8,变量最长的是int b[5] = 4*5=20,20以上8的倍数为24。



十一、还没有解决的问题

虽然知道结构体中含有结构体的长度怎么计算,但不知道它的内存是什么样子的,在VS中用

cout << "&objS3.a: "<< hex << &objS3.a << endl;

为什么显示出来是乱码??

十二、字节对齐可能带来的隐患

(说明:从一个pdf复制,参考一下)

代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:

unsigned int i = 0x12345678;

unsigned char *p=NULL;

unsigned short *p1=NULL;

p=&i;

*p=0x00;

p1=(unsigned short *)(p+1);

*p1=0x0000;

最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。

在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐。

--------------------------------------
转自:http://blog.csdn.net/mannhello/article/details/5384431


#pragma pack(push,1) & #pragma pack(pop)



引子

在程序中,有的时候我们定义结构体的时候,要用#pragma pack(push,1) & #pragma pack(pop)类似代码将结构体包起来。

一般形式如下:

#pragma pack(push,1);

struct A

{



} ;

#pragma pack(pop);

这么做有什么目的呢?

注:下列内容来自网络。

2 #pragma pack简介

#pragma pack是指定数据在内存中的对齐方式,

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

例 1:

struct sample

{

char a;

double b;

};

若不用#pragma pack(1)和#pragma pack()括起来,则sample按编译器默认方式对齐(成员中size最大的那个)。即按8字节(double)对齐,则sizeof(sample)==16.成员char a占了8个字节(其中7个是空字节)

若用#pragma pack(1),则sample按1字节方式对齐sizeof(sample)==9.(无空字节)

例 2:下面的结构各成员空间分配情况:

struct test

{

char x1;

short x2;

float x3;

char x4;

};

结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。更改C编译器的缺省字节对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

  · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。

· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

另外,还有如下的一种方式:

· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。

3 应用实例

  在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:





#pragma pack(1) //
 按照1字节方式进行对齐

struct TCPHEADER 

{

     short SrcPort; // 16位源端口号

     short DstPort; // 16位目的端口号

     int SerialNo; // 32位序列号

     int AckNo; // 32位确认号

     unsigned char HaderLen : 4; // 4位首部长度

     unsigned char Reserved1 : 4; // 保留6位中的4位

     unsigned char Reserved2 : 2; // 保留6位中的2位

     unsigned char URG : 1;

     unsigned char ACK : 1;

     unsigned char PSH : 1;

     unsigned char RST : 1;

     unsigned char SYN : 1;

     unsigned char FIN : 1;

     short WindowSize; // 16位窗口大小

     short TcpChkSum; // 16位TCP检验和

     short UrgentPointer; // 16位紧急指针

}; 

#pragma pack()

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: