What is GCC_NO_COMMON_BLOCKS used for?
Asked Answered
A

2

33

I found that my project sets

GCC_NO_COMMON_BLOCKS = NO 

under Apple LLVM Compiler 3.1 - Code Generation settings, as "No Common Blocks"

enter image description here

I would like to know: what is that flag used for?

Thanks a lot

Anhydride answered 27/6, 2012 at 19:1 Comment(0)
R
50

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: Xcode Quick Help Inspector

Reine answered 27/6, 2012 at 19:6 Comment(9)
Thanks! I didn't know that XCode's quick help also helped with build settings and that stuff. That would have saved me some time.Anhydride
This option seems to be turned on by Xcode 8 recommended settings.Halftone
the problem of it being on by default in Xcode 8 is it will mess a whole bunch of existing projects up by probably a vast majority of developers over many years.. and the "error" generated in most cases (if not all) will not point to which variables are "common", nor does the error give a hint that it is even related to "common" variables, nor is there any hint that the error's root was an (automatic) compiler setting change that caused the difference.Conciliar
This setting was turned on by Xcode 9 recommended settings and broke my build!Deon
I read the quick help and still don't know why I would want to do this or not. Xcode 9 changed this setting on one of my projects and broke the build. I was able to modify my code so that I could retain the YES setting for common blocks, but I still don't know why this is recommended and what its benefit might be.Deon
Saved me. I've spend ~1.5 days struggling with linker errors.Mccready
But what does this do?Inbound
From GCC docs: "This inhibits the merging of tentative definitions by the linker so you get a multiple-definition error if the same variable is accidentally defined in more than one compilation unit". As I understand, one should set this to 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
@ser16217248 Have a look at my reply. It explains in all detail what the consequences are of (not) using common blocks: https://mcmap.net/q/161301/-what-is-gcc_no_common_blocks-used-forSharecrop
S
0

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.

And what about functions?

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.

Note

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.

Sharecrop answered 19/1 at 17:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.