C语言编程基础-struct和union

最近的笔试题中做到了这样一个题目:

struct S
{
    char c;
    int a;
    struct S *p;
    union
    {
        short b;
        unsigned int d;
    };
};

该结构体编译到32位系统下的大小是()字节

下面就对结构体struct和共用体union大小一个简单的总结。
环境:Intel Core i5-4590,OS-64bit,CodeBlocks 16.01-32bit


1. struct

计算结构体的大小就是要看结构体是如何对齐的,结构体的对齐也可以说是内存对齐,为什么要进行内存对齐呢,主要是为了方便CPU寻址。

内存对齐关键有以下5点:

(1) 内存对齐与编译器设置有关,所以首先要搞清楚编译器这个默认值是多少;

(2) 如果不想按照编译器默认对齐的话,可以通过伪指令#pragma pack(n)指定按照n字节对齐;

(3) 结构体变量对齐遵循最小化长度规则。

(4) 结构体的大小确定:对齐后的长度必须是成员中最大的对齐参数的整数倍,最大对齐参数由(3)得到;

(5) 如果结构体A里面还有结构体B,则结构体B的对齐方式选取它里面最长的成员的对齐方式。

注:
最小化长度规则:每个结构体变量的对齐,如果对齐参数n大于该变量所占字节数m,则按照m对齐,也就是内存偏移后的地址是m的倍数,否则按照n对齐,即内存偏移后的地址是n的倍数。

所以结构体的对齐有这样的三步:
首先确定当前程序的对齐参数;接着计算每个结构体变量的大小和偏移量;最后计算结构体总大小。



默认对齐方式 + 指定对齐方式

(1) 默认对齐方式 - 自然对齐

在默认对齐方式下,结构体成员的内存分配满足下面三个条件

(1) 结构体的首地址就是结构体第一个成员的地址
(2) 结构体每个成员地址相对于结构体首地址的“偏移量”是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节;
(3) 结构体“总的大小”要是其成员中最大size的整数倍,如果不是编译器会在其末尾添加填充字节。
struct s1
{
    char a;
    int b;
    double c;
    char d;
};

struct s2
{
    char a;
    int b;
    double c;
};

输出结果:
s1结构体大小为24

a的偏移量为0
b的偏移量为4(按照默认对齐方式(2),编译器在成员a后边填充了3字节)
c的偏移量为8
d的偏移量为16
总长为24(按照默认对齐方式(3),最大成员c占用8字节,在结构体后边添加7字节)

s2结构体的大小为16


(2) 指定对齐方式

对于指定的对齐方式,其成员的地址偏移以及结构的总的大小按指定方式对齐

(1) 结构体第一个成员的地址和结构体的首地址相同
(2) 结构体每个成员的"地址偏移"需要满足最小化长度原则
    如果n大于结构体成员中最大成员的大小,则n不起作用,仍然按照默认方式对齐
(3) 结构体总的大小需要是n的整数倍,如果不是需要在结构体的末尾进行填充

可以通过下面的方法改变缺省的对齐方式

使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
使用伪指令#pragma pack (),取消自定义字节对齐方式。
#pragma pack (n)
struct aligntype
{
    char a;
    int b;
    char c;
};
#pragma pack ()

当n为4、8、16时,其对齐方式均一样,sizeof(aligntype)的结果都等于12。而当n为2时,其发挥了作用,使得sizeof(aligntype)的结果为8。

现仍以默认对齐方式中的结构体为例

#pragma pack(4)
struct s1
{
    char a;
    int b;
    double c;
    char d;
};

struct s2
{
    char a;
    int b;
    double c;
};
#pragma pack()

输出结果:
s1结构体大小为20

a的偏移量为0
b的偏移量为4(指定对齐方式,编译器在成员a后边填充了3字节)
c的偏移量为8
d的偏移量为16
总长为24(指定对齐方式,n=4小于最大成员c长度起作用,在结构体后边添加3字节)

s2结构体的大小为16

#pragma pack (2)    /*指定按2字节对齐*/
struct S
{
    char b;
    int a;
    short c;
};
#pragma pack ()     /*取消指定对齐,恢复缺省对齐*/

结构体地址/大小分析:
第一个变量b的自身对齐值为1,指定对齐值为2,假设S从0x0000开始,那么b存放在0x0000
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中
第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中
所以从0x0000到0x00007共八字节存放的是S的变量
又S的自身对齐值为4,所以S的有效对齐值为2(最小化长度规则)
又8%2=0,S只占用0x0000到0x0007的八个字节,所以sizeof(struct S) = 8.

注:
(1) 在使用#pragma pack(n)设定对齐方式一定要是2的整数幂,也就是(1,2,4,8,16,…),不然不起作用的,仍然按照默认方式对齐。
(2) 当结构体中有其他的结构体作为成员时,计算最大成员是不能把结构体成员作为一个整体来计算,要看其每个成员的大小,使用结构体中最长成员的对齐方式。这个将在下面结构体嵌套的例子中进行说明

  • 结构体嵌套
struct stu1
{
    short i;
   struct
  {
        char c;
        int j;
  }ss;
  int k;
};

结构体stu1的大小为16
结构体成员ss.c的偏移量应该是4,而不是 2。

#pragma pack(push)
#pragma pack(8)
struct s1
{
    int a;
    char b;
    int c[20];
    long l;
};
struct s2
{
    char a1;
    char a2;
    struct s1 t1;
    double b1;
};
#pragma pack(pop)

结构体s1的大小为92

a的偏移量为0
b的偏移量为4
c的偏移量为8(8大于int变量大小,以默认方式对齐;区分偏移量和成员大小)
l的偏移量为88

结构体s2的大小为104

a1的偏移量为0
a2的偏移量为1
结构体t1的偏移量为4(t1里面最长成员的“对齐方式”)
b1的偏移量为96


2. union

struct中的每个域在内存中都独立分配空间
union只分配最大域的空间,所有域共享这个空间

#include <stdio.h>

struct A
{
    char a;
    int b;
    double c;
};

union B
{
    char a;
    int b;
    double c;
};

int main()
{
    printf("%d\n", sizeof(struct A));
    printf("%d\n", sizeof(union B));
    return 0;
}

输出结果:
结构体A的大小为16
结构体B的大小为8

对于stuct对齐

(1) 先算对齐大小:对齐的大小也是取决于struct成员中字节对齐最大的那个
(2) 再算成员空间:然后根据每个成员的对齐大小对齐每个成员算出分配的空间
(3) 最后算出实际分配的空间,分配struct的实际大小不小于成员空间总和且满足是对齐大小的整数倍

对于union对齐

(1) 先算对齐大小:对齐的大小是取决于union成员中字节对齐最大的那个
(2) 再算实际分配的空间:分配给union的实际大小不能小于最大成员的大小,且要满足是对齐大小的整数倍。

  • 默认对齐
union U1
{
    char a[9];
    int b;
};

成员a是char数组,对齐大小为1字节,成员b是int,对齐大小为4字节,所以U1对齐大小为4字节;
分配给U1的实际大小既要是4字节的整数倍,又要不小于最大成员a的大小,即位4的整数倍又要大于9,所以实际分配的空间为12字节。
若将int改为double,则对齐为8,大小为16。

union U2
{
    U1 a;
    double b;
};

对比char,int和double,对齐是double的大小,为8,大小应当为是16。

  • 指定对齐
#pragma pack(4)     // pack(0)会采用默认的字节4
union U3
{
    char a[9];
    double b;
};

基本元素b的对齐由原来的8变为4,而a的对齐仍然是1。U3的对齐由8变为4,大小为12。

所以文章开头题目的答案为:16字节
结构体指针大小在32位系统下为4字节,64位系统下为8字节。



Acknowledgements:
http://www.cnblogs.com/wangguchangqing/p/4853438.html
http://www.cppblog.com/iuranus/archive/2009/01/06/71388.html
http://blog.csdn.net/yuucyf/article/details/7872502
http://blog.csdn.net/csw_100/article/details/5495309
http://www.cnblogs.com/Daniel-G/archive/2012/12/02/2798520.html
http://blog.csdn.net/talentluke/article/details/6108557

2017.04.09
本人博客会根据个人知识升级情况进行补充修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值