Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is extern optional?

I am sure I am going crazy, but consider the following C code:

// file1.c
int first;

void f(void)
{ first = 2; }

// file2.c
#include <stdio.h>

int first;
void f();
int main(void)
{
    first = 1;
    f();
    printf("%d", first);
}

These two files, for some reason will compile and link together, and print 2. I was always under the impression that unless I labelled one or the other (but not both) definitions of first with extern, this wouldn't compile, and that was in fact the whole point of extern!

like image 946
Chris Jefferson Avatar asked Feb 22 '13 14:02

Chris Jefferson


2 Answers

It only compiles because first is only declared twice, there are not actually two places in memory but only one. Just initialize the one first with int first=4; and the other with int first=5; and your linker will show you the error, e.g. GCC:

b.o:b.c:(.data+0x0): multiple definition of `_first'
a.o:a.c:(.data+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
like image 50
Bernd Elkemann Avatar answered Nov 15 '22 20:11

Bernd Elkemann


Under normal conditions (no extra gcc flags) you should be fine to compile this code as:

gcc file1.c file2.c

What's going to happen is the compiler will see that you have two global variables named the same thing and neither is initialized. Then it will place your uninitialized global variables in the "common" section of the code**. In other words it's going to have only 1 copy of the "first" variable. This happens because the default for gcc is -fcommon

If you were to compile with the -fno-common flag you'd now receive the error you were thinking of:

/tmp/ccZNeN8c.o:(.bss+0x0): multiple definition of `first'
/tmp/cc09s2r7.o:(.bss+0x0): first defined here
collect2: ld returned 1 exit status

To resolve this you'd add extern to all but one of the variables.

WARNING:
Now let's say you had two global uninitialized arrays of different sizes:

// file1.c
int first[10];

// file2.c
int first[20];

Well guess what, compiling them with gcc -Wall file1.c file2.c produces no warnings or errors and the variable was made common even though it's differently sized!!!

 //objdump from file1.c:
 0000000000000028       O *COM* 0000000000000020 first

 //objdump from file2.c:
 0000000000000050       O *COM* 0000000000000020 first

This is one of the dangers of global variables.


**If you look at an objdump of the *.o files (you have to compile with gcc -c to generate them) you'll see first placed in the common (*COM*) section:

mike@mike-VirtualBox:~/C$ objdump -t file2.o

a.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 file2.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000004       O *COM*  0000000000000004 first
0000000000000000 g     F .text  0000000000000039 main
0000000000000000         *UND*  0000000000000000 f
0000000000000000         *UND*  0000000000000000 printf
like image 29
Mike Avatar answered Nov 15 '22 20:11

Mike