Search code examples
cglobal-variablesextern

Should I use extern?


I have the following files:

  1. pass_args.c
  2. pass_args.h
  3. kbd.c
  4. kbd.h
  5. main.c

There are 3 global variables that I would need to use: freq, amp and waveforms.

Should I declare those variables in every .h files by using extern and define the variable in one of the .c file OR create a global_variable.h, declare those variables in the global_variable.h and include this global_variable.h in every .c files.

Here is the minimal reproducible example:
change_freq.c

#include "change_freq.h"

void increment_freq(double *frequency)
{
    (*frequency)++;
    if (*frequency == 1)
    {
        amp++;
    }
}

change_freq.h

#ifndef CHANGE_FREQ_H
#define CHANGE_FREQ_H

#include "global_variables.h"

void increment_freq(double *frequency);

#endif

global_variables.h

#ifndef GLOBAL_VARIABLES_H
#define GLOBAL_VARIABLES_H
double freq;
double amp;
#endif

main.c

#include "global_variables.h"
#include "change_freq.h"

int main()
{
    freq = 0;
    increment_freq(&freq);
    printf("Hello World!\n");
    printf("freq %lf\n", freq);
    printf("amp %lf\n", amp);
    return 0;
}

Solution

  • Here's a script to reproduce your project setup (run in an empty directory):

    #!/bin/sh -eu
    cat > global_variables.h <<EOF
    #ifndef GLOBAL_VARIABLES_H
    #define GLOBAL_VARIABLES_H
    double freq;
    double amp;
    #endif
    EOF
    
    cat > change_freq.h <<EOF
    #ifndef CHANGE_FREQ_H
    #define CHANGE_FREQ_H
    
    #include "global_variables.h"
    
    void increment_freq(double *frequency);
    
    #endif
    EOF
    cat > change_freq.c <<EOF
    #include "change_freq.h"
    
    void increment_freq(double *frequency)
    {
        (*frequency)++;
        if (*frequency == 1)
        {
            amp++;
        }
    }
    EOF
    
    cat > main.c <<EOF
    #include "global_variables.h"
    #include "change_freq.h"
    #include <stdio.h> ///ADD!
    
    int main()
    {
        freq = 0;
        increment_freq(&freq);
        printf("Hello World!\n");
        printf("freq %lf\n", freq);
        printf("amp %lf\n", amp);
        return 0;
    }
    EOF
    

    With it being like this it should compile and run (cc *.c && ./a.out) if and only if your compiler and linker support and implicitly allow so called common variables, which allow uninitialized global variables (double freq; double amp;) to be tentatively defined in multiple translation units with the linker then merging them. For gcc/clang you can explicitly request this nonstandard feature with -fcommon. (So gcc -fcommon *.c && ./a.out should work).

    A portable solution would be to do

    extern double freq, amp;
    

    in the header, and

    /*no-extern*/ double freq, amp;
    

    in one of the C files.

    (You see, double freq; double amp; at global scope without both extern and initializers are both declarations and tentative definitions. Tentative definitions mean that you can redefine them with initializers once, after which they're finished (and non-tentative redefining attempts become errors), and/or tentatively redefine them. If no initializer is ever given, they're zero-initialized by default and finished by the end of the compilation unit. The (fairly common) -fcommon extension (also needs support by the linker) allows you to extend this notion of tentative globals to multiple translation units by postponing the finishing of tentative globals from the end of the compilation unit to link time. It is a legacy feature from B, if I remember correctly. It relies on tentative globals being classed as a special type of linker symbol called a common symbol, which works the same as the standard C tentative definition except across all files being linked rather than just a single translation unit.)