8

I have an include file with 100+ global variables. It's being used in a library, but some programs that I'm linking the lib to also need to access the globals.

The way it was built:

// In one library .c file
#define Extern

// In the programs that use the globals
#define Extern extern

// In the .h file
Extern int a,b,c;

I had a hard time understanding why the original programmer did that so I removed that define Extern stuff. Now I think I understand the thing about TU with the help of stackoverflow: 1, 2, 3.

Now I understand that I should define the global variables in one .c file in the library and use extern in the .h file. The problem is that I don't want to duplicate code.

Should I go back to that #define Extern voodoo?

Community
  • 1
  • 1
Costi
  • 1,231
  • 2
  • 11
  • 18
  • Sorry to come up lately but the accepted answer is the wrong approach. Frederik Slijkerman answer is the right one. Only declarations in .h files and the definitions in .c files. `extern var` is a declaration, without `extern` it's a definition. Put the definition of the variable in the module (i.e. .c file) it is logically (semantically) part of. Conversely, *never* use a `extern var` in a .c file. – Patrick Schlüter Feb 19 '16 at 08:12

8 Answers8

10

The trick here is that the .h file is being used in two different ways - it's being used as a normal .h file where all the globals are declared extern and it's also being used to define the globals themselves (with no extern). This is an ugly hack but you can understand why someone felt it necessary if you have a large number of globals (a sure sign of very bad software design !).

Anyway, there is a somewhat more elegant solution - you can put all your globals in a single global struct, e.g.

//
// globals.h
//

typedef struct {
    int a;
    int b;
    // ...
    int z;
} Globals;

extern Globals globals; // declaration

-

//
// globals.c
//

#include "globals.h"

Globals globals; // definition

-

Then when you need to refer to a global it's e.g. globals.a instead of just a, which might seem like an inconvenience but this is arguably clearer and more manageable than just having naked globals scattered throughout the code.

Paul R
  • 208,748
  • 37
  • 389
  • 560
  • 1
    You're right, and putting those globals in some structures is a TODO. It's even possible to give them some meaningful names. – Costi May 19 '10 at 23:46
6

It is a bad pattern to have to define Extern in every .c file. Removing it is probably best, but you need to replace this functionality somehow. One approach is that you could use a #define in the .c file that needs to define these globals. This define will signal to the .h to not extern the global variables.

For example: The one library .c file:

#define FOO_LIBRARY_C
#include "foo_library.h"

The other .c files:

#include "foo_library.h"

foo_library.h:

#ifdef FOO_LIBRARY_C
int a,b,c
#else
extern int a,b,c
#endif

or

#ifdef FOO_LIBRARY_C
#define GLOBAL_PREFIX
#else 
#define GLOBAL_PREFIX extern
#endif

GLOBAL_PREFIX int a,b,c

This reduces the need to define the same thing in every single source file (except one) and would help to reduce errors. I also wouldn't call it Extern as that can just cause confusion, as it may or may not be "extern"

Aaron
  • 19,151
  • 4
  • 28
  • 23
  • I disagree that it's a bad pattern:I know this goes against the perceived wisdom of having the externs in .h files, but for this very case I can't think of any other drawbacks than having to write a few extra extern definitions. If you find the need to write tons of them your code is a mess (way too many global variables) and you should in any case ALWAYS think how and where you use them because they can cause very subtle bugs. By using a .h file you are giving yourself a carte blanche to use global variables indisriminantly. – Makis May 19 '10 at 20:07
  • Makis, you are conflating 2 things here; - the design choice of using global variables and - best practice when you have to use a global variable. It's clear that using global variables is generally a bad idea, but when you have to use one, use the best usage for its implementation and that is `extern var` declaration in header and `var` definition in the right `.c` file. Any other usage pattern is wrong. – Patrick Schlüter Feb 19 '16 at 08:21
5

Maybe I'm missing something too, but I ALWAYS use inclusion guards on all the header files I create:

foo.h:

#ifndef FOO_H
#define FOO_H

extern int foo;

#endif

foo.c:

#include "foo.h"

int foo = 0;

bar.c:

#include "foo.h"
#include <stdio.h>
int main(int argc, char** argv)
{
    printf("foo:%d\n",foo);
    return 0;
}
James Morris
  • 4,867
  • 3
  • 32
  • 51
  • I think that this simple strategy won't work with libraries and programs that include the same header file. – Costi May 19 '10 at 19:50
  • 1
    I much prefer this over Aaron's method also listed here. Having the definitions in one file and the declarations in another makes more sense to me, and seems like a much more common pattern. – Clinton Pierce May 19 '10 at 19:53
  • 1
    People, this won't work. Imagine if I compile the library, and, at a later date, compile a program that includes that .h file. Both will have the globals WITHOUT extern. That's the situation that I'm describing. – Costi May 19 '10 at 20:01
  • 1
    @Costi, foo.c is part of the library, therefor the globals are in the library. your programs will all include foo.h - where the globals are defined extern. the inclusion guards will allow each separately built program to include foo.h once. can you explain how this is broken because i don't see it. – James Morris May 19 '10 at 20:07
  • My bad, guys. I see now that foo.c has "int foo=0". But I still see a problem with this approach: I have over 100 globals and it's a lot of code duplicated in the .c and .h lib files . With Aaron's solution, they will be declared only in the .h file. – Costi May 19 '10 at 20:12
  • @Costi: True I guess. From a few quick searches for "c library with global variables" I get the impression that having globals in a library is a bad idea anyway. If I read it correctly, whether two simultaneously running programs share the same set of globals or have their own copies, cannot be relied upon and is OS specific etc. – James Morris May 19 '10 at 20:21
  • I don't think the data is shared between running processes using the same library. And, anyway we are linking statically to our own lib :) – Costi May 19 '10 at 20:27
  • @Costi: If this is not your library, it does not matter whether the codes are duplicated. If this is your library, you should really avoid using these many global variables. Even Paul R's solution is much better. James Morris gives the most standard solution. – user172818 May 19 '10 at 21:56
  • 1
    +1. You shouldn't be using the inclusion of header files to include "code" (versus definitions). I know that the line is blurry here, but the idea of flipping the extern keyword off to effectively have the header function as the definition works but is just a byproduct of managing "too many" globals anyways (I know this is subjective, but I'm explaining why this pattern isn't so common in the wild.) – Ben Zotto May 19 '10 at 22:26
5

The macro stuff is silly for this. Just put

extern int myGlobal;

in your header file, and in ONE .c file (typically one with the same name as the .h file), put

int myGlobal;

No need to get stressed about this level of "duplication".

Frederik Slijkerman
  • 6,471
  • 28
  • 39
  • This should be the accepted answer. It's the only sane approach. Any other preprocessor acrobatic presented in the other answers are WRONG, adding unnecessary noise in the code. – Patrick Schlüter Feb 19 '16 at 08:23
2

I might be missing something, but I see no difference between the #define thingy and just using the extern keyword.

Basically, you have to declare the vars in a .h file and define them in a .c file. Don't consider it code duplication - I think changing the point of view is the best thing you can do here :). You may write more lines of code, but they'll be readable :D.

mingos
  • 23,778
  • 12
  • 70
  • 107
  • I'm willing to be that EXTERN is placed in both the declaration and the header files to make cut and paste of the value jsut that much easier to use and to have a visual que that said var is global scope visible. That said, I shudder about the wonderful abuse this allows everywhere. Oh the days of spaghetti var tracking... – Michael Dorgan May 19 '10 at 19:42
  • Well, I do that for dll_export and such, but that's because M$VC and gcc do DLLs differently. As for externs, I very much prefer to just have one .h and one .c file for them. Like I said, that's more code, but I like readability. Maybe it's just a personal preference, I dunno. Then again, were I to erite unreadable code, I'd learn to program in Brainf*ck or something similar :) – mingos May 19 '10 at 19:47
  • That very thing is the reason I do specify global variable names somehow even though I hate the Hungarian notation with a vengeance. Depending on style of the program, I use either the "g" or "g_" prefix for these. It helps inspections more than you could believe. – Makis May 19 '10 at 19:47
  • +1 I second that I go even further. Never define a global variable in a header, never, no exception, it brings only pain and grief. Include files should be idempotent. – Patrick Schlüter Jun 06 '11 at 17:09
1

In large programs it is very important to have a single line to declare and define a global variable. Therefore, the macro approach described above is the proper way to address the issue.

// globals.h 
#ifndef GLOBALS_H 
#define GLOBALS_H  
#ifndef EXTERN 
#define EXTERN extern 
#endif
EXTERN int i; 
#endif  

// globals.c 
#define EXTERN 
#include "globals.h" 
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
user784723
  • 19
  • 3
0

As a rule of thumb - don't use global variables. Sometimes you need to use static variables in file but it is best to try to avoid them at all:

  • You cannot unit test properly code which contains global variables
  • There is no clean separation between modules which can cause problems with isolation of errors
  • It is usually not thread safe. You have to wrap them with mutexes etc. if you want to use threads (tends to be better and better idea as we get more and more cores) you can run into troubles with writeable shared state.

Sometimes in C you cannot avoid them (especially in code inherited by someone) but best is to keep them out.

As for the declaration - it might be helpful in such case:

// globals.h

#ifndef GLOBALS_H
#define GLOBALS_H

#ifndef EXTERN
#define EXTERN extern
#endif

EXTERN int i;
#endif

// globals.c
#define EXTERN
#include "globals.h"
Maciej Piechotka
  • 7,028
  • 6
  • 39
  • 61
-1

While this can be annoying at first it does help you avoid having to type thing twice or forgetting to include something in a .c file at all. I've seen:

#define FOO_C_

#include "foo.h"
#include "bar.h"

int foo_doit(int a, int b, int c) {
...
}

with foo.h being:

#ifndef FOO_H_
#define FOO_H_

#ifdef FOO_C_
#define GLOBAL
#define DECLARE( type, name, value) type name = value
#else
#define GLOBAL extern
#define DECLARE( type, name, value) extern type name;
#endif

GLOBAL int foo_doit(int a, int b, int c);
GLOBAL int foo_it; // uninitialized global variable
DECLARE(char, that[], "that");

// and sometimes using:
#ifdef FOO_C_
char word[] = letters;
#else
extern char word[];
#endif


#endif // FOO_H_
nategoose
  • 12,054
  • 27
  • 42