热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

深入理解c/c++内存对齐

这篇文章主要介绍了cc++内存对齐,有需要的朋友可以参考一下

内存对齐,memory alignment.为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
内存对齐一般讲就是cpu access memory的效率(提高运行速度)和准确性(在一些条件下,如果没有对齐会导致数据不同步现象).依赖cpu,平台和编译器的不同.一些cpu要求较高(这句话说的不准确,但是确实依赖cpu的不同),而有些平台已经优化内存对齐问题,不同编译器的对齐模数不同.总的来说内存对齐属于编译器的问题.

一般情况下不需要理会内存对齐问题,内存对齐是编译器的事情.但碰到一些问题上还是需要理解这个概念.毕竟c/c++值直接操作内存的语言.需要理解程序在内存中的分布和运行原理.

总之一句话就是:不要让代码依赖内存对齐.


1.原因:为什么需要内存对齐.
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。


2.内存对齐的规则和范例
讲述内存对齐之前先看下各种类型的大小,和编译器以及字长有关具体在此不多叙述.
具体帖子:http://blog.csdn.net/lyl0625/article/details/7350045
成员的内存分配规律:从结构体的首地址开始向后依次为每个成员寻找第一个满足条件的首地址x,该条件是x % N = 0,并且整个结构的长度必须为各个成员所使用的对齐参数中最大的那个值的最小整数倍,不够就补空字节。
结构体中所有成员的对齐参数N的最大值称为结构体的对齐参数。

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值(或默认值)和这个数据成员类型长度中,比较小的那个进行。在上一个对齐后的地方开始寻找能被当前对齐数值整除的地址.
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐.主要体现在,最后一个元素对齐后,后面是否填补空字节,如果填补,填补多少.对齐将按照#pragma pack指定的数值(或默认值)和结构(或联合)最大数据成员类型长度中,比较小的那个进行。
3、结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员类型长度的时候,这个n值的大小将不产生任何效果。
两点注意:数组,嵌套结构体.
数组:
对齐值为:min(数组元素类型,指定对齐长度).但数组中的元素是连续存放,存放时还是按照数组实际的长度.
如char t[9],对齐长度为1,实际占用连续的9byte.然后根据下一个元素的对齐长度决定在下一个元素之前填补多少byte.
嵌套的结构体:
假设
struct A
{
......
struct B b;
......
};
对于B结构体在A中的对齐长度为:min(B结构体的对齐长度,指定的对齐长度).
B结构体的对齐长度为:上述2中结构整体对齐规则中的对齐长度.

例子:
VC++6.0中n 默认是8个字节,可以修改这个设定的对齐参数
也可以采用指令:#pragma   pack(xx)控制.

1.基础例子

代码如下:

#pragma   pack(n)
struct A
{
char   c; //1byte
double d; //8byte
short s; //2byte
int i; //4byte
};
int main(int argc, char* argv[])
{
A strua;
printf("%len:d\n",sizeof(A));
printf("%d,%d,%d,%d",&strua.c,&strua.d,&strua.s,&strua.i);
return 0;
}

1)n设置为8byte时
结果:len:24,
1245032,1245040,1245048,1245052
内存中成员分布如下:
strua.c分配在一个起始于8的整数倍的地址1245032(为什么是这样读者先自己思考,读完就会明白),接下来要在strua.c之后分配strua.d,由于double为8字节,取N=min(8,8),8字节来对齐,所以从strua.c向后找第一个能被8整除的地址,所以取1245032+8得1245040, strua.s 为2byte小于参数n,所以N=min(2,8),即N=2,取2字节长度对齐,所以要从strua.d后面寻找第一个能被2整除的地址来存储strua.s,由于strua.d后面的地址为1245048可以被2整除,所以strua.s紧接着分配,现在来分配strua.i,int为4byte,小于指定对齐参数8byte,所以N=min(4,8)取N=4byte对齐,strua.s后面第一个能被4整除地址为1245048+4,所以在1245048+4的位置分配了strua.i,中间补空,同时由于所有成员的N值的最大值为8,所以整个结构长度为8byte的最小整数倍,即取24byte其余均补0.
于是该结构体的对齐参数就是8byte。
2)当对齐参数n设置为16byte时,结果同上,不再分析
3)当对齐参数设置为4byte时
上例结果为:Len:20
1245036,1245040,1245048,1245052
内存中成员分布如下:
Strua.c起始于一个4的整数倍的地址,接下来要在strua.c之后分配strua.d,由于strua.d长度为8byte,大于对齐参数4byte,所以N=min(8,4)取最小的4字节,所以向后找第一个能被4整除的地址来作为strua.d首地址,故取1245036+4,接着要在strua.d后分配strua.s,strua.s长度为2byte小于4byte,取N=min(2,4)2byte对齐,由于strua.d后的地址为1245048可以被2
整除,所以直接在strua.d后面分配,strua.i的长度为4byte,所以取N=min(4,4)4byte对齐,所以从strua.s向后找第一个能被4整除的位置即1245048+4来分配和strua.i,同时N的最大值为4byte,所以整个结构的长度为4byte的最小整数倍20byte
4)当对齐参数设置为2byte时
上例结果为:Len:16
1245040,1245042,1245050,1245052
Strua.c分配后,向后找一第一个能被2整除的位置来存放strua.d,依次类推
5)1byte对齐时:
上例结果为:Len:15
1245040,1245041,1245049,1245051
此时,N=min(sizeof(成员),1),取N=1,由于1可以整除任何整数,所以各个成员依次分配,没有间空.
6)当结构体成员为数组时,并不是将整个数组当成一个成员来对待,而是将数组的每个元素当一个成员来分配,其他分配规则不变,如将上例的结构体改为:
struct A
{
char c; //1byte
double d; //8byte
short s; //2byte
char  szBuf[5];
};
对齐参数设置为8byte,则,运行结果如下:
Len:24
1245032,1245040,1245048,1245050
Strua 的s分配后,接下来分配Strua 的数组szBuf[5],这里要单独分配它的每个元素,由于是char类型,所以N=min(1,8),取N=1,所以数组szBuf[5]的元素依次分配没有间隙。

看完上述的例子,基本分配的规律和方法应该已经知道.下面主要说明数组,嵌套结构体,指针时的一些内存对齐问题.
最重要的是自己写程序证明.

2.数组,嵌套.
测试环境:64位 ubuntu;g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

代码如下:

#include
#include
using namespace std;
#pragma pack(8)
struct Args
{
        char ch;
        double d;
        short st;
        char rs[9];
        int i;
} args;
struct Argsa
{
        char ch;
        Args test;
        char jd[10];
        int i;
}arga;

int main()
{
// cout <//cout<cout<<"Args:"<cout<<""<<(unsigned long)&args.i-(unsigned long)&args.rs<cout<<"Argsa:"<cout<<"Argsa:"<<(unsigned long)&arga.i -(unsigned long)&arga.jd<cout<<"Argsa:"<<(unsigned long)&arga.jd-(unsigned long)&arga.test<return 0;
}

输出结果:
Args:32
10
Argsa:56
Argsa:12
Argsa:32

struct Args长度32 struct Argsa长度:56.
改成#pragma pack (16)结果一样.
这个例子证明了三点:
对齐长度长于struct中的类型长度最长的值时,设置的对齐长度等于无用.
数组对齐的长度是按照数组成员类型长度来比对的.
嵌套的结构体中,所包含的结构体的对齐长度是结构体的对齐长度.

3.指针.主要是因为32位和64位机寻址上
测试环境同2.(64位系统)

代码如下:

#include
#include
#pragma pack(4)
using namespace std;

struct Args
{
        int i;
        double d;
        char *p;
        char ch;
        int *pi;
}args;
int main()
{   
        cout<<"args length:"<        cout<<(unsigned long)&args.ch-(unsigned long)&args.p<        cout<<(unsigned long)&args.pi-(unsigned long)&args.ch<        return 0;
}

设置pack为4时:
args length:32
8
4

设置pack为8时:
args length:40
8
8
看了上述内容,应该能分析出来为什么是这个结果.这里不详细描述.

3.不同编译器中内存对齐
VC 6.0上是8 byte

gcc 默认是8byte.测试版本gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
g++默认是8byte.测试版本g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
但查阅的资料说是gcc 默认是4,且不支持pragma参数设定.测试的时候gcc默认对齐为8byte且,支持pragma参数.
测试过两个不同的例子,结果相同.

4.什么时候需要进行内存对齐.
一般情况下都不需要对编译器进行的内存对齐规则进行修改,因为这样会降低程序的性能,除非在以下两种情况下:

(1)这个结构需要直接被写入文件;

(2)这个结构需通过网络传给其他程序;


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了MyBioSource转甲状腺素蛋白定量检测ELISA试剂盒的应用方法及特点。ELISA法作为一项新技术在免疫诊断中的应用范围不断扩大,不仅适用于多种病原微生物引起的传染病、非传染病的免疫诊断,也可用于大/小分子抗原的定量检测。ELISA法具有灵敏、特异、简单、快速、稳定及易于自动化操作等特点,是一种早期诊断的良好方法,也可用于血清流行病学调查。MyBioSource转甲状腺素蛋白定量检测ELISA试剂盒使用方法包括对血清和血浆的操作要求。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
author-avatar
初来乍到1231
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有