c/c++中内存对齐详解

一,什么是内存对齐?内存对齐用来做什么?

所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。

比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存的读取操作是对齐的,如果x的地址不是4的倍数,那么读取这个x,需要读取两次共8个字节,然后还要将其拼接成一个int,这比存取对齐过的x要麻烦很多。

二,怎么算内存对齐大小(理论)?

对于简单类型,如int,char,float等,其对齐大小为其本身大小,即align(int) == sizeof(int),align(char)==sizeof(char),等等。

对于复合类型,如struct,class,其本身并无所谓对齐,因为CPU没有直接存取一个struct的指令。对于struct而言,它的对齐指的是它里面的所有成员变量都是对齐的,class同理。

下面就讲讲struct对齐是怎么回事。

首先要明白三个点:

1,内存对齐是指首地址对齐,而不是说每个变量大小对齐;

2,结构体内存对齐要求结构体内每一个成员变量都是内存对齐的;

3,结构体对齐除了第2点之外还要求结构体数组也必须是对齐的,也就是说每个相邻的结构体内部都是对齐的。

OK,先知道上面这3点之后,开始接触怎么算对齐大小。

程序员可自己指定某些数据的对齐大小,通过使用下面的预处理指令,指定对齐大小为x。(这里需要注意:只能指定2的n次方作为对齐大小,对于指定对齐大小为6,9,10这样的编译器可能会不予理会)

#pragma pack(x)
//...
#pragma pack()

那到现在,可能大家有个疑问了,那对于int(这里假设sizeof(int)==4),手动指定对齐大小为8,那align(int)是等于sizeof(int)还是等于8呢 ?

这里大家可以记住,align(x) = min ( sizeof(x) , packalign) , 即sizeof(x)和指定对齐大小哪个小,对齐大小就为哪个。

因此,上面的疑问答案是align(int)=sizeof(int)=4 。

三,怎么算内存对齐大小(示范)?

#include <cassert>  

int main(int argc, char* argv[])
{
    //此处指定对齐大小为1
    //对于a,实际对齐大小为min(sizeof(int),1)=min(4,1)=1
    //对于b,实际对齐大小为min(sizeof(char),1)=min(1,1)=1
    //编译器会确保TEST_A首地址即a的地首址是1字节对齐的,此时a对齐
	//更多精彩内容:http://www.bianceng.cnhttp://www.bianceng.cn/Programming/cplus/
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_A数组,第一个TEST_A是对齐的(假设其地址为0),则第二个TEST_A的首地址为(0+5=5),对于第二个TEST_A的两个变量a,b均对齐
    //OK,对齐合理。因此整个结构体的大小为5
#pragma pack(1)
    struct TEST_A
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_A) == 5);  

    //此处指定对齐大小为2
    //对于a,实际对齐大小为min(sizeof(int),2)=min(4,2)=2
    //对于b,实际对齐大小为min(sizeof(char),2)=min(1,2)=1
    //编译器会确保TEST_A首地址即a的地首址是2字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_B数组,第一个TEST_B是对齐的(假设其地址为0),则第二个TEST_B的首地址为(0+5=5),对于第二个TEST_B的变量a,显然地址5是不对齐于2字节的
    //因此,需要在TEST_B的变量b后面填充1字节,此时连续相连的TEST_B数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+1=6
#pragma pack(2)
    struct TEST_B
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_B) == 6);  

    //此处指定对齐大小为4
    //对于a,实际对齐大小为min(sizeof(int),2)=min(4,4)=4
    //对于b,实际对齐大小为min(sizeof(char),2)=min(1,4)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_C数组,第一个TEST_C是对齐的(假设其地址为0),则第二个TEST_C的首地址为(0+5=5),对于第二个TEST_C的变量a,显然地址5是不对齐于4字节的
    //因此,需要在TEST_C的变量b后面填充3字节,此时连续相连的TEST_C数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+3=8
#pragma pack(4)
    struct TEST_C
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_C) == 8);  

    //此处指定对齐大小为8
    //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
    //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_D数组,第一个TEST_D是对齐的(假设其地址为0),则第二个TEST_D的首地址为(0+5=5),对于第二个TEST_D的变量a,显然地址5是不对齐于4字节的
    //因此,需要在TEST_D的变量b后面填充3字节,此时连续相连的TEST_D数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+3=8
#pragma pack(8)
    struct TEST_D
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_D) == 8);  

    //此处指定对齐大小为8
    //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
    //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //对于c,这是一个数组,数组的对齐大小与其单元一致,因而align(c)=align(double)=min(sizeof(double),8)=min(8,8)=8
    //对于d,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于c,由于c要求首地址8字节对齐,因此前面的a+b=5,还要在c后面补上3个字节才能对齐
    //对于d,显而易见,任何地址均对齐,此时结构体大小为4+1+3+10*8+1=89
    //对于TEST_E数组,第一个TEST_E是对齐的(假设其地址为0),则第二个TEST_E的首地址为(0+89=89),对于第二个TEST_E的变量a,显然地址89是不对齐于4字节的
    //因此,需要在TEST_E的变量d后面填充7字节,此时连续相连的TEST_E数组才会对齐
    //(注意:此处不仅要确保下一个TEST_E的a,b变量对齐,还要确保c也对齐,所以这里不是填充3字节,而是填充7字节)
    //OK,对齐合理。因此整个结构体的大小为(4)+(1+3)+(10*8)+(1+7)=96
#pragma pack(8)
    struct TEST_E
    {
        int a;
        char b;
        double c[10];
        char d;
    };
#pragma  pack()
    assert(sizeof(TEST_E) == 96);  

    return 0;
}

四,内存对齐相关

使用msvc未公开编译选项可以查看c++类的内存布局。使用方法:启动vs命令行,输入cl 【source.cpp】 /d1reportSingleClassLayout【CBaseClass1】以查看单个class的内存布局,输入cl 【source.cpp】 /d1reportAllClassLayout以查看所有类的内存布局。注意:/d1reportSingleClassLayout【CBaseClass1】没有空格 !!

大家可以用这个来对照我上面讲的例子来看编译器是怎么安排对齐的。

这个东东是神器,类似于宏展开时的选项(输出与处理过之后的源文件),一切内部布局方面的真相全都展现在你眼前,包括坑脑细胞的虚函数、虚函数表、虚基类表、虚继承等一系列坑爹。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索数组
, 字节数组
, 变量
, sizeof
, c++test
, c/c++ 内存 结构体
, x轴不连续
, 地址
, 字节
, 大小
, 对齐
, char a[]和int a[]c
, cpu字节对齐
结构体sizeof地址对齐
c 内存对齐、c语言内存对齐、c语言结构体内存对齐、内存对齐、结构体内存对齐,以便于您获取更多的相关知识。

时间: 2024-10-02 10:37:44

c/c++中内存对齐详解的相关文章

内存对齐详解

摘要 本文描述了内存对齐的各种概念和内存管理的其他知识点,应用相应的程序示例进行解释. 一.什么是内存对齐 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问, 这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是内存对齐. 二.内存对齐的原因 1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的:某些硬件平台只能在某些地址处取某些特

C语言内存对齐详解

一.字节对齐基本概念     现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐. 对齐的作用和原因:各个硬件平台对存 储空间的处理上有很大的不同.一些平台对某些特定类型的数据只能从某些特定地址开始存取.比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生 错误,那么在这种架构下编程必须保

基于Java中字符串内存位置详解_java

前言 之前写过一篇关于JVM内存区域划分的文章,但是昨天接到蚂蚁金服的面试,问到JVM相关的内容,解释一下JVM的内存区域划分,这部分答得还不错,但是后来又问了Java里面String存放的位置,之前只记得String是一个不变的量,应该是要存放在常量池里面的,但是后来问到new一个String出来应该是放到哪里的,这个应该是放到堆里面的,后来又问到String的引用是放在什么地方的,当时傻逼的说也是放在堆里面的,现在总结一下:基本类型的变量数据和对象的引用都是放在栈里面的,对象本身放在堆里面,

Hibernate配置文件中映射元素详解

详解 本文中将讲述Hibernate的基本配置及配置文件的应用,这对于正确熟练使用Hibernate是相当关键的. 配置文件中映射元素详解 对象关系的映射是用一个XML文档来说明的.映射文档可以使用工具来生成,如XDoclet,Middlegen和AndroMDA等.下面从一个映射的例子开始讲解映射元素,映射文件的代码如下. <?xml version="1.0"?><!--所有的XML映射文件都需要定义如下所示的DOCTYPE.Hibernate会先在它的类路径(c

php内存管理详解

php的内存管理 php和c最重要的区别就是是否控制内存指针. 内存 在php中, 设置一个字符串变量很简单: <?php $str = 'hello world'; ?>, 字符串可以自由的修改, 拷贝, 移动. 在C中, 则是另外一种方式, 虽然你可以简单的用静态字符串初始化: char *str = "hello world"; 但是这个字符串不能被修改, 因为它存在于代码段. 要创建一个可维护的字符串, 你需要分配一块内存, 并使用一个strdup()这样的函数将内

磁盘分区对齐详解与配置 – Linux篇

磁盘分区对齐详解与配置 – Linux篇 介绍 许多系统管理员可能不曾听过"磁盘分区对齐"之说,甚至一些有经验的存储管理员对分区对齐也不甚了解.磁盘分区不对齐现象是什么,为什么会造成比较严重的性能下降?相反,配置正确的分区起始位置(Offset)设置会使存储系统发挥更大的性能潜力.文章就磁盘分区对齐进行的介绍,并且给出了在Windows平台上如何配置的方法. 什么是磁盘分区对齐(Disk Alignment.Partition Alignment) Windows的磁盘有一种结构叫做M

从汇编看c++中的多态详解_C 语言

在c++中,当一个类含有虚函数的时候,类就具有了多态性.构造函数的一项重要功能就是初始化vptr指针,这是保证多态性的关键步骤. 构造函数初始化vptr指针 下面是c++源码: class X { private: int i; public: X(int ii) { i = ii; } virtual void set(int ii) {//虚函数 i = ii; } }; int main() { X x(1); } 下面是对应的main函数汇编码: _main PROC ; 16 : in

Java直接(堆外)内存使用详解

本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明 希望对想使用直接内存的朋友,提供点快捷的参考. 数据类型 下面这些,都是在使用DirectBuffer中必备的一些常识,暂作了解吧!如果想要深入理解,可以看看下面参考的那些博客. 基本类型长度 在Java中有很多的基本类型,比如: byte,一个字节是8位bit,也就是1B short,16位bit,也就是2B int,32位bit

java-在myeclipse中svn使用详解

问题描述 在myeclipse中svn使用详解 在myeclipse中svn使用详解.比如:标记为合并是在什么情况下用, 覆盖更新:在什么情况下使用等等. 本人对svn不是很了解,尤其是在有冲突文件的时候. 说的尽量详细点... 请各位大神指教~~~ 解决方案 如何在MyEclipse下集成SVN详解如何在MyEclipse下集成SVN详解MyEclipse中SVN使用步骤 解决方案二: myeclipse6.5集成svn 一.安装方法: 方法一.如果可以上网可在线安装 打开Myeclipse,