您的位置:首页 > 编程语言 > C语言/C++

[memory]C语言关于字节对齐的问题

2016-02-26 15:30 567 查看
一、开篇

本篇介绍一些关于C语言内存字节对齐知识,希望在以后碰到此类问题时不再慌张。

二、版权声明
博主:summer
声明:喝水不忘挖井人,转载请注明出处。
原文地址:http://blog.csdn.net/qq_21842557
联系方式:dxl0725@126.com
技术交流QQ:1073811738
三、由一道面试题引发的思考
题目如下:

给出这样一道题目:
//==============================
32位系统下,有如下定义:
typedef struct _AAA
{
char    b;
double  a;
char    c;
}AAA;

问:sizeof(AAA)的值为多少。
//==============================

这个题目本身是考察的内存对齐的问题,根据不同的操作系统和编译器,可能得到不同的结果,在Windows+VS:24,在Linux+GCC:16。答案到底是多少呢?

上述反汇编得到(Windows+VC6.0++):

typedef struct _AAA
{
char b;
double a;
char c;
}AAA;

int main(void)
{
AAA aaa;
aaa.b = 8;
00000000  push        esi
00000001  sub         esp,18h
00000004  cmp         dword ptr ds:[009B2E08h],0
0000000b  je          00000012
0000000d  call        78FD6F4F
00000012  xor         esi,esi
00000014  xor         esi,esi
00000016  mov         byte ptr [esp],8
aaa.a = 1.234;
0000001a  fld         qword ptr ds:[00FCCAA0h]
00000020  fstp        qword ptr [esp+8]
aaa.c = 0x4;
00000024  mov         byte ptr [esp+10h],4
printf("%d\n", sizeof(AAA));

由汇编容易看出:

aaa.b在[esp+0x0h]--->char小于double,按double填充7个字节

aaa.a在[esp+0x8h]

aaa.c在[esp+0x10h]-->char小于double,按double填充7个字节

四、关于字节对齐问题需要清楚下面几个问题:

1、什么是字节对齐

现代计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。

2、字节对齐的原因和作用

不同硬件平台对存储空间的处理上存在很大的不同。某些平台对特定类型的数据只能从特定地址开始存取,而不允许其在内存中任意存放。例如Motorola 68000 处理器不允许16位的字存放在奇地址,否则会触发异常,因此在这种架构下编程必须保证字节对齐。

但最常见的情况是,如果不按照平台要求对数据存放进行对齐,会带来存取效率上的损失。比如32位的Intel处理器通过总线访问(包括读和写)内存数据。每个总线周期从偶地址开始访问32位内存数据,内存数据以字节为单位存放。如果一个32位的数据没有存放在4字节整除的内存地址处,那么处理器就需要2个总线周期对其进行访问,显然访问效率下降很多。

因此,通过合理的内存对齐可以提高访问效率。为使CPU能够对数据进行快速访问,数据的起始地址应具有“对齐”特性。比如4字节数据的起始地址应位于4字节边界上,即起始地址能够被4整除。

此外,合理利用字节对齐还可以有效地节省存储空间。但要注意,在32位机中使用1字节或2字节对齐,反而会降低变量访问速度。因此需要考虑处理器类型。还应考虑编译器的类型。在VC/C++和GNU GCC中都是默认是4字节对齐。

3、编译器的优化

字节对齐问题属于编译优化选项,是可以选择的,在VC6.0下可以选择1,2,4,8,16字节对齐。不同字节对齐方式,sizeof()的结果可能不一样。

4、要明白对齐是要什么东西对齐,怎么对齐

对齐是要求结构体内的域在内存中按域数据类型对齐,即域对齐的地址是和与自身的类型相关的。例如:

byte、char:1字节对齐

short:2字节对齐

int、long: 4字节对齐

double: 8字节对齐

对齐时,1字节对齐类型可以紧挨着前一个域,因为任何地址都是1字节对齐的;1字节对齐类型必须从偶输字节开始,也就是说前面可能会有一个字节的gap;以此类推,double当然可能前面最多会有7个字节的gap被浪费掉。

5、可能的结果

因为可以选择不同对齐方式,面试题如果选择1、2、4、8、16字节对齐,sizeof()大小依此就是10、12、16、 24、 48。对齐是优化的结果,也就是说它提高了域字段访问的速度。

五、下面讨论一些关于内存对齐的原则:

1、字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:

  1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

  2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

  3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

对于以上规则的说明如下:

第一条:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

第二条:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员大小的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

第三条:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

2、内存对齐的主要作用是:

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

2)性能原因:经过内存对齐后,CPU的内存访问速度大大提升。

There's two reasons for this: backwards compatibility and efficiency

六、对齐的隐患

抛砖引玉,如下代码中关于对齐的隐患,很多是隐式的。例如,在强制类型转换的时候:

int main(void){
unsigned int i = 0x12345678;

unsigned char *p = (unsigned char *)&i;
*p = 0x00;
unsigned short *p1 = (unsigned short *)(p+1);
*p1 = 0x0000;

return 0;
}
最后两句代码,从奇数边界去访问unsigned short型变量,显然不符合对齐的规定。在X86上,类似的操作只会影响效率;但在MIPS或者SPARC上可能导致error,因为它们要求必须字节对齐。
更多详细细节请移步:C语言字节对齐问题详解

七、如何修改内存对齐方式

C语言中有个命令:#pragma pack(n)

默认情况下是以结构体中最大的数进行对齐,当数据小于最大类型的大小时,数据合并,这样做CPU避免在进行数据读写时,读取数据时由于字节没有对齐,导致同一个数据读取两次,比如一个double数据如果没有采用数据对齐,则可能在某次操作时,读取到double的前4个字节,然后计算机必须再花一次操作读取另外的4个字节,操作系统这么做是优化CPU的执行效率,以空间换时间。

默认应该是24,因为最大字节数是8,如果这样回答就能显示你的水平了。

1、这个结果不确定, 假设 1 假设按照8字节排, 结果分别是什么?

2、如果是1 一般是网络使用, 原因是节约空间。

3、如果是8字节 是为了提高cpu 获取速度。

4、对8字节的优化 将 第一个字段放到后面。 这样就是16。

5、加速办法加pading,在第一个字节后面加pad,pading 的好处,便于dispatch 的时候处理消息码。


Memory access granularity

Programmers are conditioned to think of memory as a simple array of bytes. Among C and its descendants,
char*
is
ubiquitous as meaning "a block of memory", and even Java™ has its
byte[]
type
to represent raw memory.

Figure 1. How programmers see memory




However, your computer's processor does not read from and write to memory in byte-sized chunks. Instead, it accesses memory in two-, four-, eight- 16- or even 32-byte chunks. We'll call the size in which a processor accesses memory its memory
access granularity.

Figure 2. How processors see memory




The difference between how high-level programmers think of memory and how modern processors actually work with memory raises interesting issues that this article explores.

If you don't understand and address alignment issues in your software, the following scenarios, in increasing order of severity, are all possible:

Your software will run slower.

Your application will lock up.

Your operating system will crash.

Your software will silently fail, yielding incorrect results.

更多细节参考:Data alignment: Straighten up and fly right
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: