简体   繁体   中英

Trying to understand extern in C

By the answer given here , I am trying to construct a working example and try to understand behavior of the extern linkage.

Here is my example:

extern.h

extern int global_counter;

int file_counter;

extern.c

#include "extern.h"

int global_counter = 5;

incrementor1.c

#include "extern.h"
#include <stdio.h>

void incrementor1_global()
{
    global_counter++;
    printf("From Incrementor1...\n");
    printf("Global counter is: %d\n", global_counter);
}

void incrementor1_local()
{
    file_counter++;
    printf("From Incrementor1...\n");
    printf("File counter is: %d\n", file_counter);
}

incrementor2.c

#include "extern.h"
#include <stdio.h>

void incrementor2_global()
{
    global_counter++;
    printf("From Incrementor2...\n");
    printf("Global counter is: %d\n", global_counter);
}

void incrementor2_local()
{
    file_counter++;
    printf("From Incrementor2...\n");
    printf("File counter is: %d\n", file_counter);
}

You can yourself put these into separate files as depicted. Here is my main.c now:

main.c

void incrementor1_global();
void incrementor1_local();

void incrementor2_global();
void incrementor2_local();

int main(char argc, char* argv[])
{
    incrementor1_global();
    incrementor2_global();

    incrementor1_local();
    incrementor2_local();
}

Since I link .o files to my main, I am just declaring the functions and since they are extern by default, they are linked by the linker as I understood. And lastly, here is a makefile if you want to try it yourself.

CC=gcc

CFLAGS=-I.

DEPS = extern.h

OBJ = main.o extern.o incrementor1.o incrementor2.o  

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

main: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)


.PHONY: clean

clean:
    rm -f *.o  

So by the answer linked above, I think global_counter should be shared by all translation units while each should have their own copy of file_counter. But the output I get is:

From Incrementor1...
Global counter is: 6
From Incrementor2...
Global counter is: 7
From Incrementor1...
File counter is: 1
From Incrementor2...
File counter is: 2

So, I think both incrementor have a one copy of file_counter and that is a global variable. But now I don't know how I can replicate the answer given and make them have their own copy. Any suggestions?

By the way, If I want to give a definition to file_counter in extern.h, since it is included in both incrementors I am getting multiple definitions error. This confuses me further on how could I make them have a shared copy.

You're experiencing the problem due to tentative definition.

Quoting C11 , chapter §6.9.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.

Try compiling with -fno-common flag and it should throw the error related to redefinition for file_counter .

Quoting the online manual

-fno-common

In C code, this option controls the placement of global variables defined without an initializer, known as tentative definitions in the C standard. Tentative definitions are distinct from declarations of a variable with the extern keyword, which do not allocate storage.

Unix C compilers have traditionally allocated storage for uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This is the behavior specified by -fcommon, and is the default for GCC on most targets. On the other hand, this behavior is not required by ISO C, and on some targets may carry a speed or code size penalty on variable references.

The -fno-common option specifies that the compiler should instead place uninitialized global variables in the data section of the object file. This inhibits the merging of tentative definitions by the linker so you get a multiple-definition error if the same variable is defined in more than one compilation unit. Compiling with -fno-common is useful on targets for which it provides better performance, or if you wish to verify that the program will work on other systems that always treat uninitialized variable definitions this way.

int file_counter is a tentative definition and is a global variable (with external linkage), as it is defined outside of any function. It is a good practice to have variable definitions in C source files, not in headers.

If you want each translation unit to have its own copy of file_counter , one way is to mark it as static and define it both in incrementor1.c and incrementor2.c . In that case, the visibility of the variable will be restricted only to the file in which it is defined.

Each of your compilation units that includes extern.h declares a variable called file_counter at global scope. That means that each one declares the variable and some storage for it, but the variable is theoretically visible at link time to other compilation units.

When the linker comes along, it sees these global variables and allocates them to a segment of storage and, in your case, it coalesces them into one instance, I'm not sure if this is mandated by the C standard or not, I'm too lazy to check, but it's probably an example of undefined behaviour.

What you need is to make the variable file-counter be visible only in the compilation unit in which it is declared (the .c file that included the .h ) and for that, you use the static modifier

static int file_counter;

As a rule, it should also be removed from the header and placed in the .c file where it is going to be used. It's OK for your toy example that demonstrates the difference between extern and static but in pretty much every real scenario, you do not want a new static variable in every single .c file that includes the header.

So by the answer linked above, I think global_counter should be shared by all translation units while each should have their own copy of file_counter

This is incorrect. Both global_counter and file_counter are global variables. This is because you declare file_counter outside any function.

If you declare file_counter separately inside the functions incrementor1_local and incrementor2_local and remove it from extern.h , then it will be a local variable and will have separate copies in each function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM