结构体中的内存分配--struct dirent中引起的问题

在学习linux中posix api时,遇到了一个目录文件下面一条目录信息的结构体

struct dirent
{
     long d_ino;
     _kernel_off_t d_off;
     unsigned short d_reclen;
     unsigned char  d_type;         //在有些系统中是没有这个成员的,比如本人使用的centos 6  
     char d_name[256]; 
}

   我不明白这个地方的d_reclen是什么东东,在网上查了一下,有人说是strlen(d_name),实际上是不对的。严格的说,这个成员是这个结构体除了d_name以外的成员+d_name中实际所占的char个数组成的结构体的长度!所以常见的值有16,20,24等等。这个会在最后解释。这就涉及到结构体成员在内存中分配的方式。记得以前专门研究过这个,但是没有整理,现在忘了一些细节。为了搞清楚这个问题,我又重新的查看了一遍网上关于结构体内存分配的一些说法,发现还是是错误百出!为了方便自己查询,于是写了这个扎记。

  首先要说的几点是关于概念性的东西,现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始。但是,计算机在存储数据的时候,为了访问效率的考虑,在访问特定变量的时候经常在特定的内存访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。(参考百度百科)

  如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。如果机器的整型值长度为4个字节,假设一个整型变量的地址为0x00000008,那它就是自然对齐的。假设上面整型变量的地址不是自然对齐,比如为0x00000006,则CPU如果取它的值的话需要 访问两次内存,第一次取从0x00000006-0x00000007的两个字节,第二次取从0x00000008-0x00000009的两个字节。t然后组合得到所要的数据。如果变量在0x00000005地址上的话则要访问三次内存,第一次为一个字节,第二次为两个字节,第三次为一个字节,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对 齐的数据会发生错误,举个例:
  
  char ch[4];
  char *p = &ch[1];  //这个地方取第一个的原因是保证第二个元素的地址肯定不是4的倍数。
  int i = *(int *)p;
  运行时会报segment error,而在x86上就不会出现错误,只是效率下降。(参考一篇网文)

  那么实际在考虑到程序编写过程中的对齐,还需要引出一个重要的概念:对齐参数。对于不同的系统,默认的对齐参数是不一样的,Win32平台下的微软VC编译器在默认情况下采用如下基本数据类型T的大小,即sizeof(T)。Linux下的GCC奉行的是另外一套规则:任何2字节大小(包括单字节)的数据类型(比如short)的对齐参数就是sizeof(T),而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。了解了对齐参数之后,我们对齐的规则是:每个成员按其类型的大小和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的总长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

文中所有例子都使用gcc编译,版本为gcc version 4.4.6 20120305 (Red Hat 4.4.6-4) (GCC) 。默认的对齐参数在前文已经提到,不再赘述。

[pengliang@www 2013-06-05]$ ./structlength
sizeof(int):4

sizeof(long):4
sizeof(char):1
sizeof(short):2
sizeof(double):8

结构体内存分配例子一:

1 struct test1
2 {
3        char a;
4        int b;
5        char c;   
6 }

测试可知,

sizeof(struct test1)=12;

 offsetof(struct test1,a)=0;

offsetof(struct test1,b)=4;

offsetof(struct test1,c)=8;  注:offsetof是用来判断一个成员在内存中分配的位置距离整个结构体开始位置的偏移量

结构体第一个成员为char类型,它的对齐参数为:1,它可以放到任何位置(任何数都可以是1的整数倍),占用一个字节。

b是整型,它的对齐参数是4,所以它需要放到4的整数倍的位置。因为起始的地方肯定是可以被4整除的,所以最近的一个4的整数是a的地址+4,所以需要在a后填充3个空白字节,然后放b。

c同a,占用一个字节。所以这个结构体目前所占的长度为:1+3+4+1=9。根据前面的说法,结构体的总长度必须为使用过的所以的对齐参数的整数倍,其实就是max(所有的对齐参数),在本例子中就是4,那么这个结构体的总长度必须为4的整数倍,9最近的就是12,所以在c后面补充3个空白字符!!!这个结构体的实际利用率只有50%。

是不是很简单呢?在网上很多帖子中,有人说结构体的总长度必须是结构体中所有类型的最长大小的整数倍。其实在gcc这就是不对的,自己思考为什么。想想这个例子

struct t{      int a;double b;}在gcc环境下是12   ---------12%8!=0

结构体内存分配例子二:

1 strcut test2
2 {
3      int a ;
4      char b[9];
5      char c;   
6 }

测试可知:

[pengliang@www 2013-06-05]$ ./structlength
sizeof(struct test2)=16
offsetof(struct test2)=0
offsetof(struct test2)=4
offsetof(struct test2)=13
a占四个字节,b是一个数组,对于结构体中的数组,只需要查看它中的类型。因为b数组中存放的是char类型,char的对齐参数是1,所以这个数组中元素不管前面存放的是什么,b中第一个元素总是挨着前面那个元素。b占9个字节。同理:c是char 类型,紧挨在数组后面。4+9+1=14。前面使用到的所以对齐参数有:4和1,所以max(4,1)=4,结构体的总长度是4的倍数。c后面需要填充2个字节,为16

结构提内存分配例子三:

1 struct test3
2 {
3         char a;
4         short b[5];
5         char c;
6 }

 测试结果:

[pengliang@www 2013-06-05]$ ./structlength
sizeof(struct test3)=14
offsetof(struct test3,a)=0
offsetof(struct test3,b)=2
offsetof(struct test3,c)=12

char的对齐参数为1,short的对齐参数为2。a占1个字节,因为short对齐参数为2,所以a后面填充一个字节,然后开始b数组,b一共10个字节。c紧跟b。所以暂时的长度为:1+1+10+1=13。

使用的所以对齐参数最大的为2,所以总长度要为2的倍数,c后面再添加一个空白字符。总共14字节。

经过上面三个例子,我们应该对基本数据类型的结构体的内存分配有了一定的了解,那么如果在结构体中还存在结构体成员呢?

我们有如下的处理规律:

  数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 (其实前面已经证实了这个情况)
  联合 :按其包含的成员中对齐参数最大的参数进行对齐。
  结构体: 结构体中每个数据类型都要对齐,且结构体的对齐参数是结构体成员中对齐参数最大的。

来一个例子:

 1 struct s1
 2 {
 3         short a;
 4         long b;      
 5 }
 6 
 7 struct s2
 8 {
 9         char c;
10         struct s1 struct1;
11         short e;   
12 }

输出的结果为:

[pengliang@www 2013-06-05]$ ./structtest
sizeof(struct s2) :16
offsetof(struct s2,c) : 0
offsetof(struct s2,struct1) :4
offsetof(struct s2,e) :12

在s2中,第一个成员为c,占用一个字节。然后一个成员为struct1,那么struct1的对齐参数为:max(2,4)=4{取这个结构体中所有的对齐参数的最大值},所以需要在c后面填充3个字节。然后开始放struct1,因为struct1是结构体,所以按照结构体的分配方式:a占两个字节,由于long的对齐参数为4,所以a后填充两个字节,然后开始摆放b。b占四个字节。e占两个字节。所以暂时的总长度为1+3+2+2+4+2=14。在结构体s2中使用的所以的对齐参数有:1 、4 、2,所以s2的长度为4的倍数。在e后面添加两个字节。14+2=16;

再来一个例子:

1 struct s1
2 {
    char a;
3   double b; 4 }
struct s2
{
  int c;
    struct s1 struct1[3];
   short d; }

 [pengliang@www 2013-06-05]$ ./structtest
sizeof(struct s2) :44
offsetof(struct s2,c) : 0
offsetof(struct s2,struct1) :4
offsetof(struct s2,d) :40
这个结果大家自己分析!!

联合(共同)体比较简单,我这里只举两个例子:

1   union test
2    {
3       char a;
4       int c;
5       double d;
6    }; 

sizeof(union test):8(取最长的嘛,肯定是double啊)

1  struct s
2   {
3      double e;
4     union test t1;
5      char f;
6       union test t2;
7   }

[pengliang@www 2013-06-05]$ ./uniontest
sizeof(struct s):28
offsetof(struct s,e):0
offsetof(struct s,t1):8
offsetof(struct s,f):16
offsetof(struct s,t2):20

好了,这些内容就说到这里了,可能很多人为什么一样的代码在自己的机器上面结果不要呢?这个前面我就说了,对齐的时候每一个编译器的默认对齐参数不一样,在vc中全部都是sizeof(type),所以double的默认对齐参数是8.但是在gcc中,类型大于等于4的类型全部是4,这就是区别。另外,我们可以通过这个指令来调整这个默认值:

#pragma pack(n)     //n为对齐的默认字节数

#pragma pack(1)
1
struct test3 2 { 3 char a; 4 short b[5]; 5 char c; 6 }

[pengliang@www 2013-06-05]$ ./uniontest
sizeof(struct test3):12
offsetof(struct test3,a):0
offsetof(struct test3,b):1
offsetof(struct test3,c):11
由此可以看出,如果这个默认对齐参数越小,越有利于节约内存,但是可能会带来访问的效率问题。当n=1时,就是每一个变量都是一个挨着一个存放的!!!当默认对齐参数越大,占的空间就越多,主要是在8字的变量
较多的情况下,但是这样的访问效率较好。这就是一个时间与空间的博弈!!
1  #pragma pack(8)
2   
3  struct s
4  {   
5    char a;
6     
7    short b[5];
8    double c;
9 }

这个sizeof(s)在vc下编译,结果是24,a占一个字节,补一个空,b占10个字节,补4个字节(因为b要从8整数倍开始),最后8个字节的c。1+1+10+4+8=24.

但是这个#pragma pack(1)在gcc下面好使,但是#pragma pack(8)却不好使!!!

查看论坛得知:默认的对齐是按照 int 型(4字节)对齐,如果指定 #pragma pack(N) 中的 N 的话,N 不能大于默认对齐指定的长度,即如果默认对齐是 4 的话,N的取值可以是 1、2、4,超过 4 之后作为 4 处理。在 Windows 等系统上似乎没有这个限制。-------http://bbs.chinaunix.net/thread-636323-1-1.html

那么怎么在gcc中设置一个结构体的存储对齐参数为8呢??

查找之后发现,在定义后面加上这个gcc特有的_attribute__机制即可:

1  #pragma pack(8)
2   
3  struct s
4  {   
5    char a;
6     
7    short b[5];
8    double c;
9 }__attribute__ ((aligned (8)));这样就能得到和

[pengliang@www 2013-06-05]$ ./uniontest
sizeof(struct test3):24
offsetof(struct test3,a):0
offsetof(struct test3,b):2
offsetof(struct test3,c):12
这样的话,在gcc中,这个存储结构也占用了24个字节。但是还是有点小问题:在vc中abc的偏移分别是:0、2、16.而在gcc中偏移量是0、2、12,呵呵,到这里,你应该能够分辨为什么会有这种区别了吧??

最后,好像扯得太远了,哈哈,我原来的目的是想知道dirent结构中d_reclen的内容是什么,现在看来这是so easy!!!!!!

4(d_ino)+4(d_off)+2(d_reclen)+1(type)+1(补位)+strlen(d_name)  最后通过对齐参数4来修正总体的长度!

oh,my god,一天过了.........汗

本文除了前面一点点是借鉴了别人的内容外,后面的例子都是自己写的,如果你感觉还可以的话,就给个鼓励吧!

 

转载于:https://www.cnblogs.com/CSU-PL/archive/2013/06/05/3118716.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值