I'm currently wondering why I don't get an error from GCC during compilation/linking of a small C program.
I declared in version.h the following string:
const char* const VERSION;
In version.c I have set the initialization of the variable:
const char* const VERSION = "0.8 rev 213";
No problem with that. I can use the string in the rest of the program.
If the c file is missing, no error occurs during compilation/linking but the program fails with SIGSEGV (of course) when it tries to access the variable.
Is my way of setting up the variable VERSION correct or is there a better way? Or is there a chance to get an error during compilation/linking?
You have defined (not just declared) a variable in the header.
If you ever include this header from more than one source file, the behaviour is undefined. Here's the relevant quote from the standard:
J.2 Undefined behavior
…
An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier.
…
You are relying on a GCC-specific (actually common to many compilers, but still non-standard) behaviour here, which is merging of duplicate tentative data definitions. See help for -fcommon and -fno-common GCC compilation flags. Not all compilers behave this way. Historically this is the common behaviour for linkers, because that's how Fortran worked before there was C.
Assuming this language extension, one of the definitions (one that has an explicit initializer) initialises the variable to point to your string literal. But if you omit this definition, it will remain zero-initialised. That is, it will be a constant null pointer. Not very useful.
To make long story short, never ever do that. In order to declare (but not define) a global variable in a header, use extern. If you do, and try to omit a definition elsewhere, you will likely get a linker error (though the standard does not require diagnostic for this violation, all known implementation produce one).
Your example works because of a Fortran-inspired (mis)feature of C (but not C++) called tentative definitions (6.9.2p2) which is commonly but nonstandardly extended to multiple files.
Tentative definitions are variable declarations without extern and with
no initializer. In common implementations (pun intended), tentative definitions create a special kind of symbol which is called a common symbol. During linking, in the presence of a regular symbol of the same name, other common symbols become references to the regular symbol which means all the empty VERSIONs in your translation units created there due to inclusions will become references to the regular symbol const char* const VERSION = "0.8 rev 213";. If there's no such regular symbol, the common symbols will get merged into a single zero-initalized variable.
I don't know how to get a warning against this with a C compiler.
This
const char* const VERSION;
const char* const VERSION = "0.8 rev 213";
seems to work with gcc no matter what I've tried (g++ won't accept it -- C++ doesn't have the tentative definition feature and it doesn't like const variables that aren't explicitly initialized). But you can compile with -fno-common (which is a fairly "common" (and highly recommended) nonstandard option (gcc,clang, and tcc all have it)) and then you will get a linker error if the non-initialized and the initialized extern-less declarations are in different translation units.
Example:
v.c:
const char * VERSION;
main.c
const char* VERSION;
int main(){}
compilation and linking:
gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled
(I removed the second const in the example for the sake of the C++ example — C++ additionally makes const globals static or something like that which only complicates the demonstration of the tentative definition feature.)
With tentative definitions disabled or with C++, all your variable declarations in headers should have extern in them
version.h:
extern const char* const VERSION;
and you should have exactly one definition for each global and that definition should have an initializer or be without extern (some compilers will warn if you apply extern to an initialized variable).
version.c:
#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";
In version.h you should declare VERSION as a extern like
extern const char* const VERSION;
And in version.c you should define the extern variable like
const char* const VERSION = "0.8 rev 213";
EDIT :- Also you need to make sure that only one source file defined the variable VERSION while the other source files declared it extern and you can do it by defining it in a source file VERSION.c and put the extern declaration in a header file VERSION.h.
Related
I was wondering why declaring a global variable in a header file which is included in multiple source in C was working
#ifndef INC_MAIN_H_
#define INC_MAIN_H_
int my_var;
#endif
But assigning it a default value does not work:
#ifndef INC_MAIN_H_
#define INC_MAIN_H_
int my_var = 0;
#endif
It shows multiple compiling errors in each files where I include this main.h
multiple definition of `my_var'; app/src/testfile.o:app/inc/main.h:4: first defined here
I know declaring global variables this way is not the best practice, but I can't figure out why adding an assignation would cause compiling errors and I can't find clear answers about it.
With the build tools and switches you are using, int my_var; acts mostly like a declaration that is not a definition. Multiple declarations of an object identifier are allowed.
int my_var = 0; is a definition. There should be only one definition.
Due to the history of C use and development, int my_var; is technically a tentative definition. If there is no regular definition of my_var in the translation unit (the source file being compiled, including all the files it includes), it acts as a regular definition.
However, then when you have this in a header file that is included in multiple source files, you have multiple definitions. When there are multiple definitions of the same object identifier with external linkage, the C standard does not define the behavior.
When there are multiple regular definitions, your build tools report an error. However, they treat definitions that came from tentative definitions differently: They allow them and coalesce them into a single definition. Again, this is due to the history of how C developed and was used. Also, this behavior changed in GCC recently. Prior to GCC version 10, tentative definitions were coalesced by default, as described above. With GCC 10 and later, tentative definitions are not coalesced, and multiple definitions will result in an error. The old behavior can be requested with the switch -fcommon.
To avoid tentative definitions, you should declare the identifier in the header as extern int my_var;, which is just a declaration and not a tentative definition, and you should have exactly one source file containing int my_var = 0;, which is a regular definition and not a tentative definition.
Additional information is here, here, and here.
I have these dummy piece of software made of 3 files:
test.h
int gv;
void set(int v);
test.c
#include "test.h"
void set(int x) {
gv = x;
}
main.c
#include "test.h"
#include <assert.h>
int main() {
set(1);
assert(gv == 1);
}
The code compiles and run fine in both MSVC 2019 and GCC 8, but with clang (clang-cl 11 supplied by Visual Studio 2019) fails at link time complaining about gv already defined:
1>------ Build started: Project: test, Configuration: Debug x64 ------
1>lld-link : error : undefined symbol: gv
1>>>> referenced by ...\test\main.c:6
1>>>> x64\Debug\main.obj:(main)
1>>>> referenced by ...\test\test.c:4
1>>>> x64\Debug\test.obj:(set)
1>Done building project "test.vcxproj" -- FAILED.
I understand that extern is the default storage-class specifier for objects defined at file scope, but if I explicitly specify extern to int gv, it breaks the linkage with every compiler (unless I add a definition for gv in a source file, of course).
There is something that I do not understand. What is happening?
int gv; is a tentative definition of gv, per C 2018 6.9.2 2. When there is no regular definition in a translation unit (the file being compiled along with everything it includes), a tentative definition becomes a definition with an initializer of zero.
Because this tentative definition is included in both test.c and main.c, there are tentative definitions in both test.c and main.c. When these are linked together, your program has two definitions.
The C standard does not define the behavior when there are two definitions of the same identifier with external linkage. (Having two definitions violates the “shall” requirement in C 2018 6.9 5, and the standard does not define the behavior when the requirement is violated.) For historic reasons, some compilers and linkers have treated tentative definitions as “common symbol” definitions that would be coalesced by the linker—having multiple tentative definitions of the same symbol would be resolved to a single definition. And some do not; some treat tentative definitions more as regular definitions, and the linker complains if there are multiple definitions. This is why you are seeing a difference between different compilers.
To resolve the issue, you can change int gv; in test.h to extern int gv;, which makes it a declaration that is not a definition (not even a tentative definition). Then you should put int gv; or int gv = 0; in test.c to provide one definition for the program. Another solution could be to use the -fcommon switch, per below.
The default behavior changed in GCC version 10 (and possibly Clang at some point; my Apple Clang 11 behaves differently from your report). With GCC and Clang, you can select the desired behavior with the command-line switch -fcommon (to treat tentative definitions as common symbols) or -fno-common (to cause a linker error if there are multiple tentative definitions).
Some additional information is here and here.
I understand that extern is the default storage-class specifier for objects defined at file scope
That's true but the linkage breaks because of "redefinition" of the gv symbol, isn't it?
That's because both test.c and main.c have int gv; after the preprocessor includes the headers. Thus eventually both objects test.o and main.o contain _gv symbol.
The most common solution is to have extern int gv; in the test.h header file (which tells the compiler that gv storage is allocated somewhere else). And inside the C file, main.c for example, define int gv; so that the storage for gv will be actually allocated but only once, inside main.o object.
EDIT:
Referring the same link you provided storage-class specifier, which contains the following statement:
Declarations with external linkage are commonly made available in header files so that all translation units that #include the file may refer to the same identifier that are defined elsewhere.
I am trying to implement a global singleton variable in the header-only library in C (not C++). So after searching on this forum and elsewhere, I came across a variation of Meyer's singleton that I am adapting to C here:
/* File: sing.h */
#ifndef SING_H
#define SING_H
inline int * singleton()
{
static int foo = 0;
return &foo;
}
#endif
Notice that I am returning a pointer because C lacks & referencing available in C++, so I must work around it.
OK, now I want to test it, so here is a simple test code:
/* File: side.h */
#ifndef SIDE_H
#define SIDE_H
void side();
#endif
/*File: side.c*/
#include "sing.h"
#include <stdio.h>
void side()
{
printf("%d\n",*(singleton()));
}
/*File: main.c*/
#include "sing.h"
#include "side.h"
#include <stdio.h>
int main(int argc, char * argv[])
{
/* Output default value - expected output: 0 */
printf("%d\n",*(singleton()));
*(singleton()) = 5;
/* Output modified value - expected output: 5 */
printf("%d\n",*(singleton()));
/* Output the same value from another module - expected output: 5*/
side();
return 0;
}
Compiles and runs fine in MSVC in C mode (also in C++ mode too, but that's not the topic). However, in gcc it outputs two warnings (warning: ‘foo’ is static but declared in inline function ‘singleton’ which is not static), and produces an executable which then segfaults when I attempt to run it. The warning itself kind of makes sense to me (in fact, I am surprised I don't get it in MSVC), but segfault kind of hints at the possibility that gcc never compiles foo as a static variable, making it a local variable in stack and then returns expired stack address of that variable.
I tried declaring the singleton as extern inline, it compiles and runs fine in MSVC, results in linker error in gcc (again, I don't complain about linker error, it is logical).
I also tried static inline (compiles fine in both MSVC and gcc, but predictably runs with wrong output in the third line because the side.c translation unit now has its own copy of singleton.
So, what am I doing wrong in gcc? I have neither of these problems in C++, but I can't use C++ in this case, it must be straight C solution.
I could also accept any other form of singleton implementation that works from header-only library in straight C in both gcc and MSVC.
I am trying to implement a global singleton variable in the header-only library in C (not C++).
By "global", I take you to mean "having static storage duration and external linkage". At least, that's as close as C can come. That is also as close as C can come to a "singleton" of a built-in type, so in that sense, the term "global singleton" is redundant.
Notice that I am returning a pointer because C lacks & referencing available in C++, so I must work around it.
It is correct that C does not have references, but you would not need either pointer or reference if you were not using a function to wrap access to the object. I'm not really seeing what you are trying to gain by that. You would likely find it easier to get what you are looking for without. For example, when faced with duplicate external defintions of the same variable identifier, the default behavior of all but the most recent versions of GCC was to merge them into a single variable. Although current GCC reports this situation as an error, the old behavior is still available by turning on a command-line switch.
On the other hand, your inline function approach is unlikely to work in many C implementations. Note especially that inline semantics are rather different in C than in C++, and external inline functions in particular are rarely useful in C. Consider these provisions of the C standard:
paragraph 6.7.4/3 (a language constraint):
An inline definition of a function with external linkage shall not contain a definition of a modifiable object with static or thread storage duration, and shall not contain a reference to an identifier with internal linkage.
Your example code is therefore non-conforming, and conforming compilers are required to diagnose it. They may accept your code nonetheless, but they may do anything they choose with it. It seems unreasonably hopeful to expect that you could rely on a random conforming C implementation to both accept your code for the function and compile it such that callers in different translation units could obtain pointers to the same object by calling that function.
paragraph 6.9/5:
An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression [...], somewhere in the entire program there shall be exactly one external definition for the identifier [...].
Note here that although an inline definition of a function identifier with external linkage -- such as yours -- provides an external declaration of that identifier, it does not provide an external definition of it. This means that a separate external definition is required somewhere in the program (unless the function goes altogether unused). Moreover, that external definition cannot be in a translation unit that includes the inline definition. This is large among the reasons that extern inline functions are rarely useful in C.
paragraph 6.7.4/7:
For a function with external linkage, the following restrictions apply: [...] If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.
In addition to echoing part of 6.9/5, that also warns you that if you do provide an external definition of your function to go with the inline definitions, you cannot be sure which will be used to serve any particular call.
Furthermore, you cannot work around those issues by declaring the function with internal linkage, for although that would allow you to declare a static variable within, each definition of the function would be a different function. Lest there be any doubt, Footnote 140 clarifies that in that case,
Since an inline definition is distinct from the corresponding external definition and from any other corresponding inline definitions in other translation units, all corresponding objects with static storage duration are also distinct in each of the definitions.
(Emphasis added.)
So again, the approach presented in your example cannot be relied upon to work in C, though you might find that in practice, it does work with certain compilers.
If you need this to be a header-only library, then you can achieve it in a portable manner by placing an extra requirement on your users: exactly one translation unit in any program using your header library must define a special macro before including the header. For example:
/* File: sing.h */
#ifndef SING_H
#define SING_H
#ifdef SING_MASTER
int singleton = 0;
#else
extern int singleton;
#endif
#endif
With that, the one translation unit that defines SING_MASTER before including sing.h (for the first time) will provide the needed definition of singleton, whereas all other translation units will have only a declaration. Moreover, the variable will be accessible directly, without either calling a function or dereferencing a pointer.
The GCC compiler and the Clang compilers behave differently, where the Clang allows a static variable to be declared before it is defined, while the GCC compiler treats the declaration (or "tentative definition") as a definition.
I believe this is a bug in GCC, but complaining about it and opening a bug report won't solve the problem that I need the code to compile on GCC today (or yesterday)...
Heres a fast example:
static struct example_s { int i; } example[];
int main(void) {
fprintf(stderr, "Number: %d\n", example[0].i);
return 0;
}
static struct example_s example[] = {{1}, {2}, {3}};
With the Clang compiler, the program compiles and prints out:
Number: 1
However, with GCC the code won't compile and I get the following errors (ignore line numbers):
src/main2.c:26:36: error: array size missing in ‘example’
static struct example_s { int i; } example[];
^~~~~~~
src/main2.c:33:25: error: conflicting types for ‘example’
static struct example_s example[256] = {{1}, {2}, {3}};
^~~~~~~
src/main2.c:26:36: note: previous declaration of ‘example’ was here
static struct example_s { int i; } example[];
Is this a GCC bug or a Clang bug? who knows. Maybe if you're on one of the teams you can decide.
As for me, the static declaration coming before the static definition should be (AFAIK) valid C (a "tentative definition", according to section 6.9.2 of the C11 standard)... so I'm assuming there's some extension in GCC that's messing things up.
Any way to add a pragma or another directive to make sure GCC treats the declaration as a declaration?
The C11 draft has this in §6.9.2 External object definitions:
3 If the declaration of an identifier for an object is a tentative definition and has
internal linkage, the declared type shall not be an incomplete type
I read this as saying that the first line in your code, which has an array of unspecified length, fails to be a proper tentative definition. Not sure what it becomes then, but that would kind of explain GCC's first message.
TL;DR
The short answer is that this particular construct is not allowed by the C11 standard -- or any other C standard going back to ANSI C (1989) -- but it is accepted as a compiler extension by many, though not all, modern C compilers. In the particular case of GCC, you need to not use -pedantic (or -pedantic-errors), which would cause a strict interpretation of the C standard. (Another workaround is described below.)
Note: Although you can spell -pedantic with a W, it is not like many -W options, in that it does not only add warning messages: What it does is:
Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions, and some other programs that do not follow ISO C and ISO C++.
Workarounds
It does not appear to be possible to suppress this error using a GCC #pragma, or at least the ones that I tried didn't have any effect. It is possible to suppress it for a single declaration using the __extension__ extension, but that seems to just be trading one incompatibility for another, since you would then need to find a way to remove (or macro expand away) __extension__ for other compilers.
Quoting the GCC manual:
-pedantic and other options cause warnings for many GNU C extensions. You can prevent such warnings within one expression by writing __extension__ before the expression. __extension__ has no effect aside from this.
On the GCC versions I had handy, the following worked without warnings even with -pedantic:
__extension__ static struct example_s { int i; } example[];
Probably your best bet it to just remove -pedantic from the build options. I don't believe that -pedantic is actually that useful; it's worth reading what the GCC manual has to say about it. In any event, it is doing its job here: the documented intent is to ban extensions, and that's what it is doing.
Language-lawyering
The language-lawyer justification for the above, taking into account some of the lengthy comment threads:
Definitions
An external declaration is a declaration at file scope, outside of any function definition. This shouldn't be confused with external linkage, which is a completely different usage of the word. The standard calls external declarations "external" precisely because they are outside any function definitions.
A translation unit is, thus, a sequence of external-declaration. See §6.9.
If an external declaration is also a definition -- that is, it is either a function declaration with a body or an object declaration with an initializer -- then it is referred to as an external definition.
A type is incomplete at a point in a program where there is not "sufficient information to determine the size of objects of that type" (§6.2.5p1), which includes "an array type of unknown size" (§6.2.5p22). (I'll return to this paragraph later.) (There are other ways for a type to be incomplete, but they're not relevant here.)
An external declaration of an object is a tentative definition (§6.9.2) if it is not a definition and is either marked static or has no storage-class specifier. (In other words, extern declarations are not tentative.)
What's interesting about tentative definitions is that they might become definitions. Multiple declarations can be combined with a single definition, and you can also have multiple declarations (in a translation unit) without any definition (in that translation unit) provided that the symbol has external linkage and that there is a definition in some other translation unit. But in the specific case where there is no definition and all declarations of a symbol are tentative, then the compiler will automatically insert a definition.
In short, if a symbol has any (external) declaration with an explicit extern, it cannot qualify for automatic definition (since the explicitly-marked declaration is not tentative).
A brief detour: the importance of the linkage of the first declaration
Another curious feature: if the first declaration for an object is not explicitly marked static, then no declaration for that object can be marked static, because a declaration without a storage class is considered to have external linkage unless the identifier has already been declared to have internal linkage (§6.2.2p5), and an identifier cannot be declared to have internal linkage if it has already been declared to have external linkage (§6.2.2p7). However, if the first declaration for an object is explicitly static, then subsequent declarations have no effect on its linkage. (§6.2.2p4).
What this all meant for early implementers
Suppose you're writing a compiler on an extremely resource-limited CPU (by modern standards), which was basically the case for all early compiler writers. When you see an external declaration for a symbol, you need to either give it an address within the current translation unit (for symbols with internal linkage) or you need to add it to the list of symbols you're going to let the linker handle (for symbols with external linkage). Since the linker will assign addresses to external symbols, you don't yet need to know what their size is. But for the symbols you're going to handle yourself, you will want to immediately give them an address (within the data segment) so that you can generate machine code referencing the data, and that means that you do need to know what size these objects are.
As noted above, you can tell whether a symbol is internally or externally linked when you first see a declaration for it, and it must be declared before it is used. So by the time you need to emit code using the symbol, you can know whether to emit code referencing a specific known offset within the data segment, or to emit a relocatable reference which will be filled in later by the linker.
But there's a small problem: What if the first declaration is incomplete? That's not a problem for externally linked symbols, but for internally-linked symbols it prevents you from allocating it to an address range since you don't know how big it is. And by the time you find out, you might have had to have emitted code using it. To avoid this problem, it's necessary that the first declaration of an internally-linked symbol be complete. In other words, there cannot be a tentative declaration of an incomplete symbol, which is what the standard says in §6.9.2p3:
If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type.
A bit of paleocybernetics
That's not a new requirement. It was present, with precisely the same wording, in §3.7.2 of C89. And the issue has come up several times over the years in the comp.lang.c and comp.std.c Usenix groups, without ever attracting a definitive explanation. The one I provided above is my best guess, combined with hints from the following discussions:
in 1990: https://groups.google.com/forum/#!msg/comp.std.c/l3Ylvw-mrV0/xPS0dXfJtW4J
in 1993: https://groups.google.com/d/msg/comp.std.c/abG9x3R9-1U/Ib09BSo5EI0J
in 1996: https://groups.google.com/d/msg/comp.lang.c/j6Ru_EaJNkg/-O3jR5tDJMoJ
in 1998: https://groups.google.com/d/msg/comp.std.c/aZMaM1pYBHA/-YbmPnNI-lMJ
in 2003: https://groups.google.com/d/msg/comp.std.c/_0bk-xK9uA0/dAoULatJIKwJ (I got several links from Fergus Henderson's post in this thread.)
in 2011: https://groups.google.com/d/msg/comp.lang.c/aoUSLbUBs7I/7BdNQhAq5DgJ
And it's also come up a few times on Stackoverflow:
What is the meaning of statement below that the declared type shall not be incomplete type
Why is this statement producing a linker error with gcc?
A final doubt
Although no-one in any of the above debates has mentioned it, the actual wording of §6.2.5p22 is:
An array type of unknown size is an incomplete type. It is completed, for an identifier of that type, by specifying the size in a later declaration (with internal or external linkage).
That definitely seems to contradict §6.9.2p3, since it contemplates a "later declaration with interal linkage", which would not be allowed by the prohibition on tentative definitions with internal linkage and incomplete type. This wording is also contained word-for-word in C89 (in §3.1.2.5), so if this is an internal contradiction, it's been in the standard for 30 years, and I was unable to find a Defect Report mentioning it (although DR010 and DR016 hover around the edges).
Note:
For C89, I relied on this file saved in the Wayback Machine but I have no proof that it's correct. (There are other instances of this file in the archive, so there is some corroboration.) When the ISO actually released C90, the sections were renumbered. See this information bulletin, courtesy wikipedia.
Edit: Apparently gcc was throwing an error due to the -Wpedantic flag, which (for some obscure reason) added errors in addition to warnings (see: godbolt.org and remove the flag to compile).
¯\_(ツ)_/¯
A possible (though not DRY) answer is to add the array length to the initial declaration (making a complete type with a tentative declaration where C11 is concerned)... i.e.:
static struct example_s { int i; } example[3];
int main(void) {
fprintf(stderr, "Number: %d\n", example[0].i);
return 0;
}
static struct example_s example[3] = {{1}, {2}, {3}};
This is super annoying, as it introduces maintenance issues, but it's a temporary solution that works.
I was playing with extern keyword in C when I encountered this strange behaviour.
I have two files:
file1.c
#include<stdio.h>
int main()
{
extern int a;
a=10;
printf("%d",a);
return 0;
}
file2.c
const int a=100;
When I compile these files together, there is no error or warning and when I run them, output comes to be 10. I had expected that the compiler should report an error on line a=10;.
Moreover, if I change the contents of file2.c to
const int a;
that is, if I remove the initialization of global const variable a and then compile the files, there is still no error or warning but when I run them, Segmentation Fault occurs.
Why does this phenomenon happen? Is it classified under undefined behaviour? Is this compiler- or machine- dependent?
PS: I have seen many questions related to this one, but either they are for C++ or they discuss extern only.
Compilation and linking are two distinct phases. During compilation, individual files are being compiled into object files. Compiler will find both file1.c and file2.c being internally consistent. During linking phase, the linker will just point all the occurrence of the variable a to the same memory location. This is the reason you do not see any compilation or linker error.
To avoid exactly the problem which you have mentioned, it is suggested to put the extern in a header file and then include that header file in different C file. This way compiler can catch any inconsistency between the header and the C file
The following stackoverflow also speaks about linker not able to do type checking for extern variables.
Is there any type checking in C or C++ linkers?
Similarly, the types of global variables (and static members of classes and so on) aren't checked by the linker, so if you declare extern int test; in one translation unit and define float test; in another, you'll get bad results.
It is undefined behaviour but the compiler won't warn you. How could it? It has no idea how you declare a variable in another file.
Attempting to modify a variable declared const is undefined behaviour. It is possible (but not necessary) that the variable will be stored in read-only memory.
This is a known behavior of C compilers. It is one of the differences between C and C++ where strong compile time type checking is enforced.
The segmentation fault occurs when trying to assign a value to a const, because the linker puts the const values in a read-only elf segment and writing to this memory address is a runtime (segmentation) fault.
but during compile time, the compiler does not check any "externs", and the C linker, does not test types. therefore it passes compilation/linkage.
Your program causes undefined behaviour with no diagnostic required (whether or not const int a has an initializer). The relevant text in C11 is 6.2.7/2:
All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.
Also 6.2.2/2:
In the set of translation units and libraries that constitutes an entire program, each declaration of a particular identifier with external linkage denotes the same object or function.
In C, const int a = 100; means that a has external linkage. So it denotes the same object as extern int a;. However those two declarations have incompatible type (int is not compatible with const int, see 6.7.2 for the definition of "compatible type").