概要
本文记述了一个 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)