2

Here I have an example project with two source files and a header file, as follows:

main.c:

#include<stdio.h>                                                               
#include "personal.h"                                                           
int main(){                                                                     
    i = 5;                                                                        
    printf("Value is %d\n",i);                                                    
    return 0;                                                                     
 }

sub.c:

#include "personal.h"                                                           
// do nothing

and finally personal.h:

#pragma once                                                                    
int i; 

Each of the .c file includes the personal.h, which is `guarded'. I compile with gcc, all goes fine:

>gcc sub.c main.c -o out
>./out 
Value is 5

But with g++, this happens:

>g++ sub.c main.c -o out
/tmp/cctYwVnO.o:(.bss+0x0): multiple definition of `i'
/tmp/ccPElZ27.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status

Is there anything fundamentally different between C++ and C in terms of how files are linked, preprocessor activity etc? I tried the same with other compilers like clang and the same happens. I am perhaps missing something silly here.

P. Nair
  • 79
  • 7
  • if I declare a const variable or a class in the included file 'personal.h' this error disappears and it compiles. – P. Nair Oct 29 '19 at 14:56
  • 6
    [This](https://stackoverflow.com/questions/8108072/c-include-guards-do-not-seem-to-be-working) explains why it does not work in C++. Not sure if C has the same rules or not. – NathanOliver Oct 29 '19 at 14:56
  • Does this answer your question? [What exactly is One Definition Rule in C++?](https://stackoverflow.com/questions/4192170/what-exactly-is-one-definition-rule-in-c) – Botje Oct 29 '19 at 14:58
  • C and C++ are different, maybe this is not an exception ;) – 463035818_is_not_an_ai Oct 29 '19 at 15:00
  • Initialize that variable in the header file and retry linking that in `C`. – PaulMcKenzie Oct 29 '19 at 15:03
  • @PaulMcKenzie , that clearly fails for gcc. Does 'int i' in C++ mean a definition and not a declaration, then? – P. Nair Oct 29 '19 at 15:08
  • @Botje No it does not. The answer talks about declaration in 'one' translational unit, which the guard '#pragma once' will take care of. My question is why does the C preprocessor do this across translational units belonging to the same project whereas C++ preprocessor doesn't? – P. Nair Oct 29 '19 at 15:11
  • @P.Nair I always was under the impression that multiply defined variables (not functions) was not a "real" error in `C`, and the linker handled it in whatever way it sees fit (of course under the control of a linker command-line switch of some sort). Maybe it's a MSLINK thing, but I recall seeing switches to determine what happens if multiply defined symbols were encountered. – PaulMcKenzie Oct 29 '19 at 15:14
  • @P.Nair `sub.c` and `main.c` are both translation units that each **define** `int i`. The resulting object files will contain symbols for `int i`, which results in duplicate definitions. The suggestion to change it to `extern int i` results in *declarations* of which you can have arbitrarily many. However, you then still need one definition to actually get the resulting program to link correctly. – Botje Oct 29 '19 at 15:19

2 Answers2

8

In C,

int i;

is a tentative definition. By the virtue of inclusion, you have a tentative definition for i in two compilation units. The C standard allows to have tentative definitions in multiple compilation units, but does not mandate that implementation accept that. The custom behavior for Unix C compilers is to allow it but gcc has an option (-fno-common) to prevent it and generate an error at link time (so that you can check the code for compilers, as I think Microsoft one, which does not allow it or for platforms for which that allow better code -- I know of none but that's a rationale given by GCC documentation).

IIRC, C++ has no such allowance.

Note that you probably want a declaration and not a definition in headers. Thus for the i above it should be

extern int i;

in the header and one

int i;

at global scope in one .c file.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • Finally, an actual answer. The behavior makes a lot of sense now. +1 – NathanOliver Oct 29 '19 at 15:14
  • If I could suggest improvements: Add short explanation on why this is tentative definition (it may not be obvious to beginner), and add advice to never use tentative definitions. – user694733 Oct 29 '19 at 15:18
  • What would then be a standard, safe practice to declare a global variable that can be accessed from different source files? Suppose I also have a print function in sub.c and it is called from main. How do I achieve that initializing i in main also reflect in this print function in sub.c? – P. Nair Oct 29 '19 at 15:23
  • @P.Nair again `extern int i` in the header, `int i` in one chosen implementation file. – AProgrammer Oct 29 '19 at 15:25
  • @AProgrammer I did that: extern int i in personal.h, int i=5 in main.c and a function void justPrint(){ printf("Just printing %d \n",i); } in sub.c (after including stdio.h). Compilation error this time is: /tmp/ccnBaxto.o: In function `justPrint()': sub.c:(.text+0x6): undefined reference to `i' collect2: error: ld returned 1 exit status – P. Nair Oct 29 '19 at 15:30
  • @P.Nair `int i` in `main.c` at global scope (i.e. outside the main function)? I'm so surprised that I won't test it here before you confirm. – AProgrammer Oct 29 '19 at 15:32
  • @AProgrammer, yes it works when int i is outside the scopes in either files. As you mentioned only if done in only one file. But this feel less elegant than how C could handle a global variable. Isn't an include file supposed to act in global scope? – P. Nair Oct 29 '19 at 15:39
  • 2
    @P.Nair, First, there is nothing magical about include files. They behave just as if their text was included at the inclusion place. Then rule that you can't define something is general, it is C which has a special case with the tentative definition, and this special case is not allowed by all compilers. – AProgrammer Oct 29 '19 at 15:44
1

sub.c will include personal.h and will create the variable i in global scope. Similarly, main.c will also include personal.h and create variable i in global scope. Eventually, when you link, there are two definitions of i in the global scope and hence the error.

  • The question is, how and why is this different in C? Why isn't the #pragma once taking care of the multiple declarations? – P. Nair Oct 29 '19 at 15:10
  • @P.Nair • `#pragma once` is in the context of a single translation unit. You have `#include "personal.h"` in two different translation units. – Eljay Oct 29 '19 at 15:11
  • @P.Nair `#pragma once` makes sure you don't include the file twice in the same translation unit. It doesn't prevent multiple definitions of the same identifier. In a header file you should use `extern int i;` and use `int i;` in exactly one translation unit. – Bodo Oct 29 '19 at 15:13
  • @Bodo See above – NathanOliver Oct 29 '19 at 15:16
  • @Bodo is right. And also, the reason why it is not giving an error in gcc might be due to optimization. The compiler probably sees that there is nothing in sub.c and hence nothing is compiled and so, there is nothing to be linked. – Ashwin Upadhyaya Oct 29 '19 at 15:16
  • 1
    In C, you can do this: `int i; int i; int i; int i; int i;`, and those multiple declarations are okay, and will coalesce into one definition if there is no assigning definition. In C++, those would be 5 colliding definitions. To do the same declarations in C++ you need to do them this way: `extern int i; extern int i; extern int i; extern int i; extern int i;`. – Eljay Oct 29 '19 at 15:31
  • @Eljay with gcc compiler, if you redeclare a variable within the same translation unit, it will throw and error and is not OK. The question here is not about redeclaration (because pragma once takes care of that), but about compilation units. – P. Nair Oct 29 '19 at 15:49
  • @P.Nair • I just used `gcc` (v9.2) on a C source file with the redeclaration of the same variable `int i; int i;` in the same translation unit, and it did not throw, and did not error. – Eljay Oct 29 '19 at 16:17