struct内存对齐:gcc与VC的差别

内存对齐是编译器为了便于CPU快速访问而采用的一项技术,对于不同的编译器有不同的处理方法。

Win32平台下的微软VC编译器在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则:任何2字节大小的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。这里说明一下,如果全部是char 那对齐就是1个字节。如果char  和short char放一块那就是 2个字节对齐。当然具体要看下面

char可以是任意地址处,short必须是2的倍数地址处,int必须是4倍数地址处

下面的程序可以验证:


#include <stdio.h>
#define OFFSET(TYPE,MEMBER)  ((int)(&(((TYPE*)0)->MEMBER)))
typedef struct
{
    int a;
    float b;
    char c;
    double d;
    int *pa;
    char *pc;
}Sta;
int main()
{
    printf("a_=%d\n",OFFSET(Sta,a));
    printf("b_=%d\n",OFFSET(Sta,b));
    printf("c_=%d\n",OFFSET(Sta,c));
    printf("d_=%d\n",OFFSET(Sta,d));
    printf("pa_=%d\n",OFFSET(Sta,pa));
    printf("pc_=%d\n",OFFSET(Sta,pc));
    return 0;
}

在VC上的结果是:

而在linux下的结果却是:

主要是对于double类型,VC采用的是8对齐,而gcc采用的是4对齐


(1)什么是字节对齐

一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即: 存放起始地 址 % n = 0,对于结构体而言, 这个 n 取其成员中的数据类型占空间的值最大的那个。

(2)为什么要字节对齐

内存空间是按照字节来划分的,从理论上说对内存空间的访问可以从任何地址开始,但是在实际上不同架构的 CPU 为了提高访问内存的速度,就规定了对于某些类型的数据只能从特定的起始位置开始访问。这样就决定了各种数据类型只能按照相应的规则在内存空间中存放,而不能一个接一个的顺序排列。

举个例子,比如有些平台访问内存地址都从偶数地址开始,对于一个 int 型(假设 32 位系统),如果从偶数地址开始的地方存放,这样一个读周期就可以读出这个 int 数据,但是如果从奇数地址开始的地址存放,就需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到这个 int 数据,这样明显降低了读取的效率。

(3)如何进行字节对齐

每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(不指定则取默认值)中较小的一个对齐,并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

这个规则有点苦涩,可以把这个规则分解一下,前半句的意思先获得对齐值后与指定对齐值进行比较,其中对齐值获得方式如下:

1. 数据类型的自身对齐值为:对于 char 型数据,其自身对齐值为 1,对于 short 型 为 2,对于 int, long,float 类型,其自身对齐值为 4,对于 double 类型其自身对齐值为 8,单位为字节。

2.结构体自身对齐值:其成员中自身对齐值最大的那个值。

其中指定对齐值获得方式如下:

#pragma pack (value)时的指定对齐值 value。

未指定则取默认值。

后半句的意思是主要是针对于结构体的长度而言,因为针对数据类型的成员,它仅有一个对齐参数,其本身的长度、于这个对齐参数,即 1 倍。对于结构体而言,它可能使用 了多种数据类型,那么这句话翻译成对齐规则:每个成员的起始地址 % 自身对齐值 = 0,如果不等于 0 则先补空字节直至这个表达式成立。

换句话说,对于结构体而言,结构体在内存的存放顺序用如下规则即可映射出来:

(一)每个成员的起始地址 % 每个成员的自身对齐值(注意这里不是结构体的自身对齐值) = 0, 如果不等于 0 则先补空 字节直至这个表达式成立;

(二)结构体的长度必须为结构体的自身对齐值的整数倍,不够就补空字节。

不同平台下对齐系数:

每个特定平台的编译器都有一个默认的对齐系数,gcc中是4,VC中是8。也可以通过于编译命令#pragmapack(n)来指定该系数,经测试gcc中n的值只能是1,2和4。

举个例子:

#pragmapack(8)

structA{

char a;

long b;

};

structB{

char a;

structA b;

long c;

};

structC{

char a;

structA b;

double c;

};

structD{

char a;

structA b;

double c;

int d;

};

structE{

char a;

int b;

structA c;

double d;

};

在VC中:

对于 struct A 来说,

对于 char 型数据,其自身对齐值为 1,对于 long 类型,其自 身对齐值为 4, 结构体的自身对齐值取其成员最大的对齐值,即大小 4。那么 struct A 在内存中的顺序步骤为:

(1) char a, 地址范围为 0x0000~0x0000,起始地址为 0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

(2) long b, 地址起始位置不能从 0x00001 开始, 因为 0x0001 % 4 != 0, 所 以先补空字节,直到 0x00003 结束,即补 3 个字节的空字节,从0x00004 开始存放 b, 其地址范围为 0x00004~0x0007.

(3)此时成员都存放结束,结构体长度为 8,为结构体自身对齐值的 2 倍,符合条件 (二).

此时满足条件(一)和条件(二),struct A 中各成员在内存中的位置为:a*** b ,si zeof(structA) = 8。 (每个星号代表一位, 成员各自代表自己所占的位,比如 a 占一位, b 占四位)

对于 struct B,

里面有个类型为 structA 的成员 b 自身对齐值为 4,对于 long 类 型,其自身对齐值为 4. 故 struct B 的自身对齐值为 4。那么 structB 在内存中的顺序步骤为:

(1) char a, 地址范围为 0x0000~0x0000,起始地址为 0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

(2) struct A b, 地址起始位置不能从 0x00001 开始, 因为 0x0001 % 4 != 0, 所以先补空字节,直到 0x00003 结束,即补 3 个字节的空字节,从0x00004 开始存 放 b,其地址范围为0x00004~0x00011.

(3) long c,地址起始位置从 0x000012 开始, 因为 0x0012 % 4 = 0,其地 址范围为 0x00012~0x0015.

(4)此时成员都存放结束,结构体长度为 16,为结构体自身对齐值的 4 倍,符合条件 (二).

此时满足条件(一)和条件(二),struct B 中各成员在内存中的位置为:a*** b c ,sizeof(struct B) = 16。(每个星号代表一位,成员各自代表自己所占的位,比如 a 占 一位,b 占八位,c 占四位)

对于 struct C,

里面有个类型为 structA 的成员 b 自身对齐值为 4,对于 double 类型,其自身对齐值为 8. 故 struct C 的自身对齐值为 8。那么 struct C 在内存中的顺 序步骤为:

(1) char a, 地址范围为 0x0000~0x0000,起始地址为 0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

(2) struct A b, 地址起始位置不能从 0x00001 开始, 因为 0x0001 % 4 != 0, 所以先补空字节,直到 0x00003 结束,即补 3 个字节的空字节,从 0x00004开始存放 b,其地址范围为0x00004~0x00011.

(3) double c,地址起始位置不能从 0x000012 开始, 因为 0x0012 % 8 != 0,所以先补空字节,直到 0x000015 结束,即补 4 个字节的空字节,从 0x00016开始存放 c,其地址范围为0x00016~0x0023.

(4)此时成员都存放结束,结构体长度为 24,为结构体自身对齐值的 3 倍,符合条件 (二).

此时满足条件(一)和条件(二),struct C 中各成员在内存中的位置为:a*** b ** ** c,sizeof(struct C) = 24。(每个星号代表一位,成员各自代表自己所占的位,比 如 a 占一位,b 占八位,c 占八位)

对于 struct D,

自身对齐值为 8。前面三个成员与 struct C 是一致的。对于第四 成员 d,因为 0x0024 % 4= 0, 所以可以从 0x0024 开始存放 d, 其地址范围为 0x 00024~0x00027.此时成员都存放结束,结构体长度为 28,28 不是结构体自身对齐值8 的倍数,所以要在后面补四个空格,即在 0x0028~0x0031 上补四个空格。补完了, 结构体长度为 32, 为结构体自身对齐值的 4 倍,符合条件(二).

此时满足条件(一)和条件(二),struct D 中各成员在内存中的位置为:a*** b ** ** c d ****,sizeof(struct D) = 32。(每个星号代表一位,成员各自代表自己所占 的位,比如 a 占一位,b 占八位,c 占八位, d 占四位)。

对于 struct E,

各成员在内存中的位置为:a***b c d, sizeof(struct E) = 2 4。(每个星号代表一位,成员各自代表自己所占的位,比如 a 占一位,b 占四位,c 占八位, d 占八位)。

通过 struct D 和 struct E 可以看出,在成员数量和类型一致的情况,后者的所占 空间少于前者,因为后者的填充空字节要少。如果我们在编程时考虑节约空间的话,应该遵循将变量按照类型大小从小到大声明的原则, 这样尽量减少填补空间。另外,可以在填充空字节的地方来插入reserved 成员, 例如

struct A { char a; char reserved[3]; int b;};

这样做的目的主要是为了对程序员起一个提示作用,如果不加则编译器会自动补齐。

在gcc中

由于对齐系数最大只能为4,所以上述结构体占内存大小为:8,16,20,24,24。

验证:

1、默认情况(n=4)

struct st1 {
char ch;//长度1<n,按1对齐,0%1=0,起始相对位置=0;存放区间[0]
int num;//长度4=n,按4对齐, 4%4=0,起始相对位置=4;存放区间[4,7]
long lv;//长度4=n,按4对齐,8%4=0,起始相对位置=8;存放区间[8,11]
};

整个结构体成员对齐后所占的区间为[0,8],占12个字节,接着结构体本身对齐,成员中最长的是4,n也等于4,所以结构体本身按4对齐(即对齐系数)。
整个结构体的大小 = 比整个结构体数据成员所占的总空间大或相等且和对齐系数求模结果为0、与之距离最近的数。
本例中,12%4=0,所以结构体st1占12个字节的空间。

2、#pragmapack(1)(即n=1)

struct st1 {
char ch;//长度1=n,按1对齐,0%1=0,起始相对位置=0;存放区间[0]
int num;//长度4>n,按n对齐, 1%1=0,起始相对位置=0;存放区间[1,4]
long lv;//长度4>n,按n对齐,5%1=0,起始相对位置=5;存放区间[5,8]
};

整个结构体成员对齐后所占的区间为[0,8],占9个字节,接着结构体本身对齐,成员中最长的是4,n等于1,所以结构体本身按1对齐(即对齐系数)。
整个结构体的大小 = 比整个结构体数据成员所占的总空间大或相等且和对齐系数求模结果为0、与之距离最近的数。
本例中,9%1=0,所以结构体st1占9个字节的空间。

3、#pragmapack(2)(即n=2)

struct st1 {
char ch;//长度1<n,按1对齐,0%1=0,起始相对位置=0;存放区间[0]
int num;//长度4>n,按n对齐, 2%2=0,起始相对位置=2;存放区间[2,5]
long lv;//长度4>n,按n对齐,6%2=0,起始相对位置=6;存放区间[5,8]
};

整个结构体成员对齐后所占的区间为[0,8],占9个字节,接着结构体本身对齐,成员中最长的是4,n等于2,所以结构体本身按2对齐(即对齐系数)。
整个结构体的大小 = 比整个结构体数据成员所占的总空间大或相等且和对齐系数求模结果为0、与之距离最近的数。
本例中,10%2=0,所以结构体st1占10个字节的空间。

为什么说#pragmapack(n)中n只能是1,2,4呢?
比如3,如果n=3,在编译的时候会警告“对齐边界必须是 2 的较小次方,而不是 3”,也就是说是不起作用的,按默认对齐系数对齐。
再如8,会有什么结果?看下一例:

4、#pragmapack(8)(即n=8)

struct siz {
char v1;
long long v2;
short v3;
int v4;
};

如果8起作用,分析一下:
struct siz {
char v1;//长度1<n,按1对齐,0%1=0,起始相对位置=0;存放区间[0]
long long v2;//长度8=n,按8对齐,8%8=0,起始相对位置=8;存放区间[8,15]
short v3;//长度2<n,按2对齐,16%2=0,起始相对位置=16;存放区间[16,17]
int v4;//长度4<n,按4对齐,20%4=0,起始相对位置=20;存放区间[20,23]
};

整个结构体成员对齐后所占的区间为[0,23],占24个字节,接着结构体本身对齐,成员中最长的是8,n等于8,所以结构体本身按8对齐(即对齐系数)。24%8=0,所以占24个字节。
然而,很不幸,运行的结果是20.
接下来,用默认的对齐系数4来分析一下:
struct siz {
char v1;//长度1<4,按1对齐,0%1=0,起始相对位置=0;存放区间[0]
long long v2;//长度8>4,按4对齐,4%4=0,起始相对位置=4;存放区间[4,11]
short v3;//长度2<4,按2对齐,12%2=0,起始相对位置=12;存放区间[12,13]
int v4;//长度4=4,按4对齐,16%4=0,起始相对位置=16;存放区间[16,19]
};
整个结构体成员对齐后所占的区间为[0,19],占20个字节,接着结构体本身对齐,成员中最长的是8,n等于4,所以结构体本身按4对齐(即对齐系数)。20%4=0,所以占20个字节。与运行结果一致。

综上分析,当n=8的时候gcc仍然使用的是默认的对齐系数4.

位域

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:

struct 位域结构名

{ 位域列表 };

其中位域列表的形式为: 类型说明符 位域名:位域长度

例如:

struct bs
{
int a:8;
int b:2;
int c:6;
};

如果结构体中含有位域(bit-field),那么VC中准则又要有所更改:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

struct s1
  {
    int i:8;
    char j:4;
    int a:20;
    double b;
  };

struct s2
  {
    int i:8;
    char j:4;
    int a:21;
    double b;
  };

sizeof(struct s1), VC中为24,gcc中为 12

sizeof(struct s2), VC中为24,gcc中为 16

4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 
备注:
结构体
typedef struct
{
char c:2;
double i;
int c2:4;
}N3;
在GCC下占据的空间为16字节,在VC下占据的空间是24个字节。

5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

ps:

  • 对齐模数的选择只能是根据基本数据类型,所以对于结构体中嵌套结构体,只能考虑其拆分的基本数据类型。而对于对齐准则中的第2条,确是要将整个结构体看成是一个成员,成员大小按照该结构体根据对齐准则判断所得的大小。
  • 类对象在内存中存放的方式和结构体类似,这里就不再说明。需要指出的是,类对象的大小只是包括类中非静态成员变量所占的空间,如果有虚函数,那么再另外增加一个指针所占的空间即可。

struct内存对齐1:gcc与VC的差别的更多相关文章

  1. 利用php unpack读取c struct的二进制数据,struct内存对齐引起的一些问题

    c语言代码 #include <stdio.h> struct test{ int a; unsigned char b; int c; }; int main(){ FILE *fp; ...

  2. struct内存对齐

    内存对齐其实是为了在程序运行的时候更快的查找内存而做的一种编译器优化. 我们先看这样一个例子: #include <iostream> using namespace std; struc ...

  3. 什么是内存对齐,go中内存对齐分析

    内存对齐 什么是内存对齐 为什么需要内存对齐 减少次数 保障原子性 对齐系数 对齐规则 总结 参考 内存对齐 什么是内存对齐 弄明白什么是内存对齐的时候,先来看一个demo type s struct ...

  4. 内存对齐-C语言struct内存占用问题

    转1个写的比较全面的. http://hubingforever.blog.163.com/blog/static/17104057920122256134681/ 本文编辑整理自:http://hi ...

  5. VC++中内存对齐

    我们经常看到求 sizeof(A) 的值的问题,其中A是一个结构体,类,或者联合体. 为了优化CPU访问和优化内存,减少内存碎片,编译器对内存对齐制定了一些规则.但是,不同的编译器可能有不同的实现,本 ...

  6. VC++平台上的内存对齐操作

    我们知道当内存的边界正好对齐在相应机器字长边界上时,CPU的执行效率最高,为了保证效率,在VC++平台上内存对齐都是默认打开的,在32位机器上内存对齐的边界为4字节:比如看如下的代码: struct ...

  7. 内存对齐与ANSI C中struct型数据的内存布局 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3032209.html 当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将 ...

  8. 内存对齐与ANSI C中struct型数据的内存布局

    当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不 ...

  9. 浅析内存对齐与ANSI C中struct型数据的内存布局-内存对齐规则

    这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密. 首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的 ...

随机推荐

  1. apache本地域名ip重定向vhosts

    apache本地域名ip重定向,使本机通过指定域名访问到指定ip路径. 1.apache配置apache/conf/httpd.conf  : 开启配置 Include conf/extra/http ...

  2. Python一般错误

    1. IndentationError: unindent does not match any outer indentation level 格式对齐的问题.Python对空格和Tab有严格区别

  3. win2003远程桌面端口修改

    win2003远程桌面端口修改   1.改端口:简单操作步骤:打开"开始→运行",输入"regedit",打开注册表,进入以下路径:[HKEY_LOCAL_MA ...

  4. 用refresh控制浏览器定时刷新

    package cn.itcast.response; import java.io.IOException; import java.util.Random; import javax.servle ...

  5. 【iCore3 双核心板_FPGA】例程十一:乘法器实验——乘法器使用

    实验指导书及代码包下载: http://pan.baidu.com/s/1dEijBs1 iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  6. EntityFramework 实体拆分与表拆分

    摘录自https://msdn.microsoft.com/zh-cn/data/jj591617 * 将实体类型的 CLR 属性映射到数据库中的多个表(实体拆分) 实体拆分允许一个实体类型的属性分散 ...

  7. 【译】在ASP.Net和IIS中删除不必要的HTTP响应头

    引入 每次当浏览器向Web服务器发起一个请求的时,都会伴随着一些HTTP头的发送.而这些HTTP头是用于给Web服务器提供一些额外信息以便于处理请求.比如说吧.如果浏览器支持压缩功能,则浏览器会发送A ...

  8. 贴片三极管-MOS管型号手册

    详细请查阅PDF: http://files.cnblogs.com/files/BinB-W/贴片三极管-MOS管型号手册.pdf

  9. JS之获取样式

    基本代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  10. 表单验证插件----jquery validation

    1.下载地址:http://jqueryvalidation.org/ 2.使用方法: <script type="text/javascript" src="ht ...