I found that my project sets
GCC_NO_COMMON_BLOCKS = NO
under Apple LLVM Compiler 3.1 - Code Generation settings, as "No Common Blocks"
I would like to know: what is that flag used for?
Thanks a lot
I found that my project sets
GCC_NO_COMMON_BLOCKS = NO
under Apple LLVM Compiler 3.1 - Code Generation settings, as "No Common Blocks"
I would like to know: what is that flag used for?
Thanks a lot
From Xcode's quick help:
In C, allocate even uninitialized global variables in the data section of the object file, rather than generating them as common blocks. This has the effect that if the same variable is declared (without extern ) in two different compilations, you will get an error when you link them. The only reason this might be useful is if you wish to verify that the program will work on other systems which always work this way.
You can find the quick help in the right pane, under the "Show Quick Help Inspector" tab:
YES
by default in order to avoid unexpected behaviour when accidentally defined the same variable in 2+ modules. Meanwhile, you might have to set it to NO
for backwards compatibility with code you don't own. –
Rutty On the stack it's very clear when you declare and when you define a variable. Assume we are inside a function.
int x;
Code above declares a variable x
to exist and to be of type int
. That's a declaration. As long as a variable is only declared but never used, the compiler doesn't even have to materialize it.
x = 10;
Code above defines x
to be 10
. That's a definition. You can only define a variable that has been declared previously and the compiler will materialize it, however, when optimization is performed and optimization concludes, that it isn't ever used aside from the definition, the compiler can decide not to materialize it.
int x = 10;
Code above does both at the same time, it's a combined declaration and definition. This way it is ensured that x
has a defined value when being used, as otherwise it will be undefined on first use (which is not relevant, if the first use is a definition).
But now lets assume we are outside of a function, what is the following
int GlobalValue;
?
If you said that's also a declaration, you are of course ... wrong! This is a combined declaration and definition, as global variable are implicit initialized to be all zero, so the code above is equivalent to
int GlobalValue = 0;
You cannot declare a global variable without defining it. All you can do is forward declare it. A forward declaration is the way of telling the compiler "this thing is declared elsewhere and you can expect it to de be declared as ...".
You certainly know forward declarations from functions. The following code does not compile:
void printSum( int a, int b )
{
printf("%d\n", sumOf(a, b));
}
int sumOf( int a, int b )
{
return a + b;
}
The moment you are calling sumOf()
, the compiler has no idea what parameters that function expects and what result it returns. To fix that, you add a forward declaration before printSum()
:
int sumOf( int a, int b );
Now the code compiles. This is again a declaration, but more specific, it is a forward declaration. And the same thing can be done with a variable. The following won't compile in a function but it compiles fine in global scope:
int GlobalValue;
int GlobalValue = 10;
As by re-declaring a global variable a second time, the first declaration becomes a forward declaration and the variable is now also not initialized to zero anymore but directly to 10, no matter where in the code the final declaration is:
int a;
int main( )
{
printf("%d\n", a);
return 0;
}
int a = 10;
Code above prints 10
.
However, that re-declaration only works within a single compilation unit.
Assume you have two C files. We call the first one FileA.c
and it has the following content
// FileA.c
int GlobalValue = 10;
And to make sure other code can use that variable, we also create a header file, name FileA.h
with the following content
// FileA.h
int GlobalValue;
Last but not least, we have a second file, named FileB.c
and it has the content
// FileB.c
#include "FileA.h"
#include <stdio.h>
int main( )
{
printf("%d\n", GlobalValue);
return 0;
}
What will happen when we try to build and run that without using common blocks? Hmm... let's find out:
# clang -fno-common -o test FileA.c FileB.c
duplicate symbol '_GlobalValue' in:
/private/var/folders/xxx/vg4fw0d57pgf9n3qwzwqts0c0000gn/T/FileB-ef1566.o
/private/var/folders/xxx/vg4fw0d57pgf9n3qwzwqts0c0000gn/T/FileA-46f5ae.o
ld: 1 duplicate symbols
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Oopsie. How could that fail? Well, what does FileB.c
look like after including FileA.h
? It looks like this
// FileB.c
// FileA.h
int GlobalValue;
#include <stdio.h>
int main( )
{
printf("%d\n", GlobalValue);
return 0;
}
How would the compiler know, that int GlobalValue;
is a forward declaration to a variable in FileA.c
? There is only this one declaration of it, so it becomes a combined declaration and definition, just as if I had written
int GlobalValue = 0;
But now FileB.c
defines a global variable named GlobalValue
and so does FileA.c
. Everywhere FileA.h
is included, a new variable is declared and defined. So how do I tell the compiler, that a variable named GlobalValue
of type int
is already defined somewhere else and only needs to be resolved when everything is liked together?
That's what the magical C keyword extern
is for!
(in case you ever wondered what this good for)
So let's fix it. Let's change the header file to
// FileA.h
extern int GlobalValue;
And here we go
# clang -fno-common -o test FileA.c FileB.c && ./test
10
In the past, compilers would usually place global variables in the common block instead, so let's remove extern
again from the header file and instead build with the following command
# clang -fcommon -o test FileA.c FileB.c && ./test
10
and it works as well.
The advantage is that now you don't have to use extern
all over your header files for global variables. And a lot of old C headers didn't use extern
when they should have and that's why this behavior is still supported and that's why disabling common blocks can break existing code bases.
The disadvantage of using common blocks is, that when I accidentally name my global variable the same as an already existing one in any other code file, these two are in fact seen as a the same variable by the compiler, which may not have been my intention but the compiler will not warn me about that. When I change the value of my variable, I might accidentally change the value also for other code I was not even aware of having such a variable.
And as the GCC documentation writes:
This behavior is inconsistent with C++, and on many targets implies a speed and code size penalty on global variable references. It is mainly useful to enable legacy code to link without errors.
So when you mix in C++, you may run into issues and on top of that, placing variables in common blocks leads to bigger and slower code on some systems. So you actually don't really want that behavior anymore and if you write new code, you should always disable it and only use it when building old legacy code.
In case you wonder, why you don't have to do the same thing for functions: The compiler can clearly recognize a forward function declaration (if it has no body, it's a forward declaration) and function forward declarations are extern
by default, that's why it isn't wrong to use extern
for them in header files but it also isn't necessary and will change nothing.
Please note that up to Clang 16, -fcommon
was the default, so if you didn't want that, you had to explicitly use -fno-common
.
Starting with Clang 17, -fno-common
is the new default and you must specify -fcommon
if you require the old behavior.
The latest Xcode version (Xcode 15.2) still uses Clang 15. Apple is always a bit behind here, as they have their own fork for Clang and merging the latest Clang code base into their fork and fixing resulting conflicts probably takes a while.
© 2022 - 2024 — McMap. All rights reserved.