类型alignment

http://chuansu.iteye.com/blog/1487350

暂时先不总结了。。。只给出MSDN那个例子:

struct x_
{
   char a;     // 1 byte
   int b;      // 4 bytes
   short c;    // 2 bytes
   char d;     // 1 byte
} MyStruct;

sizeof(MyStruct)的结果是12,因为a和b中间会空出3B,为了使b的4B在一个连续处理周期空间上,d后面会甩出1B的空位,为了使整个MyStruct对象可以被4整除。这都是建立在32位机器的基础上,即每个周期处理32位数据=4B。

//===============================

相关地,说说GNU C中的__attribute__机制。

转自http://blog.csdn.net/wzhwho/article/details/5517887

__attribute__ 语法格式为:__attribute__ ((attribute-list))
关键字__attribute__ 也可以对结构体(struct )或共用体(union )进行属性设置。大致有六个参数值可以被设定,即:aligned, packed, transparent_union, unused, deprecated  may_alias 。

着重说下aligned。

该属性设定一个指定大小的对齐(alignment)格式(以字节为单位),例如:

struct S {
  short b[3];
} __attribute__ ((aligned (8)));

typedef int int32_t __attribute__ ((aligned (8)));

该声明将强制编译器确保(尽它所能)变量类型为struct S 或者int32_t 的变量在分配空间时采用8字节对齐方式。

如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。如果aligned 后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:

struct S {
short b[3];
} __attribute__ ((aligned));

这里,如果sizeof(short )的大小为2(byte ),那么S的大小就为6。取一个2的次方值,使得该值大于等于6,则该值为8,所以编译器将设置S类型的对齐方式为8 字节。

需要注意的是,attribute 属性的效力与你的连接器也有关,如果你的连接器最大只支持16 字节对齐,那么你此时定义32 字节对齐也是无济于事的。

来个例子:

struct p
{
  int a;
  char b;
  short c;
}__attribute__((aligned(4))) pp;

struct m
{
  char a;
  int b;
  short c;
}__attribute__((aligned(4))) mm;

struct o
{
  int a;
  char b;
  short c;
}oo;

struct x
{
  
int a;
  char b;
  
struct p px;
  short c;
}__attribute__((aligned(8))) xx;

int main()
{
  printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d/n",sizeof(int),sizeof(short),sizeof(char));
  printf("pp=%d,mm=%d /n", sizeof(pp),sizeof(mm));
  printf("oo=%d,xx=%d /n", sizeof(oo),sizeof(xx));
  return 0;
}

输出结果:

sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,mm=12
oo=8,xx=24

分析:

sizeof(pp): sizeof(a)+sizeof(b)+sizeof(c)=4+1+1=6<8 。所以sizeof(pp)=8

sizeof(mm): sizeof(a)+sizeof(b)+sizeof(c)=1+4+2=7。但是a后面需要用3个字节填充,但是b是4个字节,所以a占用4字节,b占用4个字节,而c又要占用 4个字节。所以sizeof(mm)=12

sizeof(oo): sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=7。因为默认是以4 字节对齐,所以sizeof(oo)=8

sizeof(xx): sizeof(a)+ sizeof(b)=4+1=5,sizeof(pp)=8; xx是采用8字节对齐的,所以要在a,b后面添3个空余字节,然后才能存储px,4+1+(3)+8+1=17。因为xx采用的对齐是8字节对齐,所以xx的大小必定是8的整数倍,即xx的大小是一个比17大又是8的倍数的一个最小值,由此得到17<24,所以sizeof(xx)=24。

//==================================

来看看类

在没有虚拟函数的情况下。对象的内存布局比较简单。看下面的类:

class A
{
public:
  int m_a;
  int m_b;
private:
  int m_c;
public:
  int GetValue(){ return m_c; }
};

  实例化一个A的对象a,通过sizeof(a)我们知道这个对象的大小是12字节(3*4)。那么在这个12字节大小的内存空间,对象是怎样布局它的成员变量的呢?很简单,就是按成员变量的定义顺序来安排成员变量在对象内存中的位置。使用(int*)&a可以得到对象a的地址,这个地址就是对象内存空间的首地址,这个地址也就是成员变量m_a的地址。怎样得到m_b的地址呢?(int*)&a+1就行了。同理(int*)&a+2就是m_c的地址了。看到这里你会突然想到什么呢?没错,我们居然可以访问到对象的私有成员。使用*((int*)&a+2) = 4可以为m_c赋值。虽然这很不符合规矩且破坏了面向对象中的封装,不过这可以帮助我们理解对象的内存布局,从这里也可以感受到C++强大的灵活性。成员函数的地址并不是在对象的内存空间的,它和其它类外的函数一样,编译器会安排它们到某个内存空间。 

  下面再说说内存对齐,看下面的类:

class B
{
public:
  int m_a;
  short int m_b;
  double m_c;
private:
  int m_d;
public:
  int GetValue(){ return m_c; }
};

  大家可以猜一下类B对象的大小。sizeof(B)是24。在编译器的结构成员对齐设置为默认的情况下,分配给各个成员变量的内存大小是向占最大空间的成员变量对齐的(这里我不敢肯定,还没看到权威的说法)。在B类中,首先为m_a分配空间,编译器一次为m_a分配8个字节(与最大成员m_c对齐),实际上m_a只占4个字节,还有4个字节多。接着编译器为m_b分配空间,经检查,m_b只占2字节,刚好前面还有4个字节多,所以m_b就放在前面多出的那4个字节空间中,现在已经为m_a和m_b分配了空间,但是m_a加上m_b也就只有6字节,还有2个字节多。如果下面分配的变量刚好是2字节的话,那就刚刚好装满8个字节,没有浪费空间,可是下面是要为double类型的变量分配空间,m_c占了8个字节,显然2个字节是装不下的,因此编译器再为m_c分配了8个字节的空间,刚装满。接下来又为m_d分配空间,根据之前的规则,编译器分配给m_d的空间也是8字节。这样看来,编译器总共为B的对象分配了8+8+8=24字节的空间。可能你觉得编译器这样做是浪费内存空间,但实际上这样做用空间效率来换时间效率。如果你还是觉得空间比较重要,那么你可以通过设置编译属性或使用编译器指令#pragma来指定编译器所做的对齐方式,例如语句:#pragma pack(1)就是设置向1字节对齐。这时使用sizeof(B)得出的结果就是18了。

  现在来看有虚拟函数的情况,看下面的类:

class C
{
public:
  int m_a;
  int m_b;
private:
  
int m_c;

private:
  virtual int GetValue(){ cout<<”I got it”<<endl; return 0; }
};

  在编译器的结构成员对齐设置为默认的情况下,sizeof(C)的值是16;因为编译器为我们多产生了一个指向虚函数表(VTABLE)的指针(__vfptr)。这个很容易理解。现在有一个任务,就是想办法调用类C的私有函数GetValue()。为了调用这个私有函数,我们要想办法得到它的入口地址。于是我们会想到先定义一个函数指针,它将会用来保存这个入口地址,例如

typedef int (*FUNC)();
FUNC pFunc;//产生一个函数指针。

  接下来就是把GetValue的入口地址给找出来。我们想到虚函数是放在VTABLE中,那么我们就要想办法得到指向VTABLE的指针__vfptr。还好,__vfptr就在对象内存中首地址,它比m_a还要前,先实例化C的一个对象objc,我们使用(int*)&objc就可以得到一个指向__vfptr的指针了。然后(int*)*(int*)&objc就得到了__vfptr,接着我们就可以使用__vfptr来访问VTABLE中所保存的函数入口地址了。指针再深入(int*)*(int*)*(int*)&objc,这时候我们就得到了VTABLE中第一格(其实还有第0格,用来保存类型信息的)的函数地址,也就是GetValue()的入口地址。

  最后,讲讲在有__vfptr的情况下的内存布局。在编译器的结构成员对齐设置为默认的情况下,__vfptr似乎很霸道,它所占有的空间不能和下一个成员变量共享。前面说过,当为一个成员变量分配空间的时候,编译器会检查分配给之前那个成员变量的空间是否还有剩,如果有且可以容纳的下当前的成员变量,那么编译器就会把当前成员变量装进之前多出来的空间中。但是编译器对__vfptr所处在的空间处理就不一样。如果最大成员变量的大小为8,那么编译器首先为__vfptr分配8个字节空间,还有4个多出来。但是这多出来的4个字节不会和下一个成员变量分享,无论下一个成员变量是多大,编译器都是重新为它分配空间。不过一使用#pragma pack(1),__vfptr就没有这种特权了。

转载于:https://www.cnblogs.com/RoyCNNK/articles/3001722.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值