an ambiguity in c89 which will never be fixed

Lobsters Hottest News

Summary

A blog post examines an ambiguity in the C89 standard about implicit function declarations, where GCC and Clang disagree on interpretation, leading to different compile results for certain code.

<p><a href="https://lobste.rs/s/p3mmqr/ambiguity_c89_which_will_never_be_fixed">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 08/10/26, 08:57 AM

# an ambiguity in c89 which will never be fixed Source: [https://sebsite.pw/w/20260810-c89ambiguity.html](https://sebsite.pw/w/20260810-c89ambiguity.html) 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 footnotep\.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\.

Similar Articles

the perils of parsing type inference declarations in c

Lobsters Hottest

This blog post explores parsing ambiguities in C23 involving `auto` as a type inference specifier or storage-class specifier, showing how GCC and Clang disagree on parsing declarations like `auto x = 67;` when `x` is a typedef, and how attributes complicate the situation.

Everything in C is undefined behavior

Hacker News Top

An experienced C++ developer argues that all non-trivial C and C++ code contains undefined behavior, making memory safety impossible and calling into question the continued use of these languages in modern software development.

On C extensions, portability, and alternative compilers

Lobsters Hottest

This article discusses the practical challenges of writing portable C code due to reliance on non-standard compiler extensions and glibc's conditional headers, illustrating with examples from building a C compiler.

When can the C++ compiler devirtualize a call?

Hacker News Top

Explores when C++ compilers can devirtualize virtual function calls, covering cases like known dynamic types and final keyword, with comparisons across GCC, Clang, MSVC, and ICC.

C++ float-to-int conversion can be undefined behavior

Lobsters Hottest

A blog post highlights that C++ float-to-int conversion is undefined behavior when the value doesn't fit, and points out that Microsoft's GSL library's safe narrowing function 'gsl::narrow' also suffers from this UB, contrary to its documentation.