an ambiguity in c89 which will never be fixed

An Ambiguity in C89 Which Will Never Be Fixed

C89 标准中一个永远无法修复的歧义

2026-08-10 i found some ambiguous wording in the c89(/c90) standard, where gcc and clang disagree on the interpretation. it concerns the behavior of implicit function declarations, which were removed in c99, so this was never disambiguated. 2026-08-10 我在 C89(/C90) 标准中发现了一些模棱两可的措辞,GCC 和 Clang 对此有不同的解读。这涉及到隐式函数声明的行为,该特性在 C99 中已被移除,因此这个问题永远不会有明确的定论。

for those unaware: c89 has a cool “feature” where, if you try to call a function which doesn’t exist, rather than erroring out, the function is implicitly declared as a function with unspecified parameters returning int. more specifically: if the function in a call expression is a non-parenthesized identifier which isn’t in scope, it’s inserted into scope as an extern int (). (a funny thing about this is that it means that seemingly superfluous parentheses around expressions affect semantics: f() inserts f into scope, but (f)() doesn’t) 对于不知情的人:C89 有一个很酷的“特性”,如果你尝试调用一个不存在的函数,它不会报错,而是将该函数隐式声明为一个参数未指定且返回 int 的函数。更具体地说:如果调用表达式中的函数是一个不在作用域内的非括号标识符,它会被作为 extern int () 插入到作用域中。(有趣的是,这意味着表达式周围看似多余的括号会影响语义:f() 会将 f 插入作用域,但 (f)() 不会。)

anyways, i’m gonna show you a fun edge case with this feature. but to build up to it, let’s start simple. here’s a declaration whose declarator declares itself: int f(int [sizeof(f())]); this declares a function with one parameter, which is an array (decayed to a pointer). identifiers are inserted into scope after the declarator is completed. so when f() is called inside the array declarator, it isn’t yet in scope, so it’s declared as int (). the declarator then finishes, and f is redeclared with the compatible type int (int *). 总之,我将向你展示一个关于此特性的有趣边缘案例。但在深入之前,我们先从简单的开始。这是一个声明符声明其自身的声明:int f(int [sizeof(f())]);。这声明了一个带有一个参数的函数,该参数是一个数组(会退化为指针)。标识符是在声明符完成后才插入作用域的。因此,当在数组声明符内调用 f() 时,它尚未进入作用域,因此它被声明为 int ()。随后声明符结束,f 被重新声明为兼容类型 int (int *)

i’m now going to change the declaration by adding one character. int f(int f[sizeof(f())]); ok cool so like what the fuck. is this legal? no, really, is this legal? i’ll spoil it: clang compiles this just fine; gcc errors out. 现在我通过增加一个字符来修改这个声明:int f(int f[sizeof(f())]);。好吧,这到底是什么鬼?这合法吗?不,说真的,这合法吗?我先剧透一下:Clang 可以正常编译,而 GCC 会报错。

so as always whenever compilers disagree on semantics, we consult the holy script: ANSI X.3-159-1989, 3.3.2.2 Function calls: If the expression that precedes the parenthesized argument list in a function call consists solely of an identifier, and if no declaration is visible for this identifier, the identifier is implicitly declared exactly as if, in the innermost block containing the function call, the declaration extern int identifier(); appeared. 因此,每当编译器在语义上产生分歧时,我们总是查阅“圣经”:ANSI X.3-159-1989, 3.3.2.2 函数调用:如果函数调用中括号参数列表之前的表达式仅由一个标识符组成,且该标识符没有可见的声明,则该标识符会被隐式声明,就像在包含该函数调用的最内层块中出现了 extern int identifier(); 声明一样。

the key phrase here is “innermost block”. the standard never elaborates on what this means. intuitively, you would think this means block scope (i.e. compound statement, pretty much), so the code should be legal. but the declaration can also appear in file scope, outside of any block. implicit declarations in file scope are never explicitly disallowed, and gcc and clang both allow them. so another interpretation is that it actually means innermost scope. 这里的关键短语是“最内层块”(innermost block)。标准从未详细说明这意味着什么。直觉上,你会认为这意味着块作用域(即复合语句),因此代码应该是合法的。但声明也可以出现在文件作用域中,即任何块之外。文件作用域中的隐式声明从未被明确禁止,GCC 和 Clang 都允许这样做。因此,另一种解释是它实际上指的是“最内层作用域”。

in that case, then f() would be declared in function prototype scope, inside of the function declarator. if this is the case, then the parameter f would be an invalid redeclaration (redeclaring int () as int), so the code should be illegal. one hint that this may not be the correct interpretation is that the implicit declaration is specified to have the form extern int (). the extern specifier isn’t allowed inside function prototype scope, so maybe it’s meant to be excluded here. maybe “innermost block” actually means innermost block scope or file scope, and so the code really is legal. 在这种情况下,f() 将在函数原型作用域内(即函数声明符内部)被声明。如果是这样,那么参数 f 将是一个无效的重声明(将 int () 重声明为 int),因此代码应该是违法的。一个暗示这种解释可能不正确的线索是,隐式声明被指定为 extern int () 形式。extern 说明符在函数原型作用域内是不允许的,所以也许它在这里是被排除的。也许“最内层块”实际上指的是最内层块作用域或文件作用域,因此代码确实是合法的。

here’s another example, which confirms that the divergence in behavior comes down to which scope the implicit declaration goes in. here, clang errors and gcc succeeds (the opposite of the previous example): int main(void) { int f(int [sizeof(x())]); int x; } 这是另一个例子,证实了行为差异归结为隐式声明进入了哪个作用域。在这里,Clang 报错而 GCC 成功(与上一个例子相反):int main(void) { int f(int [sizeof(x())]); int x; }

also note that, in gcc, the implicit declaration in function prototype scope doesn’t decay to a pointer, even though function types in function prototype scope are normally impossible. gcc errors out when trying to compile the following, because f is redeclared as int (*)(): int f(int [sizeof(f())], int f()); this is yet another hint that clang’s behavior may be more “correct”. 还要注意,在 GCC 中,函数原型作用域内的隐式声明不会退化为指针,尽管函数原型作用域内的函数类型通常是不可能的。GCC 在尝试编译以下代码时会报错,因为 f 被重声明为 int (*)()int f(int [sizeof(f())], int f()); 这又是另一个暗示,表明 Clang 的行为可能更“正确”。

because the problematic behavior was removed from the standard over 27 years ago, we’ll never have closure on what the intended interpretation is. 由于这种有问题的行为在 27 年前就已从标准中移除,我们永远无法确定其预期的解释是什么。

unrelated footnote p.s. i am begging people to stop using c89. please. c11 was finalized over 15 years ago, it’s been enough time, i promise you it will be ok “but i’m writing code for a niche microprocessor made by a company that went bankrupt 25 years ago and it’s only supported by this one proprietary c89 compiler and the machine will self-destruct if you attempt to use anything else” look if you have a reason why you genuinely have no other option then you know who you are. most people have no excuse. 无关脚注 附:我恳求大家停止使用 C89。拜托了。C11 标准在 15 年前就已经定稿了,时间已经够久了,我保证一切都会好起来的。“但我正在为一家 25 年前就倒闭的公司生产的小众微处理器编写代码,它只支持这个专有的 C89 编译器,而且如果你尝试使用其他任何东西,机器就会自毁。” 听着,如果你确实有理由说明你别无选择,那么你自己心里清楚。但大多数人并没有借口。