我是靠谱客的博主 怕孤单银耳汤,最近开发中收集的这篇文章主要介绍C 语言编程 — GCC 内存对齐问题目录CPU 的内存对齐C 语言的内存对齐,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

文章目录

  • 目录
  • CPU 的内存对齐
  • C 语言的内存对齐
    • 对齐系数
    • GCC 内存对齐问题
    • 使用 pragma 宏指令修改对齐系数

CPU 的内存对齐

我们知道现代 CPU 主要有 32bit 和 64bit 字长之分,指的是 CPU 的寻址能力,即:一次可以处理的数据量大小,由 CPU 和 Bus(总线)宽度共同决定。

  • 32bit CPU:一次能够处理 32bit(4Byte)内存数据,内存寻址空间为 4GB。
  • 64bit CPU:一次能够处理 64bit(8Byte)内存数据,内存寻址空间为 256GB。

所以,在 Linux 操作系统的虚拟地址空间中,为物理内存空间的编址和寻址实现了一种称为内存对齐(Memory Alignment)的内存访问方式,比如:32bit CPU 要求 4Byte 字节对齐,64bit CPU 要求 8Byte 字节对齐。

以下图为例,如果 32bit CPU 要访问的数据刚好为 4Byte 大小,并且刚好也是内存对齐的,那么 CPU 就只需要一次内存访问即可;相反,则需要 2 次访问(跨越了 2 个对齐空间),从而浪费了 CPU 时钟周期。

在这里插入图片描述

此外,现代 CPU 还支持多种高级内存访问指令,例如 SSE 和 AVX 指令,这些指令可以同时处理多个数据元素。但是这些指令要求数据必须按照特定的对齐方式排列,否则会导致程序错误或性能下降。

C 语言的内存对齐

对齐系数

对齐系数是指数据在内存中存储时,数据的起始地址,必须对齐到某个系数的字节边界上,例如:对齐系数是 4,则表示每个数据在内存中存储时,其起始地址都必须是 4 的倍数。对齐系数是造成内存空洞的主要原因,同时也是性能提升的关键原因。设置合适的对齐系数能够做到节省空间和提升性能的均衡。

默认的对齐系数跟平台有关,通常也是 C 数据类型的大小,常见的 x86 CPU 对齐系数如下图。

在这里插入图片描述

也可以通过测试程序来获取具体 CPU 平台的对齐系数。

#include <stdio.h>

#define BASE_TYPE_SIZE(t) printf("%12s : %2d Byte%sn", #t, sizeof(t), (sizeof(t))>1? "s": "")

void base_type_size(void)
{
    BASE_TYPE_SIZE(void);
    BASE_TYPE_SIZE(char);
    BASE_TYPE_SIZE(short);
    BASE_TYPE_SIZE(int);
    BASE_TYPE_SIZE(long);
    BASE_TYPE_SIZE(long long);
    BASE_TYPE_SIZE(float);
    BASE_TYPE_SIZE(double);
    BASE_TYPE_SIZE(long double);
    BASE_TYPE_SIZE(void*);
    BASE_TYPE_SIZE(char*);
    BASE_TYPE_SIZE(int*);

    typedef struct {
    } StructNull;

    BASE_TYPE_SIZE(StructNull);
    BASE_TYPE_SIZE(StructNull*);
}


int main()
{
    base_type_size();
    return 0;
}

执行结果:

        void :  1 Byte
        char :  1 Byte
       short :  2 Bytes
         int :  4 Bytes
        long :  8 Bytes
   long long :  8 Bytes
       float :  4 Bytes
      double :  8 Bytes
 long double : 16 Bytes
       void* :  8 Bytes
       char* :  8 Bytes
        int* :  8 Bytes
  StructNull :  0 Byte
 StructNull* :  8 Bytes

GCC 内存对齐问题

在 C 语言中,基础数据类型的大小都是以 Byte 为单位设计的,例如:char 1B、int 4B 等,这些类型数据的对齐系数通常就是其自身的大小,所以可以简易的实现内存对齐,GCC 也提供了相应的内存对齐优化特性。

但是,对于结构复杂的构造数据类型而言,例如:Struct、Union 等,这些类型数据想要实现内存对齐,则还需要花费一番心机。

因为,在默认情况下,结构体的对齐系数通常是按照结构体中最宽的成员的大小来确定的,即:结构体采用所有成员中最大的对齐系数作为自身的对齐系数。

如下例子:Struct Test1 中有 1 个 int 成员(系数为 4)和 2 个 char 成员(系数为 1),那么此时 Test1 的对齐系数就为 4。

#include <stdio.h>

struct {
    int  i;
    char c1;
    char c2;
} Test1;

struct {
    char c1;
    int  i;
    char c2;
} Test2;

struct {
    char c1;
    char c2;
    int i;
} Test3;

int main()
{
    printf("%dn", sizeof(Test1));  // 输出 8
    printf("%dn", sizeof(Test2));  // 输出 12
    printf("%dn", sizeof(Test3));  // 输出 8
    return 0;
}

下图为 3 个结构体变量的内存布局图。

首先可以看见,这 3 个 Struct 的起始地址肯定都是对齐系数 4 的倍数。其次,可以发现,GCC 除了根据 “对齐系数“ 之外,还会根据 “成员顺序“ 来考虑如何为 char 成员进行空间填充。由于成员顺序不同,导致 3 个结构体分别为 char 填充的空间为:2Byte、6Byte、2Byte。

显然,不同的代码导致了不同的效率,这就是 GCC 带来的内存对齐问题。

在这里插入图片描述

使用 pragma 宏指令修改对齐系数

为了解决上述问题,C 语言提供了 pragma 宏指令,可用于修改某个结构体的对齐系数。宏指令为 #pragma pack(n),其中的 n 用于指定对齐值,通常是 2 的较小次方(e.g. 1、2、4),当为 1 的时候最节省空间,但内存访问性能有损失。

  • 如果 n 的值小于默认对齐值,则按照 n 的值进行对齐。
  • 如果 n 的值大于默认对齐值,则按照默认值进行对齐。

还可以通过指令 #pragma pack() 来告诉 GCC 关闭对某个结构体的内存对齐优化行为。

对于上述例子中的 3 个结构体,如果设定了 #pragma pack(1),那么根据对齐规则,就不再需要为 char 填充空间了。

在这里插入图片描述

如果设定了 #pragma pack(2),那么 3 个结构体的空间大小应为 6、8、6。内存分布如下图:

在这里插入图片描述

示例代码:

#include <stdio.h>

struct MyStruct {
    char a;
    int  b;
};

// 使用 #pragma pack(1) 指令修改对齐系数为 1 字节
#pragma pack(1)
struct MyStruct2 {
    char a;
    int  b;
};

int main() {
    printf("sizeof(struct MyStruct) = %lun", sizeof(struct MyStruct));    // 输出为 8
    printf("sizeof(struct MyStruct2) = %lun", sizeof(struct MyStruct2));  // 输出为 5
    return 0;
}

最后

以上就是怕孤单银耳汤为你收集整理的C 语言编程 — GCC 内存对齐问题目录CPU 的内存对齐C 语言的内存对齐的全部内容,希望文章能够帮你解决C 语言编程 — GCC 内存对齐问题目录CPU 的内存对齐C 语言的内存对齐所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部