原理简析:
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
n合法的取值范围是:1、2、4、8、16、32……
指令详解:
1.显示当前内存对齐的字节数#pragma pack(show)
2.#pragma pack(push [, identifier] [, n])
单纯使用#pragma pack(push)会将当前的对齐字节数压入栈顶,并设置这个值为新的对齐字节数, 就是说不会改变这个值。
而使用#pragma pack(push, n) 会将当前的对齐字节数压入栈顶,并设置n为新的对齐字节数。
再就是这个#pragma pack(push, identifier [, n])会在上面的操作基础上为这个对齐字节数附上一个标识符, 这里注意这个标识符只能以(
、、字母)开始,标识符中可以有(
、_、字母、数字),并且标识符不能是关键字(push, pop可以作为标识符)。
对齐规则:
附表:
附说明:
(1) 将结构体内所有数据成员的长度值相加,记为sum_a;
(2) 将各数据成员内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是【该数据成员所占内存】与【#pragma pack指定的数值】中的较小者。
(3) 将和sum_b向结构体模数对齐,该模数是【#pragma pack指定的数值】、【未指定#pragma pack时,系统默认的对齐模数8字节】和【结构体内部最大的基本数据类型成员】长度中数值较小者。结构体的长度应该是该模数的整数倍。
ps:以下程序均是在Codeblock 32bit环境下编译验证的
一、单个结构体
#include <stdio.h>
#pragma pack(4)
struct A
{
char c; //1
short sh; //011, min(4,sizeof(short))=2,对齐模数是2,存放的起始位置是2的倍数
int a; //1111, min(4,sizeof(int))=4,对齐模数是2,存放的起始位置是4的倍数
char f; //1, min(4,sizeof(char))=1,对齐模数是1,存放的起始位置是1的倍数
int *p; //0001111, min(4,sizeof(int*))=4对齐模数是4,存放的起始位置是4的倍数
char *s; //1111, min(4,sizeof(char*))=4对齐模数是4,存放的起始位置是4的倍数
double d; //1111 1111, min(4,sizeof(double))=4对齐模数是4,存放的起始位置是4的倍数
char cc; //1
}; //总长度:1011 1111 1000 1111 1111 1111 1111 1 length=29,
//但是最终的长度应该是4的倍数,所以是
//最终长度:1011 1111 1000 1111 1111 1111 1111 1000 length=32
int main()
{
printf("%d\n",sizeof(A)); //32
return 0;
}
二、结构体嵌套
#include <iostream>
#include <stdlib.h>
using namespace std;
#pragma pack (2)
typedef struct {
int a; //1111,
char b; //1,min(2,sizeof(char))=1
double c;//0 1111 1111,min(2,sizeof(double))=2
}A; //总长度:1111 1011 1111 11
//最终 :1111 1011 1111 11 length=14(为2的倍数)
struct B
{
int a; //1111,
A b; //1111 1011 1111 11
}; //总长度:1111 1111 1011 1111 11
//最终 :1111 1111 1011 1111 11 length=18(为2的倍数)
int main()
{
cout << sizeof(A)<< endl;
cout << sizeof(B) << endl;
return 0;
}
#include <iostream>
#include <stdlib.h>
using namespace std;
#pragma pack (4)
typedef struct {
int a; //1111,
char b; //1,min(4,sizeof(char))=1
double c;//000 1111 1111,min(4,sizeof(double))=4
}A; //总长度:1111 1000 1111 1111
//最终 :1111 1000 1111 1111 length=16(为4的倍数)
struct B
{
int a; //1111,
A b; //1111 1000 1111 1111,min(4,sizeof(A))=4
}; //总长度:1111 1111 1000 1111 1111
//最终 :1111 1111 1000 1111 1111 length=20(为4的倍数)
int main()
{
cout << sizeof(A)<< endl;
cout << sizeof(B) << endl;
return 0;
}
三、类
空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。非空类占用内存为0。
(一)类内部的成员变量:
*普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
*static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
(二)类内部的成员函数:
*普通函数:不占用内存。
*虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
//普通单个类
#include<iostream>
#include<stdio.h>
#pragma pack(4)
using namespace std;
class CBase1
{
private:
char c;//1
short sh;//011
int a;//1111
public:
void fOut(){ cout << "hello" << endl; } //不占内存
};
int main(){
printf("=%d\n",sizeof(CBase1)); //8
return 0;
}
//类中包含虚函数
#include<iostream>
#include<stdio.h>
#pragma pack(4)
using namespace std;
class CBase1
{
private:
char c;//1
short sh;//011
int a;//1111
public:
virtual void fOut(){ cout << "hello" << endl; } //虚函数占4个字节
virtual void fOut2(){ cout << "hello" << endl; } //不管几个虚函数,都只是占用4个字节
};
int main(){
printf("=%d\n",sizeof(CBase1)); //8+4=12
return 0;
}
//子类及子类包含虚函数
#include<iostream>
#include<stdio.h>
#pragma pack(4)
using namespace std;
class CBase1
{
private:
char c;//1
short sh;//011
int a;//1111
public:
virtual void fOut(){ cout << "hello" << endl; } //虚函数占4个字节
virtual void fOut2(){ cout << "hello" << endl; } //不管几个虚函数,都只是占用4个字节
};
class cDerive :public CBase1
{
private :
int n; //1111,
char c;//1000,
public:
virtual void fPut(){ cout << "virtual 2"; } //子类与父类共享同一个虚函数指针,占用内存为0
};
int main(){
printf("=%d\n",sizeof(cDerive)); //12+8=20
return 0;
}
四、联合体
在C Programming Language 一书中对于联合体是这么描述的:
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最”宽”的成员;
4)其对齐方式要适合其中所有的成员;
#include <iostream>
#include <stdio.h>
using namespace std;
#pragma pack(4)
union U1
{
char s[9]; //1111 1111 1000,取联合体中最大长度,并且补齐到4的倍数
int n;
double d;
};
union U2
{
char s[5];
int n;
double d; //1111 1111,取联合体中最大长度,并且补齐到4的倍数
};
int main(int argc, char *argv[])
{
U1 u1;
U2 u2;
printf("%d\n",sizeof(u1)); //12
printf("%d\n",sizeof(u2)); //8
return 0;
}
参考:
http://blog.csdn.net/lime1991/article/details/44536343
http://www.cppblog.com/deercoder/archive/2011/03/13/141717.html
http://bdbaishitong.blog.163.com/blog/static/2014930802013516103321644/