有两点需要先明确:
- Linus Torvalds(林纳斯·托瓦兹)是一位成就非凡的工程师,他创造的 Linux 内核和 Git 系统深刻影响了整个计算领域。
- 和许多资深开发者一样,他对代码风格有着极其鲜明的个人主张。在 Linux 内核这样由全球数千人协作的巨型项目中,推行统一的编码规范至关重要;但在你自己的个人项目中,是否完全遵循他的观点则见仁见智。
那他到底为什么讨厌“数组形参”?
最核心的原因很简单:在 C 语言中,根本就不存在“数组作为函数参数”这回事!
当你写下这样的函数声明:
void foo(int arr[10]);
编译器并不会真的把一个长度为 10 的数组传进去。它会悄无声息地把声明转换为一个指针:
void foo(int *arr);
这种行为源于 C 语言早期的历史设计,如今虽然作为一种语法糖被保留了下来,但本质上是一种“障眼法”。正如 Linus 在 2015 年一封著名的邮件里直白地批评道:
“C 语言中根本不存在数组参数。可悲的是,出于一些糟糕的历史原因,编译器居然接受了这种写法,并默默地把它变成指针参数。支持这种写法的人,脑子都不太清醒。”
问题究竟出在哪里?来看一个真实案例

来源:https://lkml.org/lkml/2015/9/3/428
在这封邮件中,他批评了一段来自无线网络驱动的代码:
static bool rate_control_cap_mask(..., u8 mcs_mask[IEEE80211_HT_MCS_MASK_LEN])
{
for (i = 0; i < sizeof(mcs_mask); i++)
...
}
乍一看似乎没问题,但这里埋藏了两个致命的陷阱:
mcs_mask 被声明为数组,实际上却是一个指针;
sizeof(mcs_mask) 返回的是指针的大小(4或8字节),而不是数组的真实长度!
导致的结果就是:循环只会迭代 4 次(32位系统)或 8 次(64位系统),而它本应迭代 10 次(因为 IEEE80211_HT_MCS_MASK_LEN == 10)。虽然碰巧因为数组元素是单字节,且前几个元素可能就满足了条件,使得这个 Bug 在测试中没有立刻暴露,但这仍然是一个典型的逻辑错误。
Linus 对此怒斥道:
“这段代码之所以看起来‘合理’,完全是因为第一个错误(用数组形参)掩盖了第二个错误(误用 sizeof)。它本质上是一堆互相喂养的垃圾。”
正确的写法应该是:
- 参数直接声明为指针:
u8 *mcs_mask
- 循环边界明确使用宏常量:
for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++)
或者,如果你真想表达“这是一个固定长度的数组”,那就不要依赖形参语法,而是通过注释、命名或配套的宏(例如 Linux 内核中的 ARRAY_SIZE())来传达意图。理解这些底层原理,是掌握 C/C++ 编程的关键之一。
为什么这种写法危害更大?
Linus 特别强调:这种写法具有极强的欺骗性。
- 它看起来像是在“文档化”参数的预期长度(比如
[10] 仿佛在说“我需要一个10元素的数组”);
- 但实际上,编译器完全忽略这个数字,不会做任何边界检查;
- 更糟糕的是,它会诱导程序员写出
sizeof(arr) 这种看似合理、实则完全错误的代码。
“这根本不是文档,这是在撒谎。误导性的‘文档’比没有文档更危险。” —— Linus Torvalds
在他看来,使用 int arr[10] 作为函数参数,无异于向全世界宣告:“我并没有真正理解 C语言 的底层机制”。
那我们该怎么办?
- 如果你在参与 Linux 内核或遵循其规范的项目:请严格避免数组形参,一律使用指针。
- 在你自己的项目中:虽然技术上可以使用,但你必须清醒地认识到它只是“语法糖”,背后依然是裸指针。务必避免在函数内部使用
sizeof 来获取数组长度。
- 更好的实践:显式传递数组长度作为另一个参数,或者使用结构体将指针和长度封装在一起,也可以借助
ARRAY_SIZE 这类宏来提高代码的可读性和安全性。这些都属于良好的 编程原则 。
Linus 反感“数组形参”,并非出于固执,而是因为:
- 它名不副实(声称是数组,实为指针);
- 它极易引发隐蔽的 Bug(尤其是配合
sizeof 使用时);
- 它制造了虚假的安全感,让开发者误以为编译器会帮忙进行边界检查。
正如他所说:“当我看到这种明显荒谬的代码时,我会非常愤怒——因为它让我担心那些我还没发现的、更加隐蔽的问题。”
所以,与其依赖这种“外表光鲜却暗藏陷阱”的语法,不如老老实实地使用指针,清清楚楚地传递长度——这才是对 计算机系统 底层行为有清晰认知的体现,也是真正的“懂 C”。
|