C/C++中的内存对齐 C/C++中的内存对齐

一、什么是内存对齐、为什么需要内存对齐?

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

字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。

用术语来讲就是,基本C类型在X86和ARM上都是自对齐的(self-aligned)。指针,不管是32位(4字节)还是64位(8字节)也是自对齐的。
自对齐能存取得更快是因为它能用一条指令来存取该类型数据。 另一方面,如果没有对齐限制,代码可能会在跨机器字边界存取的时候使用两条以上的指令。字符是特殊情况: 不管它在们在机器字的哪个位置,存取代价都是一样的。所以它们没有对齐要求。

二、对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
(1)数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
(2)结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
(3)当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

三、填充(padding)

现在我们来看一个简单的例子,变量在内存中的分布。


1
2
3

char   *p; 
char   c; 
int   a;

如果你不知道数据对齐,你可能会假定这三个变量在内存里占用连续的字节。 即,在32位机器上4字节的指针后面会紧跟1字节的char,而它后面会紧跟4字节的int。在64位机器上,唯一的差别是指针是8字节的。

这是(在x86或ARM或任何自对齐的机器上)实际的情况:p 存储在4字节或8字节对齐的位置上(由机器的字长决定)。这是指针对齐-可能的最严格的情况。

c的存储紧跟着p。但a的4字节对齐要求造成一个缺口,就好像有第四个变量插入其中:


1
2
3
4

char   *p;    //4  或 8字节 
char   c;      //1 字节 
charpad[3];      //3 字节 
int   a;         //4 字节

charpad[3];         表示有3个字节浪费了。

如果a是2字节的short的话,这种情况内存分布是这样的:


1
2
3
4

char   *p;    //4  或 8字节 
char   c;      //1 字节 
charpad[1];      //1 字节 
short   a;         //2 字节

如果你想让这些变量占用较少的空间,你可以交换a和c的位置:


1
2
3

char   *p;    //4  或 8字节 
int   a;         //4 字节 
char   c;      //1 字节

四、结构体的对齐和填充

上面说到结构体实际会和它的最宽成员一样对齐,编译这样做因为这是保证所有成员自对齐获取快速存取的最容易方法。

看看这个结构:


1
2
3
4
5

struct user{
    char    *name;
    char      c;
    int         age;
};

假设是在32位机器上,内存分布是这样的:


1
2
3
4
5
6

struct user{
    char    *name;  //4  字节
    char      c;  //1 字节
    charpad[3];    //3 字节
    int         age;    //4 字节
};

这样的话sizeof(user)  为 12字节

那么如果我们交换c和age的位置。


1
2
3
4
5

struct user{
    char    *name;  //4  字节
    int         age;    //4 字节
    char      c;  //1 字节
};

你可能会认为sizeof(user)为9,但是这样 sizeof(user)  还是为 12字节。

因为struct是根据最宽的成员对齐,所以最后还是有3个字节填充但未使用。

struct  user  uu[4];

这样,在uu数组里,每个成员都有3字节的拖尾填充,因为下一个结构体的第一个成员需要在4字节边界上对齐。

现在让我们考虑位域(bitfields)。它们使得你能声明比字节宽度更小的成员,低至1位。


1
2
3
4
5
6
7

struct    st{ 
    short  s; 
    char  c; 
    int  flip:1; 
    int  nybble:4; 
    int  septet:7; 
};

从编译器的角度来看,struct   st里的位域就像2字节,16位的字符数组,只用到了12位。为了使结构体的长度是它的最宽成员长度(即sizeof(short))的整数倍,还有一个字节的填充:


1
2
3
4
5
6
7
8
9

struct    st{ 
    short  s;    //2个字节 
    char  c;     //1个字节 
    int  flip:1;    //总 1 bit 
    int  nybble:4;   //总  5  bits 
    int  septet:7;   //总  12 bits 
    int  pad:4;  //总16 bits 
    charpad;       //1个字节 
};

如果你的结构体中含有结构体,里面的结构体也要和最长的标量有相同的对齐。

五、结构成员重排

来看看32位系统下的这两种情况:


1
2
3
4
5
6
7
8
9
10
11
12
13

struct user{
    char  a;    //1字节 
    charpad[3];   //3字节
    int  c;      //4字节
    char  b;  //1字节
    charpad[3];   //3字节
};
struct user{
    int  c;      //4字节
    char  a;    //1字节 
    char  b;  //1字节
    charpad[2];   //2字节
};

上面结构成员都是一样,只是顺序不一样,但是大小前面一个是12字节,后面一个是8字节。

首先我们注意到溢出只发生在两个地方。 一个是较大的数据类型(从而需要更严格的对齐)跟在较小的数据后面。另一个是结构体自然结束的位置到跨步地址之间需要填充,以使下一个相同结构能正确地对齐。最简单的消除溢出的方式是按对齐值的递减来排序成员。


1
2
3
4
5

union    u{
    char a;
    int b;
    long double c;
};   //大小为8字节  32位

struct/class/union内存对齐原则都是一样的。

本文链接:http://www.blogfshare.com/memory-alignment.html

时间: 2024-08-08 00:58:22

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

C++中怎么求类的大小?以及内存对齐原理(面试官经常问到的问题)

<pre name="code" class="cpp">#include<iostream> using namespace std; int main(){ class S{}; cout<<sizeof(S); return 0; } 程序居然输出为1!到底是怎么回事?首先我在这里要声明一点--类在未初始化之前确实不会分配空间,这里探讨的是sizeof(类)的问题,详细情况看下面的例子 C++中的类所占内存空间总结 类所占内

结构体大小的计算以及内存对其原则和修改默认对齐数

1.结构体大小的计算 **我们都知道,不论是数组还是指针都可以计算其大小, 而同样结构体也是可以计算大小的, 接下来我们就深入讨论如何计算结构体的大小.** #include<stdio.h> #includ<stdlib.h> struct s1 { char c1; int a; char c2; }; int main() { printf("%d\n", sizeof(struct s1)); system("pause"); ret

解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题

解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题 iOS开发 · 2015-01-22 11:31 MWPhotoBrowser是一个非常不错的照片浏览器,在github的star接近3000个,地址:https://github.com/mwaterfall/MWPhotoBrowser.git MWPhotoBrowser来加载小图1M以下的都应该不会有内存警告的问题.如果遇到大图,3M.4M.5M的大图,很有可能导致内存警告.最近我就遇到这个问题,很是头疼

java类中属性的加载顺序,以及内存分配情况介绍

看下面例子及说明: /** 假如有外部类调用了该类,代码为:new StaticTest(); 那么下面是类属性的加载顺序 */ public class StaticTest{ public int dsd=2;//第3 //多个对象会有多次分配内存 public awds() { int sdsfsd=2;//第4 //多个对象会有多次分配内存 } //静态代码块 static{ System.out.println("静态代码块正在加载...");//第1 } public st

Android学习笔记_78_ Android开发中使用软引用和弱引用防止内存溢出

在<Effective Java 2nd Edition>中,第6条"消除过期的对象引用"提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象池.缓存中的过期对象都有可能引发内存泄露的问题.书中还提到可以用 WeakHashMap来作为缓存的容器可以有效解决这一问题.之前也确实遇到过类似问题,但是没有接触过"弱引用"相关的问题,于是查阅了一些资料. <Java 理论与实践: 用弱引用堵住内存泄漏>

android内存优化大全_中

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). OOM: 内存泄露可以引发很多的问题:

Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)

1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题.(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间. 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2.什么叫java的内存泄露 在

.NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长

一个 asp.net core 站点,之前运行在Linux 服务器上,运行一段时间后有时站点会挂掉,在日志中记录很多“EMFILE too many open files”的错误: Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException: Error -24 EMFILE too many open files 后来将这个 asp.net 站点部署到 Windows 服务器的 IIS 上.运行一段时间后,发现其中一台

c++中的内存空间不足和自定义处理内存不足

new操作符动态分配内存时,首先它会调用对象的operator new()函数分配相应大的内存(如果对象类没有重载operator new()函数,则默认调用<new>头文件里的operator new()函数分配内存):接着调用对象的构造函数,初始化这块内存:最后返回指向该内存块的指针. 当分配内存时,如果内存空间不够用,则分配函数会默认抛出bad_allco类型异常,供用户接收并处理.另外,我们还能自己定义如何处理空间不足,用一个函数指明该如何处理内存不足.下面这个例子中,必然会导致内存不