1

I have an array of typedef struct Books Book in a file1.h declared as Book books[20]. Now, I initialise the array in file1.c and then use it in file2.c (that includes file1.h), without defining it. The program works fine. But I don't understand why. Since I have not used extern with books, shouldn't I be getting a compile-time error (undefined reference) for file2.c?

file1.h

Book books[20]; 

file1.c:

#include file1.h
    void intializeBooks(){
    /**
    *Code to initialize book name and its cost
    */
    }

file2.c:

   #include "file1.h"
    void addOrder() {
        for (int i = 0; i < 20; i++) {
                if (books[i].cost != -999) {
                    fprintf(fptr, "\t\t%d.%s\n", (i + 1),
                            books[i].name); //works correctly
                } else
                    break;
            }
Archer
  • 271
  • 5
  • 15
  • How do you know it is the same `books` in each file? – Scott Hunter Jun 05 '20 at 18:22
  • By accessing the values. @ScottHunter – Archer Jun 05 '20 at 18:23
  • 3
    You need to show the code. See [mcve]. – user3386109 Jun 05 '20 at 18:24
  • What do you mean "without defining it"? You have a definition (not a declaration) in file1.h, which is included in file2.c. The definition is therefore included in file2.c – William Pursell Jun 05 '20 at 18:34
  • @WilliamPursell Isn't books extern by default? – Archer Jun 05 '20 at 18:35
  • @WilliamPursell: It's a [tentative definition](https://en.cppreference.com/w/c/language/extern). – Nate Eldredge Jun 05 '20 at 18:42
  • See https://stackoverflow.com/a/1433387/634919 under "Not so good way" for an explanation of what is going on here. – Nate Eldredge Jun 05 '20 at 18:47
  • Does this answer your question? [How do I use extern to share variables between source files?](https://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files) – Nate Eldredge Jun 05 '20 at 18:48
  • 2
    In short, you have defined global objects with the same name in both `file1.c` and `file2.c` (since they both include the tentative definition from `file1.h`). This is undefined behavior according to the C standard, but some implementations handle it by merging both objects into one, which sounds like what you're describing. – Nate Eldredge Jun 05 '20 at 18:50

1 Answers1

2

A global declaration without an initializer constitutes a "tentative definition":

6.9.2p2:

A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

Basically, tentative definitions serve as declarations that become a definition unless it is overridden by an actual definition.

The standard guarantees that this'll work in just one translation unit: i.e., what you are doing with the Books variable being tentative in several translation unit is not guaranteed to work. However, practically it does work since common linkers (upcoming pun intended) use a special symbol type called a "common symbol" for symbols that were still tentative at the end of a translation unit, and with that special symbol type. tentative definitions work even across several translation units. (If there's one non-tentative definition, it overrides all the other ones; otherwise the tentative definitions get merged into just one zero-initialized external definition.)

Long story short, if you want to be perfectly standard compliant, you should replace the tentative definition in the header with a proper extern declaration. If you don't, your code might still work. (On Unix-based platforms, it probably will work because common symbols are a feature that's very old, even though it's nonstandard feature.) Either way it's bad practice to put tentative definitions in header files.

user3386109
  • 34,287
  • 7
  • 49
  • 68
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142