C struct内存对齐 union的大端小端

系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的数据类型所要求的位置。如某个机器的整型长度为4个字节且它的起始存储位置能够被4整除,那么结构体

struct  ALLGN {
        char    a;
        int     b;
        char    c;
};

在内存中的存储的起始位置必须是一个能够被4整除的地址。a的地址跟整个结构体的起始地址一个值,结构体中的整型b必须跳过3个字节跳到合适的边界上才能存储,存储在b之后的是最后一个字符变量c。如果声明了相同类型的第二个结构体变量,它的起始存储位置也必须满足4这个边界,所以在第一个结构体的后面还要跳过3个字节才能够存储第二个结构。因此每个结构体占用12字节内存,但只是用其中的6个字节。《C和指针》“10.3结构的存储分配”章节(Page 227)内容。


1 内存对齐

内存对齐概念暂针对C中的结构体。掌握内存对齐主要是用来避免内存自动对齐,调整结构体所占的内存达到最小。


为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐内存访问仅需要一次访问。字,双字和四字在自然边界上不需要内存对齐(对它们来说,自然边界分别是偶数地址,可以被4或8整除的地址)。一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的。一个字起始地址是奇数,但没有跨越字边界被认为是对齐的。[C语言深度剖析—阮正冲]

 

自然边界

在系统(编译器)默认下,在存储变量时,当前变量vn的地址减去紧凑存储关系区域的第一个变量v1的地址的差值为变量vn长度的整数倍。此时当前变量vn的地址被称为自然边界[自己推测的一个概念]

 

如以下结构体:

struct _test{
    char ch;
    int  i;
}test;

32位机上,若&test.ch的值为0x0000,则地址为0x0004test.i的自然边界。在自然边界前的地址段必须能够容纳下正整数个当前变量。int占用一个字,那么i应该出现在自然边界自上(能够整除4的地址之上)。

 

内存对齐

以上的结构体在32位编译器下,地址为0x0000test.ch占一个字节内存后,按理说&test.i的起始地址应为0x0001,结束地址应为0x0003。但由于编译器的内存对齐会将test.i自动后移到它的自然边界上存储,即&test.i的值为0x0004(假设编译器内存对齐默认值为4),结束地址为0x0007。编译器默认的内存对齐就是将当前变量存储到自然边界之上。


在内存对齐下,对于以下结构体:

struct _test{
   int  i;
   char ch;
}test;
假若 &test.i = 0x0000 ,则 &test.ch = 0x0004 test.ch 元素会占用 4 个字节,也就是说 test 结构体还是会占用 8 个字节大小。


X86 32位编译器的默认内存对齐值一般为变量自身的长度值(将其存储在自身的自然边界上)。对于C来说,可以使用预处理命令#pragram pack()来修改编译器的默认内存对齐值。

 

如使用#pragram pack(2)修改内存对齐值为2后,内存对齐=min(2,sizeof(int))&test.i的起始地址为0x0002,结束地址为0x0005

 

综上,内存对齐(以下3层,层层递进)

  • 不使用#pragram pack(n)修改编译器的内存对齐值,内存对齐值 = sizeof(vn)
  • 使用#pragram pack(n)修改编译器的内存对齐值为n后,在#pragram pack(n)与语句#pragram pack(),内存对齐值= min(nsizeof(vn))
  • 结构体内任一元素之前的地址段能够存储正整数个长度为内存对齐值的元素。

 

(1)结构体内元素的内存对齐

struct _test2{
    char ch1;
    char ch2;
    int  i;
    long li;
}test2;

 

[1]结构体所占内存大小

[x86 32bit]根据内存对齐的特征瞄一眼结构体给出结构体所占内存大小值:1 + 3 + 4 + 4 =12字节。

 

[x86 32bit] sizeof(test2)= 12字节。

 

[2]每元素占内存大小

分析:ch11 bytech23 bytes(两字节的空闲地址,由i对齐造成)i4 bytesli4 bytes

 

(2)结构体内含结构体的内存对齐

struct  _test3{
    char ch1;
    struct _test2  t2;
    int i;
}test3;

[1]结构体所占内存大小

[x86 32bit]瞄内存值为:4 + 12 + 4 = 20 bytes

 

[x86 32bit] sizeof(test3)= 20 bytes

 

[2]每元素占内存大小

分析:结构体内的结构体元素内存对齐方式:以结构体元素内占内存最大的元素的长度对齐

 

所以:ch1 4 bytes(3bytes为空闲,由t2对齐造成)t212 bytes(对齐方式不变)i4 bytes

 

(3)含#pragram pack()的内存对齐

#pragma pack(n)n=1, 2, 4,8,…

 

[1]#pragram pack()设定值小于sizeof(vn)

#pragma pack(2)
struct  _test3{
    char ch1;
    struct _test2  t2;
    int i;
}test3;
#pragram pack()

[x86 32bit]瞄内存值为:2 + 12 + 4 = 18bytes。此时int类型的i就跨越了4字节的一个边界,被认为是内存是未对齐的。

 

[x86 32bit]sizeof(test3) = 18 bytes

 

分析:ch1 2 bytes(1bytes空闲,由#pragram pack(2)造成,它的值比t2的对齐长度4要小)t212 bytes[因为struct _test2不在#pragma pack(2)#pragma pack()之间,struct _test2  t2只是一个内存拷贝,故而t2的占内存大小不变]i4 bytes

 

[2]#pragram pack()设定值大于sizeof(vn)

#pragma pack(8)
struct  _test3{
    char ch1;
    struct _test2  t2;
    int i;
}test3;

#pragram pack()

[x86 32bit] 瞄内存值:4 + 12 + 4 = 20 bytes

 

[x86 32bit]sizeof(test3)= 20 bytes

 

分析:ch1 4 bytes(#pragma pack(8) > t2的内存对齐值4)t212 bytesi4 bytes

 

2 大端小端

(1)大小端存数据方式

大端:数据高字节内容存在低地址中。

[x86 32bit] int i = 1;

0x000x000x000x01

0xijkm,                     0xijk(m+1) ,                     xijk(m+2),            0xijk(m+3)


小端:数据高字节内容存在高地址中。

[x86 32bit] int i = 1;

0x000x00x000x01

0xijk(m+3)                  0xijk(m+2)                    0xijk(m+1)                0xijkm

大端小端成为了存储数据的一个特点。在必要的时刻就需要明确大端小端的存储方式来明确变量的值(人为判断时)。如union类型就是一个例子。

 

(2)union和大小端

[1]union数据元素存储方式

union _utest{
    char ch1;
    char ch2;
    int  i;
}utest;

union类型变量内的所有元素的内存地址共享起始地址。为union变量开辟其内占内存最大的那个内存值。

ch1(ch2)int[0]int[1]int[2]int[3]

 

如以上sizeof(utest) = 4 bytes。且&utest.ch1&utest.ch2&utest.i的值都一样。

 

[2]union元素值

union变量的某段内存地址上定是多元素的地址。故而给union变量的某元素赋值时,可能其它的元素的值也被“潜”定了。

utest.i = 1;

大端下

0x00

0x00

0x00

0x01

ch1(ch2)                    i

 

utest.i,utest.ch1,utest.ch2起始地址相同。大端时,utest.i的值在高地址字节中。

utest.ch1 =utest.ch2 = 0x00;

 

小端下

0x01

0x00

0x00

0x00

ch1(ch2)                     i

 

utest.i,utest.ch1,utest.ch2起始地址相同。大端时,utest.i的值在低地址字节中。

utest.ch1 = utest.ch2 = 0x01;

 

测试了一下Debian Linux 5.0是小端模式。

 

如果是测试当给utest.ch1utest.ch2赋值时,i的值为多少。这种情况先要对i的其它位(如utest.i=0)初始化,不然输出来是乱码。

 

C Note Over。

结构体的内存对齐是指在分配内存时,结构体成员的起始地址需要按照一定规则对齐。这个规则通常由编译器根据目标平台的要求来确定,以保证结构体的访问效率和内存使用效率。 在C语言中,结构体的对齐方式可以通过预处理指令`#pragma pack`或者`__attribute__((packed))`来设置。默认情况下,编译器会尽可能地对齐结构体成员,以提高访问速度。对齐规则一般满足以下几个原则: 1. 对齐基本类型:结构体成员的起始地址要能够整除其类型的大小。例如,一个int类型的成员要求按4字节对齐。 2. 对齐顺序:结构体成员的顺序通常是按照声明的顺序排列的,但是编译器可能会对成员重新排序以减少内存空洞。 3. 对齐填充:为了满足对齐要求,编译器可能会在结构体成员之间插入一些填充字节,以保证下一个成员能够正确对齐。 考虑一个例子: ```c struct Example { char a; int b; double c; }; ``` 假设`char`类型占1字节,`int`类型占4字节,`double`类型占8字节。根据对齐规则,编译器可能会对结构体进行对齐如下: ``` struct Example { char a; // 1字节 char padding[3]; // 填充字节,以保证int类型对齐 int b; // 4字节 double c; // 8字节 }; ``` 这样,结构体的大小为16字节,保证了每个成员的对齐要求。 需要注意的是,结构体的内存对齐可能会影响到结构体的大小和内存布局,特别是在涉及到跨平台或网络通信时需要特别小心。可以通过设置对齐方式来控制结构体的内存对齐,以满足特定需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值