【C/C++】详解内存对齐问题,C语言内存对齐整理
🔥个人主页:艾莉丝努力练剑
❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、C/C++干货分享&学习过程记录
🍉学习方向:C/C++方向
⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平
前言:本专栏记录了博主C++从初阶到高阶完整的学习历程,会发布一些博主学习的感悟、碰到的问题、重要的知识点,和大家一起探索C++这门程序语言的奥秘。这个专栏将记录博主C++语法、高阶数据结构、STL的学习过程,正所谓“万丈高楼平地起”嘛,我们话不多说,继续进行C++阶段的学习。本文我们不讲C++主线的内容,我们来拓展一下或者说整理一下我们学习时C/C++时经常会提到的一些专有名词,例如形参、实参,显式类型转换和隐式类型转换类型转换,内置类型、内存对齐问题等等。
C++的两个参考文档:
老朋友(非官方文档):cplusplus
官方文档(同步更新):cppreference
目录
正文
1、什么是内存对齐
2、为什么需要内存对齐
2.1 硬件要求
2.2 性能优化
2.3 跨平台兼容
3、对齐规则详解
3.1 基本数据类型的自然对齐
3.2 结构体的对齐规则
3.3 联合体(union)的对齐
4、结构体成员排列优化
5、内存布局示例
5.1 示例1:基本结构体
5.2 示例2:调整成员顺序优化空间
6、控制对齐方式
6.1 编译器指令
6.2 C++11后的标准方式
6.3 跨平台写法
7、实际开发中的注意事项
8、实际应用场景
8.1 网络协议处理
8.2 硬件寄存器映射
8.3 性能优化
9、检测对齐的方法
10、常见问题与解决方案
10.1 问题1:非对齐访问导致崩溃(ARM平台)
10.2 问题2:结构体大小意外过大
10.3 问题3:跨平台数据不一致
10.4 问题4:跨平台结构体大小不一致
11、调试与检测
11.1 查看对齐值
11.2 检查指针是否对齐
11.3 静态断言(C11)
12、性能影响实测示例
13、高级话题:缓存行对齐
结尾
正文
博主之前就介绍过内存对齐的内容,是在C语言的自定义类型:结构体里面介绍的——
【自定义类型:结构体】:类型声明、结构体变量的创建与初始化、内存对齐、传参、位段
1、什么是内存对齐
内存对齐(Memory Alignment)是指数据在内存中的存储地址必须是某个值的整数倍(通常是2、4、8等2的幂次方)。现代计算机体系结构对内存访问进行了优化,要求特定类型的数据必须从特定倍数的地址开始存储。
2、为什么需要内存对齐
2.1 硬件要求
-
CPU访问效率:多数CPU访问对齐的数据只需要一个总线周期,而非对齐访问可能需要多个周期
-
硬件支持:某些架构(如ARM)完全不允许非对齐访问,会导致硬件异常
(硬件要求:多数CPU访问对齐数据效率更高,有些架构(如ARM)直接不支持非对齐访问)
2.2 性能优化
-
缓存行优化:对齐数据能更好地利用CPU缓存
(性能优化:对齐数据能更好利用CPU缓存行)
-
SIMD指令:许多SIMD指令要求数据必须对齐
(指令集要求:如SSE/AVX等SIMD指令要求数据必须对齐)
2.3 跨平台兼容
-
不同平台可能有不同的对齐要求
3、对齐规则详解
3.1 基本数据类型的自然对齐
-
char
:1字节对齐 -
short
:2字节对齐 -
int
/float
:4字节对齐 -
double
/long long
:8字节对齐(32位系统可能4字节) -
指针:4字节(32位)或8字节(64位)对齐
3.2 结构体的对齐规则
-
结构体的对齐要求是其成员中最大对齐要求的那个值
-
结构体的大小必须是其对齐要求的整数倍
-
每个成员的偏移量必须是其自身对齐值的整数倍
struct Example1
{char a; // 1字节int b; // 4字节(需要从4的倍数地址开始)short c; // 2字节
};
// 大小可能是12字节(1 + 3填充 + 4 + 2 + 2填充)
3.3 联合体(union)的对齐
-
对齐要求等于其最大成员的对齐要求
-
大小等于最大成员的大小(向上对齐)
4、结构体成员排列优化
通过合理排列成员顺序可以节省内存:
// 优化前(12字节)
struct bad_layout
{char a;int b;short c;
};// 优化后(8字节)
struct good_layout
{int b;char a;short c;
};
优化原则:
按对齐值从大到小排列成员
相同类型的成员尽量集中放置
5、内存布局示例
5.1 示例1:基本结构体
struct S1
{char a; // 偏移0// 3字节填充int b; // 偏移4short c; // 偏移8// 2字节填充(使总大小为12,是4的倍数)
};
// sizeof(S1) == 12
5.2 示例2:调整成员顺序优化空间
struct S2
{int b; // 偏移0char a; // 偏移4short c; // 偏移6// 无填充(总大小8,已是4的倍数)
};
// sizeof(S2) == 8
6、控制对齐方式
6.1 编译器指令
-
GCC/Clang:
__attribute__((aligned(n)))
或__attribute__((packed))
// 强制4字节对齐
struct __attribute__((aligned(4))) aligned_struct
{char a;int b;
};// 取消对齐(packed)
struct __attribute__((packed)) packed_struct
{char a;int b;
};
-
MSVC:
__declspec(align(n))
__declspec(align(16)) struct aligned_struct
{char a;int b;
};
演示如下:
// 强制16字节对齐
struct alignas(16) AlignedStruct
{int a;double b;
};// 取消对齐(可能降低性能但节省空间)
#pragma pack(push, 1)
struct PackedStruct
{char a;int b;short c;
};
#pragma pack(pop)
6.2 C++11后的标准方式
alignas(16) int aligned_array[4]; // 16字节对齐struct alignas(8) MyStruct
{char a;int b;
};
6.3 跨平台写法
#if defined(_MSC_VER)
#define ALIGN(n) __declspec(align(n))
#else
#define ALIGN(n) __attribute__((aligned(n)))
#endifALIGN(8) struct cross_platform_struct
{int a;char b;
};
7、实际开发中的注意事项
网络传输:传输结构体前应序列化或使用
#pragma pack(1)。
文件IO:直接读写结构体要考虑对齐差异。
跨平台开发:不同平台对齐要求可能不同。
性能敏感代码:合理安排结构体成员顺序。
SIMD编程:必须保证数据对齐。
8、实际应用场景
8.1 网络协议处理
#pragma pack(push, 1) // 1字节对齐
struct network_packet
{uint16_t header;uint32_t length;char data[256];
};
#pragma pack(pop) // 恢复默认对齐
8.2 硬件寄存器映射
struct hw_register
{volatile uint32_t CTRL ALIGN(4);volatile uint32_t STATUS ALIGN(4);volatile uint32_t DATA ALIGN(4);
};
8.3 性能优化
// 缓存行对齐(通常64字节)
struct cache_line_aligned
{int data ALIGN(64);
};
9、检测对齐的方法
// C++11
static_assert(alignof(int) == 4, "int must be 4-byte aligned");// 运行时检查
bool is_aligned(const void* p, size_t alignment)
{return (reinterpret_cast<uintptr_t>(p) % alignment) == 0;
}
10、常见问题与解决方案
10.1 问题1:非对齐访问导致崩溃(ARM平台)
char buffer[100];
int *p = (int *)(buffer + 1); // 非对齐指针
*p = 42; // 在ARM上可能崩溃
解决方案:确保指针类型转换后的对齐要求。
// 方法1:使用memcpy
int value = 42;
memcpy(buffer + 1, &value, sizeof(value));// 方法2:确保对齐
int *p = (int *)(buffer + (4 - ((uintptr_t)buffer % 4)));
10.2 问题2:结构体大小意外过大
解决方案:重新排列成员顺序,把大对齐成员放前面。
10.3 问题3:跨平台数据不一致
解决方案:使用序列化代替直接内存拷贝。
10.4 问题4:跨平台结构体大小不一致
解决方案:
使用编译器指令统一对齐方式
避免直接读写结构体二进制,改用序列化
11、调试与检测
11.1 查看对齐值
#include <stddef.h>
printf("int alignment: %zu\n", _Alignof(int));
11.2 检查指针是否对齐
int is_aligned(const void *ptr, size_t alignment)
{return ((uintptr_t)ptr % alignment) == 0;
}
11.3 静态断言(C11)
_Static_assert(_Alignof(double) == 8, "double must be 8-byte aligned");
12、性能影响实测示例
示例一:
// 测试对齐访问的性能差异
void test_aligned_access()
{const int SIZE = 1000000;// 非对齐内存char* unaligned = new char[SIZE * 4 + 1];int* data1 = reinterpret_cast<int*>(unaligned + 1); // 强制非对齐// 对齐内存int* data2 = new int[SIZE];// 性能测试...
}
示例二:
#include <stdio.h>
#include <time.h>#define SIZE 10000000void test_aligned()
{_Alignas(16) int array[SIZE];// 测试对齐访问性能...
}void test_unaligned()
{char buffer[SIZE * 4 + 1];int *array = (int *)(buffer + 1); // 故意不对齐// 测试非对齐访问性能...
}int main()
{// 对比两个函数的执行时间
}
13、高级话题:缓存行对齐
对于多线程编程,避免false sharing(伪共享):
struct alignas(64) CacheLineAligned
{ // 典型缓存行大小64字节int data;// 填充剩余空间
};
结尾
往期回顾:
【C/C++】具有C风格的强制类型转换:显式类型转换、隐式类型转换,C语言强制类型转换
【C/C++】C++引用和指针的对比
【C/C++】形参、实参相关内容整理
【C/C++】Dev-C++的安装与使用以及快捷键整理
【日常问题解决方案】VS2022不小心解决方案资源管理器把关掉了怎么办
VS2022进行监视功能的步骤
结语:本文内容到这里就全部结束了。本文我们介绍了C语言以及C++的内存对齐问题。