当前位置: 首页 > news >正文

C 内存对齐踩坑记录

概要

本文记述了一个 C 语言使用过程中由于不当设置内存对齐属性导致的问题。

背景

笔者在多年前开发了一个模块 hm ,该模块被多个模块深度使用并一直健康稳定运行,且该模块已经两年多没有任何变动。

然而,最近一个模块使用 hm 时,发现其初始化时会导致进程 coredump —— 而 coredump 的位置就在 hm 这个多年未动的代码上面。

问题表现

分析过程中,涉及代码实现部分均已简化删减,只保留了能说明情况的数据结构和编码。

hm 的结构体如下,该结构体由于业务需要,设置了特定的内存对齐方式:

typedef struct {bool a;bool b;int c __attribute__((aligned(64)));
} hm_t __attribute__((aligned(128)));

其中,模块 a 的使用方法如下:

int main(void)
{init();hm_var.a = true;hm_var.b = false;hm_var.c = 10086;HELLO("module a");return check(&hm_var, true, false, 10086) ? 0 : -1;
}

而模块 b 的使用方法也完全相同:

int main(void)
{init();hm_var.a = true;hm_var.b = false;hm_var.c = 10086;HELLO("module b");return check(&hm_var, true, false, 10086) ? 0 : -1;
}

然而,在实际运行时,两者却产生了截然不同的效果:

focksor@focksor:~/workSpace/notebook_demo__c_param_pack_issue$ make a
gcc -std=c11 -Wall -Wextra -O2 -o module_a.elf hm.c module_a.c
./module_a.elf
hello from module a
hm var: a=1 b=0 c=10086
check: a=1 b=0 c=10086
focksor@focksor:~/workSpace/notebook_demo__c_param_pack_issue$ make b
gcc -std=c11 -Wall -Wextra -O2 -o module_b.elf hm.c module_b.c
./module_b.elf
hello from module b
hm var: a=1 b=0 c=286331153
check: a=1 b=0 c=10086
expect hm->c is 10086 but actually is 286331153
make: *** [Makefile:7: b] Error 255

可以看到,两者的使用方法完全一致,但是产生了截然不同的效果——虽然在看此文章的读者可能已经猜到了是内存对齐的问题,但是这个项目实际具有数万行的关联代码,笔者当时可是头疼得很。

分析过程

预处理

使用 gcc -E 指令预处理模块的 .c 文件,查看展开后的内容,两个模块的展开 diff 如下:

1c1
< # 0 "module_a.c"
---
> # 0 "module_b.c"
6c6,12
< # 1 "module_a.c"
---
> # 1 "module_b.c"
> # 1 "some_other_utils.h" 1
> #pragma pack(1)
> typedef struct {
>     int a;
> } some_other_struct_t;
> # 2 "module_b.c" 2
577,583c583
< # 2 "module_a.c" 2
< # 1 "some_other_utils.h" 1
< #pragma pack(1)
< typedef struct {
<     int a;
< } some_other_struct_t;
< # 3 "module_a.c" 2
---
> # 3 "module_b.c" 2
589c589
< # 7 "module_a.c" 3 4
---
> # 7 "module_b.c" 3 4
591c591
< # 7 "module_a.c"
---
> # 7 "module_b.c"
594c594
< # 8 "module_a.c" 3 4
---
> # 8 "module_b.c" 3 4
596c596
< # 8 "module_a.c"
---
> # 8 "module_b.c"
600c600
<     printf("hello from %s\n", "module a");
---
>     printf("hello from %s\n", "module b");
602c602
< # 12 "module_a.c" 3 4
---
> # 12 "module_b.c" 3 4
604c604
< # 12 "module_a.c"
---
> # 12 "module_b.c"
606c606
< # 12 "module_a.c" 3 4
---
> # 12 "module_b.c" 3 4
608c608
< # 12 "module_a.c"
---
> # 12 "module_b.c"

注意到中间有一段不同:

> # 1 "module_b.c"
> # 1 "some_other_utils.h" 1
> #pragma pack(1)
> typedef struct {
>     int a;
> } some_other_struct_t;
> # 2 "module_b.c" 2
577,583c583
< # 2 "module_a.c" 2
< # 1 "some_other_utils.h" 1
< #pragma pack(1)
< typedef struct {
<     int a;
< } some_other_struct_t;
< # 3 "module_a.c" 2

可以看到是模块引用的另外一个头文件中,使用 #pragma pack 设置了内存对齐属性(并且没有恢复!)——而如果这个在 hm.h 之前定义,将会使 hm_t 的内存对齐属性失效。

源码对比

知道了是因为内存对齐属性导致的问题,我们再回来看两个模块的源码,可以看到一些端倪:

$ diff module_a.c module_b.c  -u
--- module_a.c  2025-09-03 16:29:27.837430915 +0800
+++ module_b.c  2025-09-03 16:29:49.329691060 +0800
@@ -1,5 +1,5 @@
-#include "hm.h"#include "some_other_utils.h"
+#include "hm.h"int main(void){
@@ -8,6 +8,6 @@hm_var.b = false;hm_var.c = 10086;-    HELLO("module a");
+    HELLO("module b");return check(&hm_var, true, false, 10086) ? 0 : -1;}

可以看到,两个模块导入 some_other_utils.h 和 hm.h 的顺序不同,而 some_other_utils.h 的定义如下:

#pragma pack(1)
typedef struct {int a;
} some_other_struct_t;#define HELLO(from) printf("hello from %s\n", from)

在这个头文件中,开发者设置了内存对齐属性(但是忘了恢复)。因此,在该头文件之后导入的任何其它头文件都会遵循这个内存对齐属性——而不是遵循在结构体定义时设定的属性。

修复方法

既然知道了问题,修复方法倒是很简单:设置后取消,或只针对单个结构体设置,而不要全局设置。

设置后取消

该方法是利用 #pragma pack 的 push 和 pop 方法(也就是大家熟悉的入栈出栈)完成,在使用完成之后,及时恢复属性原来的值:

diff --git a/some_other_utils.h b/some_other_utils.h
index 35fe9d6..fa16baa 100644
--- a/some_other_utils.h
+++ b/some_other_utils.h
@@ -1,6 +1,7 @@
-#pragma pack(1)
+#pragma pack(push, 1)typedef struct {int a;} some_other_struct_t;
+#pragma pack(pop)#define HELLO(from) printf("hello from %s\n", from)

单结构体设置

就像 hm_t,使用 __attribute__ 指定单个结构体的属性,而不要全局指定:

diff --git a/some_other_utils.h b/some_other_utils.h
index 35fe9d6..76d4525 100644
--- a/some_other_utils.h
+++ b/some_other_utils.h
@@ -1,6 +1,5 @@
-#pragma pack(1)typedef struct {int a;
-} some_other_struct_t;
+} some_other_struct_t __attribute__((aligned(1)));#define HELLO(from) printf("hello from %s\n", from)

资料

本文代码已上传至:focksor/notebook_demo__c_param_pack_issue

http://www.dtcms.com/a/365850.html

相关文章:

  • 【随手记】vscode中C语言满足KR风格的方法
  • Elasticsearch核心数据类型
  • 深度学习——卷积神经网络
  • AI产品经理面试宝典第84天:RAG系统架构设计与优化策略面试指南
  • 分布式AI算力系统番外篇-----超体的现世《星核》
  • 【Doris入门】Doris数据表模型使用指南:核心注意事项与实践
  • 从PkiAsn1Decode函数到ASN1Dec_SignedDataWithBlobs函数
  • 中山AI搜索优化实践:技术干货解析与金拓智能案例
  • select, poll, epoll
  • 【108】基于51单片机智能输液监测系统【Proteus仿真+Keil程序+报告+原理图】
  • 详尽 | Deeplabv3+结构理解
  • CSS中使用 HSL(Hue, Saturation, Lightness) 动态生成色值
  • 二叉树结尾——销毁,层序遍历与判断完全二叉树
  • python如何解决html格式不规范问题
  • windows系统服务器测试部署springboot+vue+mysql项目
  • 使用 Acme.sh 获取和管理免费 SSL 证书
  • vue2头部布局示例
  • Anaconda3出现Fatal error in launcher: Unable to create process using.....问题
  • python 连接数据库进行文件查重(SAP版本)
  • RoPE位置编码缩放因子的最优解:频率维度与位置敏感度的精妙权衡
  • TypeScript:Promise的详细用法讲解
  • 面试复习题--kotlin的设计模式
  • shell内置命令
  • UART更好的封装 添加容错代码
  • Qt6用Chart模块做数据可视化?别再用老套路,看看这套35张图背后的秘密
  • [密码学实战](GBT 15843.2-2017)Java实现基于SM4的实体鉴别机制(四十八)
  • MinIO祭了,RustFS来了!
  • 关于node中的一些用到的读取文件方法
  • Dubbo3单端口多协议源码分析
  • 员工拍照泄密?U盘偷拷资料?终端数据安全如何守护?