2

I am trying to understand extern. According to one of the top answers to How to correctly use the extern keyword in C

it's in order to solve the problem of multiple inclusions of a header file resulting in multiple copies of the same variable and thus leading to, presumably, a linking error.

So I tried it by creating the following files:

count.h

int count;

count.c

int count = 0;

add.h

int sum(int x, int y);

add.c

#include "count.h"
int sum(int x, int y){
count = count + 1;
return x+y;}

sub.h

int sub(int x, int y);

sub.c

#include "count.h"
int sub(int x, int y){
count = count + 1;
return x - y;
}

main.c

#include "count.h"
#include "add.h"
#include "sub.h"
#include <stdio.h>

int main(){
  printf("%d\n", count);
  printf("%d\n", sub(100,1));
  printf("%d\n", count);
  printf("%d\n", add(100,1));
  printf("%d\n", count);
}

This compiles and runs fine with output:

0
99
1
101
2

I get the same output with or without extern in the original count.h file. So what am I missing in the answer?

Now, I thought the answer is that "I'm just declaring multiple copies of count" since there are no header guards, and that is OK because multiple declarations are OK, whereas multiple definitions are not. But then if that's the case, I would expect the following to compile, but it does not since I am "redefining count."

int main(){
   int count;
   int count; 
   int count = 0;
}

According to this answer, int count does count as a definition. In C, is it valid to declare a variable multiple times?

MathStudent
  • 310
  • 2
  • 12

2 Answers2

2

The difference in the second case is that you declared count multiple times at the same scope within a function. Had you done this:

int count;
int count; 
int count = 0;

int main(){
    return 0;
}

You would be fine. The reason this works is because the declarations without an initializer are a tentative definition.

From section 6.9.2 of the C standard

2 A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static , constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

...

4 EXAMPLE 1

int i1 = 1;         //    definition, external linkage
static int i2 = 2;  //    definition, internal linkage
extern int i3 = 3;  //    definition, external linkage
int i4;             //    tentative definition, external linkage
static int i5;      //    tentative definition, internal linkage
int i1;             //    valid tentative definition, refers to pre vious
int i2;             //    6.2.2 renders undefined, linkage disagreement
int i3;             //    valid tentative definition, refers to pre vious
int i4;             //    valid tentative definition, refers to pre vious
int i5;             //    6.2.2 renders undefined, linkage disagreement
extern int i1;      //    refers to previous, whose linkage is external
extern int i2;      //    refers to pre vious, whose linkage is internal
extern int i3;      //    refers to previous, whose linkage is external
extern int i4;      //    refers to previous, whose linkage is external
extern int i5;      //    refers to previous, whose linkage is internal
dbush
  • 205,898
  • 23
  • 218
  • 273
2

Your

int count;

in count.h is actually a definition, not a mere declaration. It is a tentative definition, but as every tentative definition it gives birth to a normal full-blown definition of int count at the end of each translation unit that contains the above.

So, by including your count.h into multiple translation units you produced multiple definitions of external object int count in your program, which is formally illegal in standard C. It "compiles and runs fine" only because of a compiler extension implemented by many modern C compilers.

According to Rationale for International Standard — Programming Languages — C (see section 6.2.2 Linkages of identifiers), this used to be OK in the relaxed legacy declaration/definition model employed by Unix compilers back in the day (so called "Relaxed Ref/Def" model). However, more formal K&R C rejected Unix model and replaced it with so called "Strict Ref/Def" model, which already allowed only one definition. Later, the standardization committee settled on a mix of "Strict Ref/Def" model and "Initialization" model, which still allows only one definition. Yet, popularity of "Relaxed Ref/Def" in old Unix compilers is the reason many modern compilers continue to support this model as an extension.

In order to make your program compliant with the requirements of standard C you have to remove the definition of int count from the header file and replace it with a non-defining declaration

extern int count;

This is what makes extern useful with variable declarations.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Is there a way to get the compiler to complain about what I have as is? I have tried gcc with -Wall and -pedantic. I would just like to see a program fail to compile or warn or anything that is fixed by adding extern. But thank you, this clears up a lot. – MathStudent Dec 01 '17 at 02:13
  • @MathStudent - The usual way to avoid this is to *not* have global variables accessed from several different .c files. Declare a function `AddCount` in count.h and hide the variable away in the .c file. – Bo Persson Dec 01 '17 at 02:33
  • @MathStudent: I don't know if there's a way to force GCC to complain about it. But I know, for example, that Tiny C compiler will catch this as an error. – AnT stands with Russia Dec 01 '17 at 02:34