0

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

jbulatek
  • 154
  • 10
  • See also [How do I use `extern` to share variables between source files?](https://stackoverflow.com/q/1433204/15168) That does have material covering the use of the 'common' extension. It also notes that GCC 10.1.0 and later have changed the default behaviour (effectively from `-fcommon` to `-fno-common`) to hew more closely to the standard. With a modern version of GCC, you would get the doubly-defined variable errors you expect. – Jonathan Leffler Aug 26 '22 at 19:19

2 Answers2

3

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
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • that would brake my rule of not adding ```extern```. I may led you astray a bit in my question, because truly in my code I use multiple ```myOps``` variables in file2, file3 etc. so it is far more readable when I only include headers instead of putting tens of externs. I don't think I can put extern in file2.h as I include it in file2.c. Which made me think if in my example file1 and file2 will use the same variables, or different with shared name. – jbulatek Aug 25 '22 at 13:53
  • 1
    Re “that would brake my rule of not adding `extern`.” That sounds like a bad rule. – Eric Postpischil Aug 25 '22 at 14:01
  • 1
    Re “I don't think I can put extern in file2.h as I include it in file2.c.” Things in file2.h may be declared `extern` even if that file is included in file2.c. – Eric Postpischil Aug 25 '22 at 14:01
  • @EricPostpischil so having: ``` extern int var; int var = 1; ``` is ok by standard? – jbulatek Aug 25 '22 at 14:51
  • 1
    @jbulatek you can put `extern`s wherever you want; all the `extern` statement does is tell the compiler "I promise that a variable with this name and this type will exist somewhere in this program, so don't error out when you see its name mentioned". That said, the DRY principle says that it's best to only have things in one spot (otherwise you have to make any updates/changes to multiple places, and its easy to mess up and have things become inconsistent); and if you're going to pick a single location for your `extern` directive(s), then in a common .h file is the best place for it. – Jeremy Friesner Aug 25 '22 at 14:57
  • @jbulatek: `extern int var; int var = 1;` is fine. `extern int var;` says “Somewhere else, there is a definition of `var`, and its type is `int`.” Then `int var = 1;` is somewhere else, and it defines `var` to be an `int` with initial value 1. – Eric Postpischil Aug 25 '22 at 15:08
1

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312