程序编译器对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。

n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,

第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

结构的总大小也有个约束条件,分下面两种情况:

如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。

重要的规则

1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;

2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐(对齐指的是数据类型在内存中的起始的N倍的跨度)(指复杂类型中成员类型中自己的对齐方式,并不是结构体等整体的对齐方式)

3,结构联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;

4,复杂类型(如结构)整体的对齐按照结构体中长度最大的数据成员和#pragma pack指定值之间较小N_min的那个值进行;

这样在成员是复杂类型时,可以最小化长度;(指整体的对齐方式

5,结构整体长度的计算必须取所用过的所有对齐参数的N_min整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;

整体复杂结构体的长度:(1)复杂结构体中最长的数据成员长度 与“设定默认长度” 的最小长度N_min(2)复杂类型整体实际长度(按照各自成员对齐规则的计算结果) 取N_min的整倍数,且如果有空余则空余补齐。

最优的存储,但是性能可能不是最优。

注意:对齐(对齐指的是数据类型在内存中距离起始长度M_selft倍的跨度, 其中N为本数据成员的长度M_selft),

  如果 struct {char a, float b, int c}

  因为: char 1bit, float 2Bit, int 4bit.

     则实际的内存占用情况: a:[0] X  b:[2][3]   c:[4][5][6][7]

  即: [a][][b][b][c][c][c][c], 因为 a:1首地址, b=2则需要对齐到长度为2的地址[2][3], c=4需要对齐到长度为4的地址

  


  对齐的实现:通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。 但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。
作用:指定结构体、联合以及类成员的packing alignment;(设定对齐方式)
语法#pragma pack( [show] | [push | pop] [, identifier], n )

说明:

1,pack提供数据声明级别的控制,对定义不起作用;
2,调用pack时不指定参数,n将被设成默认值,默认8字节;
3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降

语法具体分析:

1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

强调一点:
#pragma pack(4)
typedef struct
{
char buf[];
word a;
}kk;
#pragma pack()

  对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。

这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

  补充一下,对于数组,比如:char a[3];

  这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
  如果写: typedef char Array3[3];
  Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
  不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.

例子一:
#pragma pack(4)
class TestB
{
public:
int aa;//第一个成员,放在[0,3]偏移的位置,(结构体内部成员对齐, int = 4 )
char a; //第二个成员,自身长为1,#pragma pack(),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[]的位置。(结构体内部成员类型对齐, 1 < 4 取最小1字节对齐)
short b; //第三个成员,自身长2,#pragmapack(),取小值,取2,按2字节对齐,所以放在偏移[,]的位置。(结构体内部成员类型对齐, 2 < 4 取最小2字节对齐)
char c;//第四个,自身长为1,放在[8]的位置。(结构体内部成员类型对齐, 1 < 4 取最小1字节对齐)
};
[5]地址,因为short b为2长度,则需要跳过。
可见,此类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof(int ), pack_value ) = ,所以sizeof( TestB ) = ;
说明:
  整体复杂结构体的长度:
(1)复杂结构体中最长的数据成员长度 与“设定默认长度” 的最小长度N_min
(2)复杂类型整体实际长度 取N_min的整倍数,且如果有空余则空余补齐。
例子二:
#pragma pack(2)
class TestB
{
public:
int aa;//第一个成员,放在[0,3]偏移的位置, (结构体内部成员类型对齐, int=4 > 2 取最小2字节对齐)
char a; //第二个成员,自身长为1,#pragmapack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。(结构体内部成员类型对齐, 1 < 2 取1字节对齐)
short b; //第三个成员,自身长2,#pragmapack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。(结构体内部成员类型对齐,2 = 2 取2字节对齐)
char c;//第四个,自身长为1,放在[8]的位置。(结构体内部成员类型对齐, 1 < 2 取最小1字节对齐)
};
[5]地址,因为short b为2长度,则需要跳过。
可见,此类实际占用的内存空间是9个字节。根据规则5,
可见结果与例子一相同,各个成员的位置没有改变,但是此时结构整体的对齐是min( sizeof(int ), pack_value ) = ,所以sizeof( TestB ) = ;
例子三:
#pragma pack(4)
class TestC
{
public:
char a;//第一个成员,放在[0]偏移的位置,
short b; //第二个成员,自身长2,#pragmapack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
char c;//第三个,自身长为1,放在[4]的位置。
};
[1]地址,因为short b为2长度,则需要跳过,直接定位到2的倍数的对齐地址。[2][3]。
整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), ) = 2对齐,所以结果是sizeof( TestC ) = ;
例子四:
struct Test
{
char x1; //第一个成员,放在[0]位置,
因为short x2为2长度,则需要跳过[1],直接定位到2的倍数的对齐地址。[2][3]。
short x2;//第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,
float x3;//第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,
char x4;//第四个陈冠,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,
};
所以整个结构体的实际内存消耗是9个字节,但考虑到结构整体的对齐是4个字节(默认的对齐方式),所以整个结构占用的空间是12个字节。
例子五:
#pragma pack(8) struct s1
{
short a; //第一个,放在[0,1]位置,
因为 b 的长度为4, 需要“对齐地址为4倍的地址”,所以忽略掉了[2][3]
long b; //第二个,自身长度为4,按min(4,8) = 4对齐,所以放在[4,7]位置
};
所以结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ),pack_value ) = 4字节,所以整个结构占用的空间是8个字节。 struct s2
{
char c; //第一个,放在[0]位置,
s1 d; //第二个,根据规则四,对齐是min( 4,pack_value ) = 4字节,所以放在[4,11]位置,
long long e;//第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,
};
所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。
#pragma pack()
所以:
sizeof(s2) = , s2的c后面是空了3个字节接着是d。

endl;

C++内存对齐的理解的更多相关文章

  1. c/c++中内存对齐完全理解

    一,什么是内存对齐?内存对齐用来做什么? 所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段. 比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存 ...

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

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

  3. C/C++中内存对齐问题的一些理解(转)

    内存对齐指令 一般来说,内存对齐过程对coding者来说是透明的,是由编译器控制完成的 如对内存对齐有明确要求,可用#pragma pack(n)指定,以n和结构体中最长数据成员长度中较小者为有效值 ...

  4. C/C++: C++位域和内存对齐问题

    1. 位域: 1. 在C中,位域可以写成这样(注:位域的数据类型一律用无符号的,纪律性). struct bitmap { unsigned a : ; unsigned b : ; unsigned ...

  5. C语言中内存对齐

    今天一考研同学问我一个问题,一个结构体有一个int类型成员和一个char类型成员,问我这个结构体类型占多少个字节,我直接编个程序给他看结果.这个结构体占八个字节,咦,当时我蛮纳闷的,一个int类型四个 ...

  6. 内存对齐 和 sizeof小结

    数据对齐(内存对齐)指该数据所在的地址必须是该数据长度的整数倍.X86CPU能直接访问对齐的数据,当它试图访问未对齐的数据时,会在内部进行一系列的调整,降低运行速度.数据对齐一般出现在结构体和类中,在 ...

  7. 解析C语言结构体对齐(内存对齐问题)

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

  8. C语言再学习之内存对齐

    昨天看Q3的代码,看到有个_INTSAIZEOF的宏,着实晕了一阵.一番google后,终于明白,这个宏的作用是求出变量占用内存空间的大小,先看看_INTSAIZEOF的定义吧: #define _I ...

  9. C结构体中数据的内存对齐问题

    转自:http://www.cnblogs.com/qwcbeyond/archive/2012/05/08/2490897.html 32位机一般默认4字节对齐(32位机机器字长4字节),64位机一 ...

随机推荐

  1. 所有HTTP请求参数及报文查看SERVLET

    HttpRequestServlet.java 说明: 用于接受所有http形式的请求,并把接受到的request中param及getInputStream全打印出来. package king.se ...

  2. B. Shaass and Bookshelf DP

    http://codeforces.com/contest/294/problem/B 据说是贪心,我用了一个复杂度是2e8的dp水过去了. 其实这题就是给你n个数,每个数有两个权值,分成两组,使得第 ...

  3. sql server中自连接的使用

    一.用SQL自连接查询处理列之间的关系 SQL自身连接,可以解决很多问题.下面举的一个例子,就是使用了SQL自身连接,它解决了列与列之间的逻辑关系问题,准确的讲是列与列之间的层次关系.SQL代码如下: ...

  4. Activity之间传递参数(四)

    --------siwuxie095 获取Activity的返回参数 1.首先修改两个布局文件,都修改为 LinearLayout 布局, 添加orientation属性为:vertical. (1) ...

  5. 转载:Bootstrap之表格checkbox复选框全选

    转:http://blog.csdn.net/shangmingchao[商明超的博客] 效果图: HTML中无需添加额外的一列来表示复选框,而是由JS完成,所以正常的表格布局就行了: 版权声明:如需 ...

  6. Hibernate 注解的用法以及说明

    1.类级别注解 @Entity     映射实体类 @Table    映射数句库表  @Entity(name="tableName") - 必须,注解将一个类声明为一个实体be ...

  7. js正则获取图片的src属性及正则分割一个字符串

    try{     var str='<P>xczxzxzxcxcxc<IMG src="http://file.ynet.com/2/1612/12/12119048.jp ...

  8. Ubuntu root 密码 sudo passwd

    用vmware安装好ubuntu后,su - 切换不到root,提示密码错误. 解决办法: 1.用当前登录用户打开终端,在终端输入命令 sudo passwd,输入当前用户的密码然后回车 2.会提示输 ...

  9. String,StringBuffer

    String类代表不可变的字符序列. String s1 = "hello"; String s2 = "hello"; s1 == s2  ==> tr ...

  10. (重要)使用廉价的VPN隐私会被窥探

    使用廉价的VPN可以节省一部分的支出,但是损失的却是我们的网上隐私,很多部门都很关心我们在网上干些什么,本来使用VPN是为了保护自己的隐私不被窥探,很显然这是事与愿违的,一些小公司或者大公司,通过提供 ...