C语言中的结构体内存对齐现象

参考资料

先来看一段代码

#include <stdio.h>

int main() {
    
    
    // 声明结构体char_short_long
    struct {
    
    
        char  c;
        short s;
        long  l;
    } char_short_long;
    // 声明结构体long_short_char
    struct {
    
    
        long  l;
        short s;
        char  c;
    } long_short_char;
    // 声明结构体char_long_short
    struct {
    
    
        char  c;
        long  l;
        short s;
    } char_long_short;

    printf(" \n");
    printf(" Size of char   = %d bytes\n",sizeof(char));
    printf(" Size of short  = %d bytes\n",sizeof(short));
    printf(" Size of long   = %d bytes\n",sizeof(long));
    printf(" \n");  //char_short_long
    printf(" Size of char_short_long       = %d bytes\n",sizeof(char_short_long));
    printf("     Addr of char_short_long.c = 0x%p (10进制:%d)\n",&char_short_long.c,&char_short_long.c);
    printf("     Addr of char_short_long.s = 0x%p (10进制:%d)\n",&char_short_long.s,&char_short_long.s);
    printf("     Addr of char_short_long.l = 0x%p (10进制:%d)\n",&char_short_long.l,&char_short_long.l);
    printf(" \n");

    printf(" \n");  //long_short_char
    printf(" Size of long_short_char       = %d bytes\n",sizeof(long_short_char));
    printf("     Addr of long_short_char.l = 0x%p (10进制:%d)\n",&long_short_char.l,&long_short_char.l);
    printf("     Addr of long_short_char.s = 0x%p (10进制:%d)\n",&long_short_char.s,&long_short_char.s);
    printf("     Addr of long_short_char.c = 0x%p (10进制:%d)\n",&long_short_char.c,&long_short_char.c);
    printf(" \n");

    printf(" \n");  //char_long_short
    printf(" Size of char_long_short       = %d bytes\n",sizeof(char_long_short));
    printf("     Addr of char_long_short.c = 0x%p (10进制:%d)\n",&char_long_short.c,&char_long_short.c);
    printf("     Addr of char_long_short.l = 0x%p (10进制:%d)\n",&char_long_short.l,&char_long_short.l);
    printf("     Addr of char_long_short.s = 0x%p (10进制:%d)\n",&char_long_short.s,&char_long_short.s);
    printf(" \n");
    return 0;
}

上面一段程序,使用gcc 4.8.5版本在装有 Intel芯片的64位机器编译并执行,输出如下:

Size of char   = 1 bytes
Size of short  = 2 bytes
Size of long   = 8 bytes
 
Size of char_short_long       = 16 bytes
     Addr of char_short_long.c = 0x0x7ffcb42ff240 (10进制:-1271926208)
     Addr of char_short_long.s = 0x0x7ffcb42ff242 (10进制:-1271926206)
     Addr of char_short_long.l = 0x0x7ffcb42ff248 (10进制:-1271926200)
 
 
Size of long_short_char       = 16 bytes
     Addr of long_short_char.l = 0x0x7ffcb42ff230 (10进制:-1271926224)
     Addr of long_short_char.s = 0x0x7ffcb42ff238 (10进制:-1271926216)
     Addr of long_short_char.c = 0x0x7ffcb42ff23a (10进制:-1271926214)
 
 
Size of char_long_short       = 24 bytes
     Addr of char_long_short.c = 0x0x7ffcb42ff210 (10进制:-1271926256)
     Addr of char_long_short.l = 0x0x7ffcb42ff218 (10进制:-1271926248)
     Addr of char_long_short.s = 0x0x7ffcb42ff220 (10进制:-1271926240)

我们会发现,跟想象中不同,C语言中的结构体的真实大小不一定是所有成员占用的大小。以char_short_long为例,char虽然占据只占据一个字节(地址是-208),可是short的储存地址不是挨着char的地址-207,而是-206。这意味着这两个成员之间存在间隙。

结构体成员变量的放置顺序影响着结构体所占的内存空间的大小。结构体的内存对齐规则如下:

在没有#pragma pack宏的情况下:

  • 原则1

    结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

  • 原则2

    结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

  • 原则3

    结构体作为成员时,结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素时,那么b应该从8的整数倍地址处开始存储,因为sizeof(double) = 8 bytes)

为什么需要内存对齐

为什么需要内存对齐?这其实牵扯到计算机组成原理。我们知道,CPU一次能读取多少内存要看数据总线是多少位,例如32位机器一次就只能读取4个字节。另外,内存单元会按照若干个字节进行划分(所谓的按字编址),地址线设为某个值实际上是个片选信号,意味着选中某个内存单元,换句话说,CPU能访问的首地址是不连续的,类似于0 4 8 12这样。

假设一个字(内存单元)占用4个字节,如果结构体紧凑地存放数据,例如long(4字节)紧挨着char(1字节)存放,此时该结构占用两个内存单元(5个字节),在32位的机器上读取long的值的时候就需要两个总线周期(需要片选两个内存单元)。很明显,时间上存在优化的空间,只要把long完整放在某个内存单元,就可以使得CPU可以在一个总线周期内完成取数操作,这相当于牺牲了额外的空间来提高效率。不要小看节省下来的一个总线周期,毕竟,访问主存需要的时间远远长于CPU进行计算的时间。

还可以手动设置对齐方式:

// 表示它后面的代码都按照n个字节,与最大成员字节数之间的最小值,进行内存对齐
// 因此,实际上这里存在一个平衡,防止最大成员过大,但又完全按照最大成员进行内存对齐而导致大量的内存浪费
#pragma pack(n)
// windows下VS平台默认对齐数可以是(1,2,4,8)
// Linux下默认对齐数可以是(1,2,4)

#pragma pack()  // 取消按照n个字节对齐,是对#pragma pack(n)的一个反向操作

当然,C语言标准中,对于内存对齐的规定并不严格。一般原则是:struct应按照计算机最舒服的方式对齐,兼顾节约内存。

pragma预编译指令

#pragma 预编译指令比较复杂,这一条预处理指令的用法在不同的编译器上是不一样的,没有完全统一。你只能在网上找到一些比较通行的用法,如:

  • #pragma once 所在文件只编译一次。
  • #pragma message("…") 编译器碰到这一句时就会在编译输出窗口输出括号内的字符串。
  • #pragma warning(…)
  • #pragma comment(…)

要想了解 #pragma 的具体用法,首先应该确定你要了解哪个编译器的,然后去查编译器的使用手册。找不到就算了,别用这种预处理指令。

猜你喜欢

转载自blog.csdn.net/chong_lai/article/details/123468989
今日推荐