C语言内存对齐详解
一、字节对齐基本概念
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
本文地址:http://www.cnblogs.com/archimedes/p/memory-alignment.html,转载请注明源地址。
请看下面的结构:
struct struct1
{
double dda;
char cda;
int ida;
};
sizeof(struct1) = ?
错误的求法:
sizeof(struct1)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当你运行如下测试代码:
#include<stdio.h>
struct mystruct
{
double dda;
char cda;
int ida;
}; int main()
{
struct mystruct ss;
printf("%d\n",sizeof(ss));
return ;
}
运行结果为:16
其实,这是编译器对变量存储的一个特殊处理。为了提高CPU的存储速度,编译器对一些变量的起始地址做了“对齐”处理。在默认情况下,编译器规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式:
类型 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
char 偏移量必须为sizeof(char)即1的倍数
int 偏移量必须为sizeof(int)即4的倍数
float 偏移量必须为sizeof(float)即4的倍数
double 偏移量必须为sizeof(double)即8的倍数
Short 偏移量必须为sizeof(short)即2的倍数
各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节编译器会自动填充。同时编译器为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节
现在来分析编译器是怎样来存放结构的:
struct struct1
{
double dda;
char cda;
int ida;
};
第一个成员dda分配空间,其起始地址跟结构的起始地址相同(偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员cda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把cda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员ida分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把ida存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,没有空缺的字节需要填充。所以整个结构的大小为:sizeof(struct1)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
下面再举个例子,交换一下上面的struct1的成员变量的位置,使它变成下面的情况:
struct mystruct2
{
char cda;
double dda;
int ida;
};
运行结果为:24
struct mystruct2
{
char cda; //偏移量为0,满足对齐方式,cda占用1个字节;
double dda; //下一个可用的地址的偏移量为1,不是sizeof(double)=8
//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐
//方式),因此VC自动填充7个字节,dda存放在偏移量为8
//的地址上,它占用8个字节。 int ida; //下一个可用的地址的偏移量为16,是sizeof(int)=4的倍
//数,满足int的对齐方式,所以不需要VC自动填充,type存
//放在偏移量为16的地址上,它占用4个字节。 //所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构
//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为
//sizeof(double)=8的倍数。
};
所以该结构总的大小为:sizeof(struct2)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
二、#pragma pack(n)来设定变量以n字节对齐方式
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法:
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了4+8+4=16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。
再看下面这个例子:
#pragma pack(8)
struct S1{
char a;
long b;
}; struct S2 {
char c;
struct S1 d;
long long e;
};
#pragma pack()
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.
S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,sizeof(S2)为24个字节.
这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度。
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。
三、minix的stdarg.h文件中对齐方式
在minix的stdarg.h文件中,定义了如下一个宏:
/* Amount of space required in an argument list for an arg of type TYPE.
* TYPE may alternatively be an expression whose type is used.
*/ #define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - ) / sizeof (int)) * sizeof (int))
从注释以及宏的名字可以看出是有关内存对齐方面的作用。根据前面关于C语言内存对齐方面的理论可知
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式(各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数);
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
此时n = 4,对于sizeof(TYPE)一定为自然数,sizeof(int) - 1 = 3
sizeof(TYPE)只可能出现如下两种情况:
(1) 当sizeof(TYPE) >= 4,偏移量 = (sizeof(TYPE)/4)*4
(2) 当sizeof(TYPE) < 4,偏移量 = 4
此时sizeof(TYPE) = 1 or 2 or 3,而(sizeof(TYPE) + 3) / 4 = 1
为了将上述两种情况统一,偏移量 = ((sizeof(TYPE) + 3) / 4) * 4
在有的源代码中,将内存对齐宏__va_rounded_size通过位操作来实现,代码如下:
#define __va_rounded_size(TYPE) \
((sizeof(TYPE)+sizeof(int)-)&~(sizeof(int)-))
由于 ~(sizeof(int) – 1) ) = ~(4-1)=~(00000011B)=11111100B
(sizeof(TYPE) + sizeof(int) – 1)就是将大于4m但小于等于4(m+1)的数提高到大于等于4(m+1)但小于4(m+2),这样再& ~(sizeof(int) – 1) )后就正好将原长度补齐到4的倍数了。
C语言内存对齐详解的更多相关文章
- C语言内存对齐详解(2)
接上一篇:C语言内存对齐详解(1) VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式.VC 中提供了#pr ...
- C语言内存对齐详解(3)
接上一篇:C语言内存对齐详解(2) 在minix的stdarg.h文件中,定义了如下一个宏: /* Amount of space required in an argument list for a ...
- C语言内存对齐详解(1)
一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这 ...
- [转]C++结构体|类 内存对齐详解
内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址).访问数据(表现为CPU读取数据)的一种方式,包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐 . 为什么需要内存对齐?对 ...
- C/C++内存对齐详解
1.什么是内存对齐 还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte:但是实际上,通过运 ...
- 3.c语言结构体成员内存对齐详解
一.关键一点 最关键的一点:结构体在内存中是一个矩形,而不是一个不规则形状 二.编程实战 #include <stdlib.h> #include <stdio.h> stru ...
- 【转载】图说C++对象模型:对象内存布局详解
原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...
- java程序运行时内存分配详解
java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下 一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...
- C语言memset函数详解
C语言memset函数详解 memset() 的作用:在一段内存块中填充某个给定的值,通常用于数组初始化与数组清零. 它是直接操作内存空间,mem即“内存”(memory)的意思.该函数的原型为: # ...
随机推荐
- 实现TabView(页签)效果
今天花了点时间,设计了一个网页上用的tabview(页签.tabcontrol)效果.个人觉得实现得比较不错,网页元素用得比较少,js代码也比较精练.测试了一下支持IE.FireFox以及chrome ...
- 让文档和Demo生成更加简单和强大 - SmartDoc 0.1.1 说明
新特性 smartDoc 0.1.1版正式发布,其中加入了更多方便生成文档的功能,主要特性如下: * 加入@demo配置项,看可以动态抓取html和js的内容作为@example,同时支持扩展@dem ...
- [转] C#反射设置属性值和获取属性值
/// /// 获取类中的属性值 /// /// /// /// public string GetModelValue(string FieldName, object obj) { try { T ...
- 使用Html5+C#+微信 开发移动端游戏详细教程:(六)游戏界面布局与性能优化
本篇教程我们主要讲解在游戏界面上的布局一般遵循哪些原则和一些性能优化的通用方法. 接着教程(五),我们通过Loading类一次性加载了全部图像素材,现在要把我们所用到的素材变成图片对象显示在界面上,由 ...
- Android 布局之TableLayout
Android 布局之TableLayout 1 TableLayout简介 TableLayout是表格布局.TableLayout 可设置的属性包括全局属性及单元格属性. 1.1 全局属性 有以下 ...
- Android manifest之系统自带的permission
Android manifest之系统自带的permission 本文描述Android系统自带的permission.点击查看:“关于permission的原始定义和说明”.点击查看:“Androi ...
- 高性能JavaScript-JS脚本加载与执行对性能的影响
在web产品优化准则中,很重要的一条是针对js脚本的加载和执行方式的优化.本篇文章简单描述一下其中的优化准则. 1. 脚本加载优化 1.1 脚本位置对性能的影响 优化页面加载性能的原则之一是将scri ...
- webapp,liveapp: 流式布局和rem布局
liveapp场景应用,一般针对的是移动端,近来也是很火,颇有一些感受,拿来分享一下. 页面宽度范围: 一般移动端页面我们的像素范围是320px-640px,最大640px,最小320px,所以设计稿 ...
- android基础---->JSON数据的解析
上篇博客,我们谈到了XML两种常用的解析技术,详细可以参见我的博客(android基础---->XMl数据的解析).网络传输另外一种数据格式JSON就是我们今天要讲的,它是比XML体积更小的数据 ...
- php中mysql操作的buffer知识
php与mysql的连接有三种方式,mysql,mysqli,pdo.不管使用哪种方式进行连接,都有使用buffer和不使用buffer的区别. 什么叫使用buffer和不使用buffer呢? 客户端 ...