Home > Enterprise >  Best way to share multiple global variables between compilation units in C
Best way to share multiple global variables between compilation units in C

Time:08-26

I am trying to 'gather' global cmdOps spread in multiple files into lookup table for convenient usage in main. I did as shown below, but as pointed out this approach does not guarantee file1.c and file2.c to use the same variables, but I really want to avoid using extern declarations in file1.c as there will be tens of them if not more.

main.c:

#include "file1.h"

int getLutIdx(int argc, char *argv[]);
extern myLUT *LUT;

int main(int argc, char *argv[])
{
   int idx = getLutIdx(argc, argv);
   myArgs args;
   LUT[idx].ops->parseArgs(argc, argv, &args);
   LUT[idx].ops->validateArgs(&args);
   LUT[idx].ops->executeCmd(&args);
}

file1.h:

typedef struct myArgs {
   union {
      cmd1Args_t cmd1Args;
      cmd2Args_t cmd2Args;
      ...
   }
}myArgs;
typedef int (*op1)(int argc, char *argv[], myArgs *args);
typedef int (*op2)(myArgs *args);
typedef int (*op3)(myArgs *args);
typedef struct cmdOps {
   op1 parseArgs;
   op2 validateArgs;
   op3 executeCmd;
} cmdOps;
typedef struct myLUT {
  char  *cmdName;
  cmdOps *ops;
}myLUT;

file1.c:

#include "file1.h"
#include "file2.h"
#include "file3.h"

myLUT LUT[CMD_NUM] {
 { "CMD1", &cmd1Ops },
 { "CMD2", &cmd2Ops },
...
}

file2.h:

int cmd1ParseArgs(int argc, char *argv[], myArgs *args);
int cmd1ValidateArgs(myArgs *args);
int cmd1Execute(myArgs *args);

int cmd2ParseArgs(int argc, char *argv[], myArgs *args);
int cmd2ValidateArgs(myArgs *args);
int cmd2Execute(myArgs *args);

cmdOps cmd1Ops;
cmdOps cmd2Ops;

file2.c

#include "file1.h"
#include "file2.h"

myOps cmd1Ops = {
   .parseArgs= cmd1ParseArgs,
   .validateArgs = cmd1ValidateArgs,
   .executeCmd = cmd1Execute
}

myOps cmd2Ops = {
   .parseArgs= cmd2ParseArgs,
   .validateArgs = cmd2ValidateArgs,
   .executeCmd = cmd2Execute
}

...

Whole question edited, thanks for previous comments. Goal is for user to invoke:

./myProg <cmd_name> <cmd_args>

and each command (or sets of commands) can accept different parameters

CodePudding user response:

The "best way" would be to not have any global variables (or at least, as few as possible), since global variables are likely to make your program difficult to understand and debug as it gets larger and more complex.

If you must have global variables, however, I suggest declaring the global variables in just one .c file:

myOps file2Ops;  // in somefile.c

... and placing any necessary extern declarations in a .h file that other files can include:

extern myOps file2Ops;  // in someheader.h

CodePudding user response:

But it baffles me why don't I get any error or warning about duplicated declaration…

At file scope, myOps file2Ops; is a tentative definition, which is not actually a definition, directly. If there is no definition for it by the end of the translation unit, the behavior is as if there were a definition myOps file20ps = {0};. (Having an initializer would make the declaration a proper definition instead of a tentative definition.)

Then, because myOps file20ps; appears in file2.h and that is included in both file1.c and file2.c, your program has multiple external definitions of file20ps. The C standard does not define the behavior of this because it violates the constraint in C 2018 6.9 5:

… If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

Historically, some C implementations have defined the behavior of external identifiers resulting from tentative definitions to act as “common” symbols. When linking the object modules, a definition originating from a tentative definition will be coalesced with other definitions, so there will be only one definition in the final program. Your C implementation (notably the compiler and the linker) appears to be doing this, and that would be why you did not get an error message.

(This was the default behavior for GCC prior to version 10. You can request the old behavior with -fcommon or the non-coalescing behavior with -fno-common. Some additional information is here and here.)

… and I also don't know if this approach would be considered 'good practice'

You have not shown much context for what you are doing or why. Generally, external identifiers for objects should be avoided, but there can be appropriate uses.

  •  Tags:  
  • c
  • Related