热门标签 | 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)这个结构需通过网络传给其他程序;


推荐阅读
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 如何在PHPcms网站中添加广告
    本文详细介绍了在PHPcms网站后台添加广告的方法,涵盖多种常见的广告形式,如百度广告和Google广告,并提供了相关设置的步骤。同时,文章还探讨了优化网站流量的SEO策略。 ... [详细]
  • 当iOS设备越狱后,某些插件可能会导致系统崩溃(白苹果)。此时,可以通过进入安全模式来排查并删除有问题的插件。本文将详细介绍如何通过特定按键组合进入不加载MobileSubstrate的安全模式,并提供相关背景知识。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • C++: 实现基于类的四面体体积计算
    本文介绍如何使用C++编程语言,通过定义类和方法来计算由四个三维坐标点构成的四面体体积。文中详细解释了四面体体积的数学公式,并提供了两种不同的实现方式。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 如何优化2060显卡设置以提升《Apex英雄》游戏体验
    《Apex英雄》作为一款热门的战术竞技游戏,吸引了大量玩家。本文将探讨如何通过优化GeForce RTX 2060显卡设置,确保在《Apex英雄》中获得最佳性能和流畅的游戏体验。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 本文介绍如何通过SQL查询从JDE(JD Edwards)系统中提取所有字典数据,涵盖关键表的关联和字段选择。具体包括F0004和F0005系列表的数据提取方法。 ... [详细]
  • 如何高效创建和使用字体图标
    在Web和移动开发中,为什么选择字体图标?主要原因是其卓越的性能,可以显著减少HTTP请求并优化页面加载速度。本文详细介绍了从设计到应用的字体图标制作流程,并提供了专业建议。 ... [详细]
  • 本文详细介绍了如何通过命令行启动MySQL服务,包括打开命令提示符窗口、进入MySQL的bin目录、输入正确的连接命令以及注意事项。文中还提供了更多相关命令的资源链接。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文将详细介绍在Windows 7环境下,检查U盘启动盘是否制作成功的多种方法,包括通过BIOS设置和使用模拟启动工具。 ... [详细]
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社区 版权所有