C/C++内存对齐 ZZ

这篇文章写得非常深入浅出。推荐。图需要到原博看。

http://songlee24.github.io/2014/09/20/memory-alignment/

下面是网易的一道笔试题:struct { uint32_t m1; char m2; } varray[2];以下哪些判断一定成立?(多选)

  1. sizeof(varray[0]) == 5
  2. sizeof(varray[0]) == 8
  3. (void*)&(varray[0].m1) < (void*)&(varray[0].m2)
  4. (char*)&varray[0] == (char*)&(varray[0].m1)
  5. (char*)&varray[0] + sizeof(varray[0]) == (char*)&varray[1]
  6. (char*)&(varray[0].m2) + 1 == (char*)&varray[1]
  7. (char*)&(varray[0].m2) + 4 == (char*)&varray[1]

这个题目考查的就是内存对齐的知识点,看完这篇文章你就知道这道题应该选什么了。

一、什么是内存对齐

内存对齐(Memory alignment),也叫字节对齐。

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

举一个简单的例子,uint32_t所占内存空间为 4 bytes,char为 1 byte。如果把它们放在一个结构体中,则所占的内存空间应该是 4 + 1 = 5 bytes 。而事实上,在VS2012和gcc环境下,sizeof 操作的结果都是 8 bytes:

12345678910111213
struct{    uint32_t m1;    // #include<stdint.h>    char m2;}varray;

int main(){    printf("%d\n",sizeof(varray.m1));  // 输出4    printf("%d\n",sizeof(varray.m2));  // 输出1    printf("%d\n",sizeof(varray));     // 输出8    return 0;}

示图:

这里是以4个字节为一个对齐单位。

二、为什么要内存对齐

之所以要内存对齐,有两方面的原因:

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

三、对齐的规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。你可以通过预编译命令#pragma pack(n),n=1,2,4,8,16 来改变这一系数,其中 n 就是你要指定的“对齐系数”。

1)概念:

有效对齐值:是 #pragma pack指定值 和 结构体中最长数据类型长度 中较小的那个。有效对齐值也叫对齐单位

注意:VS、VC 默认是#pragma pack(8),而 gcc 默认是#pragma pack(4),并且gcc只支持1,2,4对齐。

2)规则:

  1. 结构体变量的首地址是有效对齐值(对齐单位)的整数倍。
  2. 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
  3. 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
  4. 结构体内类型相同的连续元素将在连续的空间内,和数组一样。

下面给出几个例子帮助理解(测试环境为VS2012):

例一:

12345678910111213141516171819202122232425262728
struct{    int i;    // 4个字节    char c1;  // 1个字节    char c2;  // 1个字节}x1;

struct{    char c1;  // 1个字节    int i;    // 4个字节    char c2;  // 1个字节}x2;

struct{    char c1;  // 1个字节    char c2;  // 1个字节    int i;    // 4个字节}x3;

int main(){    printf("%d\n",sizeof(x1));  // 输出8    printf("%d\n",sizeof(x2));  // 输出12    printf("%d\n",sizeof(x3));  // 输出8    return 0;}

可以看出,上面定义的三个结构体只是交换了成员声明的顺序。由于结构体中最长的数据类型为4个字节,而VS2010默认#pragma pack(8),所以有效对齐值(对齐单位)为 4 bytes。根据前三条规则可以画出以下图:

例二:

123456789101112
struct{    int a;      // 4个字节    char b[6];  // 6个字节    double c;   // 8个字节}st;

int main(){    printf("%d\n",sizeof(st));  // 输出24    return 0;}

上面结构体中最长的数据类型 double 为 8 个字节,而VS2012中默认#pragma pack(8),所以有效对齐值(对齐单位)为 8 bytes。根据前三条规则可以画出以下图:

其中的字符数组 char b[6] 可以看做6个单独的 char 成员。

四、pragma pack(n)

  • 上面说到,不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n),n=1,2,4,8,16 来改变这一对齐系数。
  • #pragma pack(n)是通过改变有效对齐值来改变数据成员在内存中的布局,若你设定的 n 值没有影响或改变有效对齐值,则成员在内存中的布局不会改变。

下面就看看在1、2、4字节对齐的情况下例一、例二的变化:

1字节对齐:#pragma pack(1)

这时的有效对齐值(对齐单位)为 1 字节,则根据对齐规则,可知成员都是连续存储的。

例一中的输出结果会变为 6,6,6,如下图:

例二中输出结果则变为 4 + 6 + 8 = 18:

2字节对齐:#pragma pack(2)

这时的有效对齐值(对齐单位)为 2 字节,则根据对齐规则,可知例一的输出结果会变为 6,8,6,如下图:

例二结构体中最长数据类型 double 为 8 个字节,所以有效对齐值是 2 。此时输出结果还是18,如下图:

4字节对齐:#pragma pack(4)

对于例一,结构体中最长的数据类型 int 是 4 个字节,所以此时的有效对齐值(对齐单位)仍为 4,没有变化,所以输出仍然是 8,12,8。

而在例二中,原来的有效对齐值为 8,现在变成了 4 。所以输出结果变为 20,具体如下图:

对于 8 字节对齐、 16 字节对齐,在这里就不举例了,相信根据对齐规则你可以很容易写出来。需要注意的是,有些编译器,比如gcc,只支持 1,2,4 对齐。

附:答案

相信看到这里,文章开头的那个网易笔试题应该就很容易得出答案了。只需要根据内存对齐把结构体的内存布局图画出来就一目了然了:

所以多选答案应该是 2、4、5、7。

对于这种类型的题目,最好的办法就是根据对齐规则画出对齐后的内存布局图,简单清晰且不容易出错。

时间: 2024-10-06 05:02:25

C/C++内存对齐 ZZ的相关文章

关于内存对齐的那些事

Wrote by mutouyun. (http://darkc.at/about-data-structure-alignment/) 1. 内存对齐(Data Structure Alignment)是什么 内存对齐,或者说字节对齐,是一个数据类型所能存放的内存地址的属性(Alignment is a property of a memory address). 这个属性是一个无符号整数,并且这个整数必须是2的N次方(1.2.4.8.--.1024.--). 当我们说,一个数据类型的内存对齐

内存对齐与自定义类型

一.内存对齐 (一).为什么会有内存对齐? 1.为了提高程序的性能,数据结构(尤其是栈)应该尽可能的在自然边界上对齐.原因是为了访问未对齐的内存,处理器需要进行两次访问,而访问对齐的内存,只需要一次就够了.这种方式称作"以空间换时间"在很多对时间复杂度有要求问题中,会采用这种方法. 2.内存对齐能够增加程序的可移植性,因为不是所有的平台都能随意的访问内存,有些平台只能在特定的地址处处读取内存. 一般情况下内存对齐是编译器的事情,我们不需要考虑,但有些问题还是需要考虑的,毕竟c/c++是

内存对齐,大端字节 &nbsp; 序小端字节序验证

空结构体:对于空结构体,就是只有结构体这个模子,但里面却没有元素的结构体. 例: typedef struct student { }std: 这种空结构体的模子占一个字节,sizeof(std)=1. 柔性数组: 结构体中最后一个元素可以是一个大小未知的数组,称作柔性数组成员,规定柔性数组前面至少有一个元素. typedef struct student { int i; char arr[];     //柔性数组成员 }std: sizeof(std)=4; sizeof求取该结构体大小是

20160402_C++中的内存对齐

原题: 有一个如下的结构体: struct A{  long a1;  short a2;  int a3;  int *a4; }; 请问在64位编译器下用sizeof(struct A)计算出的大小是多少? 答案:24 -------------------------------------------------------------------------------- 本题知识点:C/C++ 预备知识:基本类型占用字节 在32位操作系统和64位操作系统上,基本数据类型分别占多少字节

内存对齐

有虚函数的话就有虚表,虚表保存虚函数地址,一个地址占用的长度根据编译器不同有可能不同,vs里面是8个字节,在devc++里面是4个字节.类和结构体的对齐方式相同,有两条规则1.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行.2.结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照

内存对齐和大小端

一.内存对齐的原因 根本原因:cpu是根据内存访问粒度(memory access granularity,下文简写成MAG)来读取内存,MAG就是cpu一次内存访问操作的数据量,具体数值依赖于特定的平台,一般是2byte.4byte.8byte. 内存对齐:更够减少内存读取次数(相对于内存不对齐),为了访问未对齐的内存,处理器需要作两次内存访问:而对齐的内存访问仅需要一次访问. 二.内存对齐的步骤 每个平台上的编译器都有自己的默认“对齐系数”.同时,我们也可以通过预编译命令#pragma pa

c++编程思想(三)--c++中c 续,重点sizeof和内存对齐

之前理论性的太多,下面就是代码及理论结合了 1.sizeof()是一个独立运算符,并不是函数,可以让我们知道任何变量字节数,可以顺带学一下struct,union,内存对齐 内存对齐:为了机器指令快速指向地址值,编译器内部实际上会内存对齐,怎么理解了,以struct为例 先讲一下各个变量类型内存大小 所以struct理论上是:1+2+4+4+4+8+8 = 31,但是实际是 实际大小是32(1+2+1+4)+(4+4)+8+8 然后再把int和short位置调换 实际大小是40   (1+3)+

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

struct内存对齐:gcc与VC的差别 内存对齐是编译器为了便于CPU快速访问而采用的一项技术,对于不同的编译器有不同的处理方法. Win32平台下的微软VC编译器在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T).比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始.Linux下的GCC奉行的是另外一套规则:任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模

c++中类对象的内存对齐

很多C++书籍中都介绍过,一个Class对象需要占用多大的内存空间.最权威的结论是: *非静态成员变量总合.(not static) *加上编译器为了CPU计算,作出的数据对齐处理.(c语言中面试中经常会碰到内存对齐的问题) *加上为了支持虚函数(virtual function),产生的额外负担. 下面给出几个程序来看一下: #include <iostream> #include <cstdio> #include <string> using namespace