首先由一个程序引入话题:
程序的输出结果为:
sizeof(st1) is 12
sizeof(st2) is 8
问题出来了,这两个一样的结构体,为什么sizeof的时候大小不一样呢?
本文的主要目的就是解释明白这一问题。
内存对齐,正是因为内存对齐的影响,导致结果不同。
对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。
那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int) + 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。
在解释内存对齐的作用前,先来看下内存对齐的规则:
<!--[if !supportLists]-->1、 <!--[endif]-->对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。
<!--[if !supportLists]-->2、 <!--[endif]-->在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
#pragma pack(n) 表示设置为n字节对齐。 VC6默认8字节对齐
以程序1为例解释对齐的规则 :
St1 :char占一个字节,起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:
oxxx|oooo|oo
0123 4567 89 (地址)
(x表示额外添加的字节)
共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12 。此时的内存状态为:
oxxx|oooo|ooxx
0123 4567 89ab (地址)
到此内存对齐结束。St1占用了12个字节而非7个字节。
St2 的对齐方法和st1相同,读者可自己完成。
内存对齐的主要作用是:
<!--[if !supportLists]-->1、 <!--[endif]-->平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
<!--[if !supportLists]-->2、 <!--[endif]-->性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。
图一:
这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。
图二:
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
<!--[if !supportLists]-->1、<!--[endif]-->数据从0字节开始
<!--[if !supportLists]-->2、<!--[endif]-->数据从1字节开始
再次假设内存读取粒度为4。
图三:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
图四:
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接down掉了。
1 //环境:vc6 + windows sp2 2 //程序1 3 #include <iostream> 4 5 using namespace std; 6 7 struct st1 8 { 9 char a ; 10 int b ; 11 short c ; 12 }; 13 14 struct st2 15 { 16 short c ; 17 char a ; 18 int b ; 19 }; 20 21 int main() 22 { 23 cout<<"sizeof(st1) is "<<sizeof(st1)<<endl; 24 cout<<"sizeof(st2) is "<<sizeof(st2)<<endl; 25 return 0 ; 26 }
程序的输出结果为:
sizeof(st1) is 12
sizeof(st2) is 8
问题出来了,这两个一样的结构体,为什么sizeof的时候大小不一样呢?
本文的主要目的就是解释明白这一问题。
内存对齐,正是因为内存对齐的影响,导致结果不同。
对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。
那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int) + 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。
在解释内存对齐的作用前,先来看下内存对齐的规则:
<!--[if !supportLists]-->1、 <!--[endif]-->对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。
<!--[if !supportLists]-->2、 <!--[endif]-->在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
#pragma pack(n) 表示设置为n字节对齐。 VC6默认8字节对齐
以程序1为例解释对齐的规则 :
St1 :char占一个字节,起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:
oxxx|oooo|oo
0123 4567 89 (地址)
(x表示额外添加的字节)
共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12 。此时的内存状态为:
oxxx|oooo|ooxx
0123 4567 89ab (地址)
到此内存对齐结束。St1占用了12个字节而非7个字节。
St2 的对齐方法和st1相同,读者可自己完成。
内存对齐的主要作用是:
<!--[if !supportLists]-->1、 <!--[endif]-->平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
<!--[if !supportLists]-->2、 <!--[endif]-->性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。
图一:
这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。
图二:
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
<!--[if !supportLists]-->1、<!--[endif]-->数据从0字节开始
<!--[if !supportLists]-->2、<!--[endif]-->数据从1字节开始
再次假设内存读取粒度为4。
图三:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
图四:
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接down掉了。
发表评论
-
const 与static 关键词在c 和 c++中的作用
2012-08-11 11:49 998一.C语言中的const ... -
error LNK2005 and error LNK2019
2012-08-09 23:08 1174error LNK2005 and error LNK2019 ... -
LIBCD.lib(crt0dat.obj) : error LNK2005: _exit already defined in msvcrtd.lib(MSV
2012-08-07 09:20 1442LIBCD.lib(crt0dat.obj) : error ... -
在VS2010下配置VC++ Directories
2012-08-07 08:29 10981:可以 在“class View” 面板 -> 右 ... -
零星笔记,待整理
2010-11-01 13:00 510//用 getline 函数从输入读取整行内容。然后为 ... -
VC: error LNK2019:unresolved external symbol *** referenced in function ***的解决方案
2010-10-30 21:13 2140当头文件中声明了一个函数,但是在相应的源文件中却没有对该函数进 ... -
stdcall cdecl fastcall thiscall naked call的具体含义
2010-10-30 18:54 983在C语言中,假设我们有 ... -
#define总结 (网上资料汇集)
2010-10-30 08:06 7441. 定义简单的常数:定义常量,便于修改(切不可在后面加上分号 ... -
(zz)编译与使用Qt4,Visual Studio Express 2008
2010-10-14 22:35 1409一、Qt版本 我现在用的是4.5.2。好像前段时间4.6出来 ... -
strcat 内部实现
2010-09-21 00:43 1828char * strcat(char * dest, ... -
deep copy & shallow copy
2010-09-15 19:27 852A shallow copy of an object cop ... -
reinterpret_cast的本质(转)
2010-09-14 23:15 1644先看看下面的代码: #include <iostr ... -
C++ 经典书籍
2010-09-14 16:18 1723下载地址: http://www.51cnnet.net/di ... -
[转]C++ reinterpret_cast,const_cast等 显式类型转换总结
2010-09-13 23:23 1053#include<iostream.h> ... -
C++提供了四种新的类型强制
2010-09-13 23:13 719static_cast const_cast reinte ... -
C++著名程序库的比较和学习经验
2010-09-13 19:30 8611、C++各大有名库的介绍——C++标准库 2、C++各大有名 ... -
c++ library and resource List
2010-09-13 18:56 9301,前言 无数次听 ... -
C++中操作符operator的两种用法
2010-09-12 02:23 11481.operator overloading C++可能通过o ... -
两种自动类型转换:构造函数与operator
2010-09-12 02:23 928如果有两个对象:one, two, 要想把one转换成为two ... -
C++中的类拷贝构造函数和模板拷贝构造函数
2010-09-11 20:53 3034类(包括模板类)构造函数是真实的构造函数;然而模板构造函数,其 ...
相关推荐
内存对齐规则通常包括以下几点: 1. **成员对齐规则**:结构体的第一个成员的地址是0,后续每个成员的起始位置应当是其自身大小的整数倍。例如,如果一个成员是4字节的整数,那么它的起始地址必须是4的倍数。 2. *...
本文将从深入浅出的角度,展示了指针的密码,介绍 C++ 中的内存对齐规则的概念、作用、实现和算法。 概念: 内存对齐是指在存储器中对数据的排列方式,使得每种类型的变量都从特定的内存地址开始访问,以提高存取...
### 内存对齐原理详解 #### 一、什么是内存对齐? 在计算机科学中,内存对齐(Memory Alignment)是指数据类型与内存地址之间的关系。简单来说,就是指数据结构(尤其是结构体中的成员)在内存中的起始地址应该...
那么,如何判断内存对齐规则呢?这里有三个原则: 1. 数据成员对齐规则:结构体(struct)或联合(union)的数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置要从该成员大小或者...
了解这些内存对齐规则对于优化代码和理解程序在内存中的行为至关重要,尤其是在处理大型数据结构或跨平台开发时。正确地对齐数据可以提高访问速度,减少不必要的内存消耗,同时确保程序能在不同架构上正确运行。
说明: 1.为什么需要内存对齐? 2.总结结构中内存对齐的规则
内存对齐是计算机编程中一个重要的概念,尤其是在优化程序性能和保证系统兼容性时不可...在阅读和学习这些材料时,你可以重点关注如何根据数据类型和编译器设置调整内存对齐策略,以及如何通过编程技巧来优化内存使用。
在C语言中,结构体的大小不仅取决于结构体成员的个数和类型,还取决于内存对齐的规则。在32位机中,int是4个字节,char是1个字节,因此结构体T{char ch; int i;}的大小应该是5个字节。但是,在VC6中测试结果却是8个...
- 当结构体包含其他结构体作为成员时,子结构体会按照自己的对齐规则对齐,这可能导致父结构体的总大小增加。 - 数组的每个元素都会按照元素类型的要求进行对齐,所以数组可能会导致额外的填充。 理解并掌握内存...
1. 数据成员对齐规则:结构体的第一个数据成员存储在偏移量为0的位置,之后的每个数据成员存储的起始位置必须是其自身大小的整数倍。例如,如果一个结构体包含一个int(假设为4字节),那么接下来的数据成员将从4的...
内存字节对齐是一种编程实践中遵循的规则,它涉及到如何在计算机内存中安排数据结构的成员,以便提高访问...理解和掌握对齐规则能够帮助程序员编写出更高效、更稳定的代码,尤其是在处理大型数据结构和跨平台项目时。
内存对齐是C语言中一个重要的...在实际编程中,合理地利用内存对齐规则,能够帮助我们解决各种内存管理和数据结构设计中的难题,提高代码的质量和可维护性。希望本文能帮助读者更好地理解和掌握C语言中的内存对齐技术。
本文将深入探讨C语言中的内存对齐概念、原因、规则以及如何进行内存对齐。 内存对齐的基本原理是,每个数据类型都有一个对齐值,也称为对齐因子或对齐约束,表示该类型数据应该在内存中开始的位置必须是其对齐值的...
### C++内存对齐原理与实践 #### 一、引言 在C++编程中,内存对齐(Memory Alignment)是一项重要的技术,它涉及到如何在内存中存储数据以提高程序的运行效率。良好的内存对齐不仅可以减少数据访问的时间,还可以...