Do you need absolutely need to use extern with an incomplete type for instance int a[];
for it to use an array a definition in a linked file? My logic is that it doesn't reserve memory so it's not a definition but a declaration (like a function prototype, which also doesn't require extern for the compiler to leave it to the linker implicitly). I would test it myself but I can't currently.

- 4,129
- 1
- 32
- 42
-
2That wouldn't be a completely insensible rule if it were part of the standard, but there is certain kind of simplicity/elegance to having fewer special cases: every filescope object declaration that wants to be just a declaration needs an `extern`, no exceptions. – Petr Skocik Mar 19 '20 at 14:47
2 Answers
Since you ask do you absolutely need to use extern
with a declaration of an identifier with a nominally incomplete type, the answer is technically “no,” for two reasons:
- The C standard is voluntary. Nothing requires you to obey it.
- If you are using the C standard, and you declare
int a[];
externally (outside any function) in one translation unit andint a[5] = { 3, -7, 24, 5, 7 };
inside another translation unit, and you usea
in the program, the behavior is not defined by the C standard. That is, the C standard “allows” you to do it but does not define the result.
I will come back to explain why the latter is not defined. First, let’s see why the answer to the question you actually wanted to ask is “yes.”
If instead you ask do you need to use extern
to get a defined result, and presumably the result that you want, then the answer is “yes.” If you declare int a[];
in a translation unit, it is a tentative definition per C 2018 6.9.2 2:
A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier
static
, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.
This means that, if you declare int a[];
externally and do not otherwise define it in the same translation unit (the source file with all the included files), it is as if you wrote int a[] = { 0 };
, which defines it to be an array of one element. So it is effectively a definition, not just a declaration.
To prevent it from being a tentative definition and becoming a definition, you need to declare it as extern int a[];
.
If you do not, then you will have this definition in one source file and the definition in the other file you are linking. Then, if you use a
in the program, C 2018 6.9 5 is violated:
… If an identifier declared with external linkage is used in an expression (other than as part of the operand of a
sizeof
or_Alignof
operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.
This “shall” is part of the semantics of external definitions, not part of the constraints, so it is governed by 4 2:
If a “shall” or “shall not” requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined…
This explains my second bullet point above.
Adding to that, though, this is one instance where the “undefined behavior” of the C standard is actually in some common use. In common Unix tools, a tentative definition in one translation unit is resolved as desired with a non-tentative definition in other translation units. So “no” is also the answer to your intended question provided you are using tools that support this.

- 195,579
- 13
- 168
- 312
-
1I'd heartily recommend *against* relying on the linker merging duplicate definitions. Certainly that has been a default behavior for many Unix-style linkers, but it can be disabled in at least some of these, and it is now disabled by default in some widely used ones. See [the current GCC docs](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html), for example (the relevant option for that compiler is `-fcommon` / `-fno-common`). – John Bollinger Mar 19 '20 at 15:05
-
I was surprised by the _tentative definition_ resulting in `int a[] = { 0 };` because I didn't think `0` was the same as `{ 0 }`. But then I saw 6.9.2p5 EXAMPLE 2 which agrees with you. – Ian Abbott Mar 19 '20 at 15:40
-
actually, yeah if I had just put int a[]; in repl.it then it literally says 'main.c:2:5 warning: tentative array definition assumed to have one element'. Also, i'm pretty sure that sizeof would not work if extern int a[]; with no length information were used. i.e. The array would decay. – Lewis Kelsey Mar 20 '20 at 12:19
-
1@LewisKelsey: `a` would not be converted to a pointer in `sizeof`. If its type is incomplete where `sizeof` is used, it violates the constraint in C 2018 6.5.3.4 1: “The `sizeof` operator shall not be applied to an expression that has function type or an incomplete type…” So the compiler must diagnose it. (If there is no definition in the translation unit, the type is completed as described above when the end of the translation unit is reached, but it is incomplete up to that point.) – Eric Postpischil Mar 20 '20 at 12:27
I used repl.it (clang) and it has an interesting result.
Scenario 1:
#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
extern int a[];
int main()
{
printf("%d", a[1]); // '2'
printf("%lu", sizeof(a)); // error 'invalid application of sizeof to incomplete type int []'
return 0;
}
Scenario 2:
#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
int a[];
int main()
{
printf("%d", a[1]); // '2'
printf("%lu", sizeof(a)); // error 'invalid application of sizeof to incomplete type int []'
return 0;
}
Scenario 3:
#include<stdio.h>
extern int a[];
int main()
{
printf("%d", a[1]); // '0'
printf("%lu", sizeof(a)); // error 'invalid application of sizeof to incomplete type int []'
return 0;
}
Scenario 4:
#include<stdio.h>
int a[5] = {1,2,3};
#include<stdio.h>
extern int a[2];
int main()
{
printf("%d", a[1]); // '2'
printf("%lu", sizeof(a)); // '8'
return 0;
}
Scenario 5:
#include<stdio.h>
int a = 2;
#include<stdio.h>
int a;
int main()
{
printf("%d", a); // '2'
return 0;
}
Only static int a;
will produce '0' but int a;
appears to be taken implicitly as extern int a;
although (extern) int a = 1;
would be taken as a multiple definition error if initialised in the other file (if not initialised in the other file i.e. int a;
, the other file will use the overridden extern initialisation in the main file). (extern) int a;
in one file and int a;
in the other file causes a single zero-initialised uninitialised int a to be used in both files. extern int a;
and nothing in the other file causes an error.
Refer to this as to why.

- 4,129
- 1
- 32
- 42