2

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;
}
Jayden
  • 25
  • 7
  • 2
    of course the answer might be regarded as opinion-based I would nor repeat myself and have only *one* header which includes the `extern` declaraions – Ingo Leonhardt Apr 17 '23 at 13:38
  • @IngoLeonhardt if that is the case, why not just declare the variable in global_variable.h and include it in every .c files without extern? – Jayden Apr 17 '23 at 13:46
  • 1
    Don't mix declaration and definition: if you _define_ a variable, eg `double freq;` in a .h and include that in multiple compilation untits (.c files) linking will fail because of the multiple symbol `freq`. Thus you need `extern` in the .h and have the real definition in exactly one .c file – Ingo Leonhardt Apr 17 '23 at 13:50
  • @IngoLeonhardt let's say we now create a global_variable.h and we create double freq without extern. At the same time, I am also using #ifndef as include guard. global_variable.h file is now included in multiple .c files, but double freq will still only be defined once, hence I am confused and don't know why and when to use extern. What would be the best practices in using extern? – Jayden Apr 17 '23 at 13:56
  • 1
    include guards affect the preprocessor only and can't prevent you from definining a symbol in different CUs where the header is used. Remember: each .c is individually compiled to an object file and then all object files are linked to the program. And linking fails if the same symbol is defined in more than one object. So according to your question about best practice: In header files only _declare_ variables using `extern` and never define them there. Definitions should only be in .c – Ingo Leonhardt Apr 17 '23 at 14:03
  • @IngoLeonhardt but I just done it and it works... I am confused... – Jayden Apr 17 '23 at 14:13
  • @Jayden regarding your last comment, you should post a new question and provide a [mcve] rather than a description of what you've done. – Jabberwocky Apr 17 '23 at 14:14
  • @IngoLeonhardt: Re “… include that in multiple compilation untits (.c files) linking will fail…”: That depends on the build tools. `double freq;` at file scope is a tentative definition, which is technically a declaration that is not a definition but which may cause a definition to effectively come into existence. The C standard does not specify the behavior if multiple definitions exist, not even to prohibit them, and some C implementations coalesce multiple definitions resulting from tentative definitions into a single definition as normal behavior without any error message. – Eric Postpischil Apr 17 '23 at 14:18
  • 3
    @Jayden: If what you described worked, you are likely using a version of GCC before version 10. Before version 10, [GCC marked definitions resulting from tentative definitions as “common” symbols and coalesced them](https://stackoverflow.com/a/67270401/298225). – Eric Postpischil Apr 17 '23 at 14:22
  • Think of it like this: You cannot define the variable in the header (with a value and everything) because the header files are not executed when running the code. What you can do, is use the header to tell multiple c-files that this variable exists. Therefore, you declare it as `extern`. Since you have to initialize the variable somewhere, to give it memory and an initial value, you can do that at the beginning of your main function (for example). `extern' tells the compiler that this variable is defined outside the scope of this header. – Torge Rosendahl Apr 17 '23 at 14:23
  • @TorgeRosendahl _"you can do that at the beginning of your main function"_ huh? That would be local variables .... – Jabberwocky Apr 17 '23 at 14:40
  • @Jabberwocky I tried to provide minimal reproducible example, but it failed with another error... – Jayden Apr 17 '23 at 15:01
  • @Jayden `undefined reference to 'increment_freq'` is a linker error. How do you compile/link? BTW the file list you provide at the beginning of the question doesn't match the files of your code, maybe that's part of the problem. – Jabberwocky Apr 17 '23 at 15:03
  • @Jabberwocky sry for the confusion. I did some mistakes on my end. – Jayden Apr 17 '23 at 15:22

2 Answers2

1

Your code is almost looks fine.

This is what you should do:

change_freq.c

#include "change_freq.h"

#include "global_variables.h"  // <<< add this line

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"  <<< remove this line

void increment_freq(double frequency);

#endif

global_variables.h

#ifndef GLOBAL_VARIABLES_H
#define GLOBAL_VARIABLES_H
extern double freq;   // << put extern here
extern double amp;
#endif

main.c

#include <stdio.h>
#include "global_variables.h"
#include "change_freq.h"

double freq;   // << define your global variables here
double amp;

int main()
{
 ...

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
0

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.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142