结构体共用体内存分配问题

前言:最近在看这方面的东西,有所感触,所以写写,如果有不正确或者不完善的地方,还望不吝赐教。

一、背景知识

1.什么是字节对其齐?

现代计算机中内存空间基本都是按字节(Byte)为单位进行划分的,从理论上看,对任何数据类型的存储和访问都可以从任意地址开始,但实际实现中,为了访问速率的提升,通常按特定的规则来排列和存储数据,读取时在从相应的地址读取。而这其中的规则就是对齐。

2.字节对其有什么好处?

要理解字节对其的好处,就得先了解计算机的寻址方式。比如有些平台每次读都是从偶地址开始,那么对于一个int型(假设为32位系统),如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。在这种情况下,字节对其就能保证每个int的存储都是从偶地址开始,亦即每次读取一个int都只需要一个读周期。

二、字节对齐

不同操作系统里默认的对其方式存在差别,不过我见过貌似都是8 Byte对齐的,可以通过#pragma pack(n)来设定按n字节对其。

例:

#pragma pack(2)                                                      #pragma pack(4)

struct B                        struct  C

{                            {

   char  b;                        char  b;

           int    a;                         int    a;

           short  c;                         short  c;

}                           }

  针对上面两个列子,

对于结构体B:

  数据成员b自身的对其值为1,指定对其值为2,故有效对其值为1(min(2,1)),假设存放地址为0x0000;

  数据成员a自身对其值为4,指定对其值为2,故有效对其值为2(min(2,4)),存放地址的起始地址%2==0,故存放地址应该是:0x0002-0x0005

  数据成员c自身对其值为2,指定对其值为2,故有效对其值为2(min(2,2)),存放地址的起始地址%2==0,故存放地址应该是:0x0006-0x0007

  总的内存消耗为8。

  结构体B自身对其值为4(max(1,2,4)),指定对其值为2,故结构体B有效对其值为2(min(2,4)),总内存消耗8%2==0,不需要补充。

  对于结构体C:

  数据成员b自身的对其值为1,指定对其值为4,故有效对其值为1(min(4,1)),假设存放地址为0x0000;

  数据成员a自身对其值为4,指定对其值为4,故有效对其值为4(min(4,4)),存放地址的起始地址%4==0,故存放地址应该是:0x0004-0x0007

  数据成员c自身对其值为2,指定对其值为4,故有效对其值为2(min(2,4)),存放地址的起始地址%2==0,故存放地址应该是:0x0008-0x0009

  总的内存消耗为10。

  结构体B自身对其值为4(max(1,2,4)),指定对其值为4,故结构体B有效对其值为4(min(4,4)),总内存消耗10%4!=0,需要最少补充2个字节(由此可看出字对齐的弊端——内存浪费,但就换取的效率而言,这还是值得的),故总的内存消耗为12字节。

  故:sizeof(B)=8,  sizeof(C)=12.

三、内存分配

1. 栈上内存分配(静态内存分配)

由于计算机中用于静态内存分配的地址空间有限(一般为1M或者2M),所以栈上地址分配是从高地址向低地址分配(但在具体的某个结构内部是从低到高)。下面通过共用体和结构体进行说明。

union:

union data1                       
{
double d;
int i;
char c1;
char c2[9];
};
int main(int argc,char **argv)
{
data1 d1;
d1.c2[0] = 10;
d1.c2[1] = 1;
d1.c2[2] = 1;
d1.c2[3] = 10;
cout <<"sizeof short union data1: "<<sizeof(data1)<<endl;
cout <<"sizeof short int: "<<sizeof(int)<<endl;
cout << d1.d << endl;
cout << d1.i << endl;
cout << d1.c1 << endl;
cout << (size_t)&d1 << endl;
cout << (size_t)&d1.i << endl;
cout << (size_t)&d1.c1 << endl;
cout << (size_t)&d1.c2<< endl;
cout << (size_t)&d1.c2[0]<< endl;
cout << (size_t)&d1.c2[1]<< endl;
cout << (size_t)&d1.c2[2]<< endl;
cout << (size_t)&d1.c2[3]<< endl;
cout <<endl;
system("pause");
return 0; 
}

结果为:

说明:所设对齐方式为#pragma  pack(8);对于共用体,内存地址从起始地址往高地址分配,也就是在内部是从低到高进行分配的。(d显示不正常是因为高32位没有赋值,c1无显示的原因不太清楚,望高人人指点)。

struct:

struct data1
{
    double d;
    int i;
    char c1;
    char c2[9];
};
int main(int argc,char **argv)
{
data1 d1;
cout <<"sizeof struct data1: "<<sizeof(d1)<<endl;
cout << (size_t)&d1 << endl;
cout << (size_t)&d1.d << endl;
cout << (size_t)&d1.i << endl;
cout << (size_t)&d1.c1 << endl;
cout << (size_t)&d1.c2 << endl;
cout << (size_t)&d1.c2[0] << endl;
cout << (size_t)&d1.c2[1] << endl;
cout <<endl;
system("pause");
return 0; 
}

结果:

2. 堆上内存分配(动态内存分配)

计算机中动态内存的分配是在堆上进行(这个有没有大小限制以及上限为多少不太清楚),地址分配从低地址往高地址分配。

四、数据存储模式

1. 大端模式(big-endian)

数据的高字节存放在内存的低位,低字节存放在内存的高位(比较符合视觉效果);

2. 小端模式(little-endian)

数据的高字节存放在内存的高位,低字节存放在数组的低位。

比如16bit宽的数0x1234CPU内存中的存放方式(假设从地址0x4000开始存放)为:

Little-endian模式:

内存地址

0x4000

0x4001

存放内容

0x34

0x12

Big-endian模式:

内存地址

0x4000

0x4001

存放内容

0x12

0x34

测试CPU存储模式的方法: 利用共用体的特性
int checkCPU()
{
    union w
    {
      int a;     
      char b;
    } c;
    c.a = 1;
    return (c.b == 1);
}
原理:a应该占4个字将1赋给a,这样a只用到一个字节如果是小端模式,则应该放在第一个字节中,此时b对应的值也就是1。
当然还有其他方法也能实现测试,像linux内核就是通过一个简单的宏来实现的。具体参见博客: http://blog.chinaunix.net/uid-25367385-id-188322.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值