C89 中的一个永远无法修复的歧义
An ambiguity in c89 which will never be fixed

原始链接: https://sebsite.pw/w/20260810-c89ambiguity.html

C89 标准对隐式函数声明的规则存在歧义——该特性在 C99 中已被移除,而在 C89 中,调用未声明的函数会将其隐式定义为 `extern int ()`。 GCC 和 Clang 对标准中“最内层块”要求的解读不同,导致在处理涉及函数原型的复杂边界情况时产生冲突。其核心分歧在于隐式声明是应存在于函数原型作用域内,还是更广的文件或块作用域内。如果进入原型作用域,该声明在技术上往往无效,从而导致编译错误;如果进入文件作用域,代码则可能编译通过。 这些不一致性凸显了依赖传统 C89 行为的风险。由于该特性在几十年前就已被正式废弃,目前不存在绝对“正确”的解读,编译器厂商只能自行抉择。作者最后强烈建议开发者放弃 C89,并指出像 C11 这样的现代标准早已解决了此类歧义。

最近的一场 Hacker News 讨论指出,C89 标准中存在一个至今未决的歧义,原因在于该有问题的行为早在 27 年前就已经被废弃。尽管一些评论者认为现代编译器能够捕捉到此类问题,使得这种歧义在实践中无关紧要,但另一些人则认为这一历史性的怪癖在学术上十分迷人。讨论强调,C 语言的演进——包括 C11 等后续标准——已经使许多此类“怪异”行为变得过时,实际上也封死了对原始标准进行任何官方澄清的可能。
相关文章

原文

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.

联系我们 contact @ memedata.com