C语言结构体的大小——内存对齐和位域的使用

来源:互联网 发布:手机查询淘宝信誉级别 编辑:程序博客网 时间:2024/04/29 16:33

C语言结构体对齐

C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?结合网上的资料和自己的编程实践,总结如下。

首先考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡对齐节省了时间。假设一个体系结构的字长w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。

既然内存对齐是必要的,那么结构体到底是怎样对齐的呢?

       (在没有定义#pragma pack宏的情况下):

       原则1、数据成员对齐规则:结构struct或联合union)的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员按其类型大小和默认对齐参数(32位系统通常默认按4字节对齐)中较小的一个对齐每个成员的起始地址%每个成员的自身对齐值=0,否则补空直至满足条件比如对于char型数据,其自身对齐值为1,对于short型为2对于int型为4double32位机大小8字节、系统默认对齐方式是4字节,取其较小者,所以其对齐值是4

       原则2结构体作为成员:如果一个结构里有结构体成员,则按照其成员中自身对齐值最大的那个值对齐。struct a里存有struct bb里有charintdouble等元素,那b应该从4的整数倍开始存储。)

       原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须为所用过的最大对齐参数的整数倍,不够就补空字节。比如在32位系统中最大对齐参数4,如果一个结构中有类型为intlongfloatdouble等长度等于或大于4的成员,则其总大小必须是4的整数倍。


       这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

       例1

typedef struct _A {
                     short a1;
                     short a2;
                     short a3;
                    }A;

typedef struct _B{
                   long a1;
                   short a2;
                  }B;

       sizeof(A) = 6; 这个很好理解,三个short对齐方式都是2

       sizeof(B) = 8; 这个是不是比预想的大2个字节?long4short2,整个为8,因为原则3

       例2

typedef struct _A{
                    int a;
                    char b;
                    short c;
                    }A;

typedef struct _B{
                   char b;
                   int a;
                   short c;
                    }B;

       sizeof(A) = 8; int4char1short2,这里用到了原则1和原则3

       sizeof(B) = 12; 是否超出预想范围?char1int4short2,怎么会是12?还是原则1和原则3

       深究一下,为什么是这样,我们可以看看内存里的布局情况。

                        a           b         c
       A的内存布局:1111,    1*,      11

                        b           a           c
       B的内存布局:1***,     1111,   11**

       其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为cshort类型,其起始位置要为2的倍数,就是原则1c的后面没有补充,因为bc正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。

       B中,bchar1b后面补充了3个字节,因为aint4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数。如果c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

       再看一个结构中含有结构成员的例子:

       例3typedef struct _A{
                     int a;
                     double b;
                     float c;
                    }A;

                typedef struct _B{
                     char e[2];
                     int f;
                     double g; 
                     short h;
                     A i;
                    }B;

       sizeof(A) = 16这个比较好理解,int4double8float4double对齐方式取了较小的默认方式,按4字节对齐。所以整个A的大小为16

       sizeof(B) = 36看看B的内存布局。

                       e      f           g         h            i
       B的内存布局:11* *,   1111,   11111111, 11 * * ,  1111, 11111111, 1111


       i其实就是A的内存布局。i的起始位置要为4的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。

       以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,那么对齐方式就要按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;

       有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗?!

                       a          b           c
       A的内存布局:1111,     11111111,   1111

                      e    f        g              h      i
       B的内存布局:11, 1111, 1111111, 11 , 1111, 11111111, 1111

       那#pragma pack(2)的结果又是多少呢?#pragma pack(8)呢?留给大家自己思考吧,相信没有问题。

       还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取sizeof值。C99规定intunsigned intbool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

       使用位域的主要目的是压缩存储,其大致规则为:
       1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
       2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
       3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,gcc通常采用压缩的方式
       4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
       5) 整个结构体的总大小为对齐方式的整数倍。对齐方式去最宽基本类型成员大小和默认对齐方式的较小者。

        还是让我们来看看例子。

       例4typedef struct _A{
                     char f1 : 3;
                     char f2 : 4;
                     char f3 : 5;
                     }A;

                     a       b       c
       A的内存布局:111,    1111 *,   11111 * * *

       位域类型为char,第1个字节仅能容纳下f1f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2

       例5typedef struct _B{
                    char f1 : 3;
                    short f2 : 4;
                    char f3 : 5;
                    }B;

       由于相邻位域类型不同,在VC6中其sizeof6,在gcc中为2

如果是

typedef struct _B{
                    char f1 : 3;
                    int f2 : 4;
                    char f3 : 5;
                    }B;
gcc中大小则是4,它们仍然被压缩在了一起,但是长度必须是基本类型int型大小的整数倍;

如果是

typedef struct _B{
                    char f1 : 3;
                    long long  f2 : 4;
                    char f3 : 5;
                    }B;

32位系统,gcc编译后大小仍然是4它们仍然被压缩在了一起。虽然long long大小是8,但默认对齐参数是4


       例6struct C{
                     char f1 : 3;
                     char f2;
                     char f3 : 5;
                    };

       非位域字段穿插在其中,不会产生压缩,在VC6gcc中得到的大小均为3
       
       最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 脸书账号被禁用怎么办 文档变成了d盘怎么办? 派派背包满了怎么办 黑裤子掉颜色了怎么办 快递被别人领走怎么办 绒面靴子长霉了怎么办 新买的鞋子开胶怎么办 白色的皮鞋边发黄怎么办 新鞋大拇指顶脚怎么办 耐克标志开胶了怎么办 鞋开胶了怎么办不用胶 gta按home没反应怎么办 gta5线上车没了怎么办 吃了粘壳的鸡蛋怎么办 gta5短信删错了怎么办 电风扇2档3档开好关不管用怎么办 gta5把车替换了怎么办 gta5任务完成后卡了怎么办 gta5车被扣押了怎么办 侠盗猎车手5卡怎么办 英雄联盟转区后没法快捷施法怎么办 欠太多人的钱怎么办 我欠了很多钱怎么办 输了那么多钱我该怎么办 家里欠了钱我该怎么办 欠了好多钱我该怎么办 赌球输了好几千怎么办 欠信用卡的人死了怎么办 欠别人钱人死了怎么办 别人欠我钱人死了怎么办 美国生娃孩子怎么办医保 黑在美国病了怎么办 在外打工房租太贵怎么办 在外面打工房租租不起怎么办 买车型号错了怎么办 沃出行不退押金怎么办 钢铁雄心4人力0怎么办 钢铁雄心4没工厂怎么办 钢铁雄心4锁区怎么办 qq超市金币满了怎么办 旋转轮胎2车翻了怎么办