← 返回日报
精读 预计 3 分钟

an ambiguity in c89 which will never be fixed

摘要

作者在 C89 / C90 标准中发现一处措辞歧义:隐式函数声明(C99 已移除)在函数原型作用域中的行为,GCC 与 Clang 解释不一致。文章以两个代码示例展示分歧:一个 GCC 报错而 Clang 通过,另一个相反。作者引用标准 3.3.2.2 节中 “innermost block” 这一未定义措辞,讨论其应指块作用域还是最内层作用域,并指出 extern 说明符在函数原型作用域中不被允许等线索,暗示 Clang 的行为可能更正确。由于该特性早已从标准中移除,此歧义永远不会被澄清。文末附注作者恳请开发者停止使用 C89,改用 C11。

荐读理由

文中揭示的隐式函数声明在 C89 中的作用域歧义,能让你在写兼容旧标准代码时提前避开 gcc 与 clang 行为不一致的坑,避免被看似合法的括号改动带偏。

原文

an ambiguity in c89 which will never be fixed

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.

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)

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 *).

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. 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.

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. 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.

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;
}

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".

because the problematic behavior was removed from the standard over 27 years ago, we'll never have closure on what the intended interpretation is.

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.

Lobsters · 1 赞 · 0 评 讨论 → 阅读原文 →

这条对你有帮助吗?