#pragma pack(n)解析

原理简析:

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
     n合法的取值范围是:1、2、4、8、16、32……

指令详解:

参考简书原文:http://www.jianshu.com/p/90a6eef329ec

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值