1

I am working on a question from my computer programming course this semester and am unable to resolve a linker error that I am facing. Can someone take a look at the following files and let me know what's wrong. As instructed, I placed guards to ensure multiple definitions don't take place but the guards don't seem to be functioning as I expected because gcc is giving me and error saying that count and booksCatalog are defined multiple times.

Below are the C files and header files used for the question.

book.h

#ifndef BOOK_H
#define BOOK_H

#include <stdio.h>

typedef enum shelf{Shelf1=1,Shelf2=2,Shelf3=3,Shelf4=4} SHELF;

typedef struct book{
    int ID;    
    SHELF shelfNum;
    float price;
} BOOK;


// the function should create a new variable of the type struct book (typdefed as BOOK)
// using the parameters passed to it and return it.
BOOK newBook(int ID, SHELF shelfNum, float price);

// this function should print the values of the members of book1 (passed as parameter)
void printBook(BOOK book1);

#endif

book.c

#include "book.h"

BOOK newBook(int ID, SHELF shelfNum, float price)
{
    // implement this function as per specification in books_def.h
    BOOK newbook = {ID, shelfNum, price};
    return newbook;
}

void printBook(BOOK book1)
{
   // implement this function as per specification in books_def.h
   printf("Book ID : %d\n", book1.ID);
   printf("Book shelf number : %d\n", book1.shelfNum);
   printf("Price of Book: %f\n\n",book1.price);
   return;
}

catalog.h

#ifndef CATALOG_H
#define CATALOG_H

#include "book.h"

#define MAX_SIZE 100

BOOK booksCatalog[MAX_SIZE];
int count;

// This function should take an input parameter of the type struct book (typdefed as BOOK)
// and it to the booksCatalog array at position count. Then, count should be incremented.
void addBookToCatalog(BOOK book1);


// This function should print all the books that are added to booksCatalog array.
// It should use the printBook() function defined in "book_def.h" to print the details of each book.
void printBookCatalog();


// This function should sort the booksCatalog array based on the ID of the book.
void sortBookCatalogOnID();

#endif

catalog.c

#include "catalog.h"

void addBookToCatalog(BOOK book1)
{
    // implement this function as per specification in books_catalog.h
    if (count<MAX_SIZE)
    {
        booksCatalog[count]=book1;
        count++;
    }
    else
        printf("Catalog is full!");
    return;
    
}

void printBookCatalog()
{
    // implement this function as per specification in books_catalog.h
    for (int i=0; i<count; i++)
    {
        printBook(booksCatalog[i]);
    }
    return;
}

void sortBookCatalogOnID()
{
    // implement this function as per specification in books_catalog.h
    int min;
    for (int i=0; i<count-1; i++)
    {
        min=i;
        for (int j=i+1; j<count; j++)
        {
            if ((booksCatalog[j].ID)<(booksCatalog[min].ID))
                min=j;
        }
        BOOK temp = booksCatalog[i];
        booksCatalog[i]=booksCatalog[min];
        booksCatalog[min]=temp;
    }
    return;
}

main.c

#include "catalog.h"
#include "book.h"

int main()
{
    count = 0;
    BOOK book1 = newBook(1847, Shelf3, 8768.95);
    BOOK book2 = newBook(5984, Shelf1, 7845.25);
    BOOK book3 = newBook(6325, Shelf2, 3154.47);
    BOOK book4 = newBook(5843, Shelf2, 1487.51);
    BOOK book5 = newBook(7894, Shelf2, 541.29);
    // printBook(book1); 

    // adding book1 to catalog of books
    addBookToCatalog(book1);
    addBookToCatalog(book2);
    addBookToCatalog(book3);
    addBookToCatalog(book4);
    addBookToCatalog(book5);

    printBookCatalog();

    sortBookCatalogOnID();

    printBookCatalog();

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 2
    Related/possible duplicate: [How do I use extern to share variables between source files?](https://stackoverflow.com/q/1433204/12149471) – Andreas Wenzel Jun 24 '23 at 11:12

2 Answers2

1

The header catalog.h has two tentative definitions of variables

BOOK booksCatalog[MAX_SIZE];
int count;

As the header is included in modules catalog.c and main.c then in fact these two variables are defined twice in each translation unit like

BOOK booksCatalog[MAX_SIZE] = { 0 };
int count = { 0 };

From the C23 Standard (6.9.2 External object definitions)

2 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}.

You should declare the variables without their definitions in the header catalog.h

extern BOOK booksCatalog[MAX_SIZE];
extern int count;

(Note: the variables in these declarations shall not be initialized. Otherwise they be externally defined in the header and the linker again will issue an error because the header is included in two translation units.)

and define them in the module catalog.c like for example

BOOK booksCatalog[MAX_SIZE] = { 0 };
int count = 0;

Pay attention to that in C opposite to C++ to make a file (a namespace in C++) scope external definition of a variable you need to initialize it. In C++ there is no such a notion as "tentative definition".

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

In your header file catalog.h you have the line

int count;

which is a tentative definition. In this case, this tentative definition will always become an actual definition.

This means the two source files main.c and catalog.c are each providing a definition of the variable count. This violates the one-definition rule, even if the two definitions are identical.

In order to comply with the one-definition rule, only one of the source files should provide a definition. Therefore, I suggest that in the header file catalog.h, you change the line

int count;

to:

extern int count;

That way, this line will be a declaration instead of a tentative definition.

However, now the variable count is declared in main.c and catalog.c, but not defined anywhere. Therefore, in order to comply with the one-definition rule, you must also define the variables in one of the source files. This definition can be a (tentative) definition

int count;

or an explicit definition:

int count = 0;

The variable booksCatalog has the same problem.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • But shouldn't the guards placed prevent this from happening. If the macro is already defined, then the definition shouldn't be compiled right? I just started programming in C and am not aware of exactly what's happening with the tentative definition. – ErrorEliminator Jun 24 '23 at 11:36
  • @ErrorEliminator: The include guards prevent the same header file from being included in the same source file (translation unit) more than once. It does not prevent a header file from being included in more than one source file. – Andreas Wenzel Jun 24 '23 at 11:38
  • If the header file is included in more than one source file, then when they are linked, won't the macros being defined trigger the #ifndef directive and skip the compilation of the tentative definition. P.S. A lot of this is going over my head rn, so if I am blatantly wrong, can you pls simplify what you mean as I am unaware of a lot of what is used in your answer like translation unit and tentative definition – ErrorEliminator Jun 24 '23 at 11:43
  • @ErrorEliminator: Each translation unit (i.e. `.c` source file) is, at least conceptually, compiled independantly. Afterwards, the object code emitted by the compiling process is linked, to form an executable. The `#define` macros only apply to the compiling process, so only affect the compiling of a single translation unit. These macros do not exist anymore when the linking takes place. Therefore, the preprocessor directive `#define BOOK_H` will only have an effect on the current translation unit and will not have any influence on other translation units. – Andreas Wenzel Jun 24 '23 at 11:54
  • Oh so the definition will take place nonetheless in both files, because main.c and catalog.c are compiled independently. And the macros only prevents inclusion of book.h more than once in main.c because it is included separately as well as indirectly via inclusion of catalog.h in main.c and inclusion of book.h in catalog.h So giving the extern keyword just tells the compiler that my variable is declared here but defined somewhere else? – ErrorEliminator Jun 24 '23 at 11:58
  • @ErrorEliminator: Yes, that is correct. As long as you follow the rule that header files should only contain **declarations** of variables, not **definitions**, and make sure that every variable is defined in **exactly one** `.c` source file, then you should have no problems with the one-definition rule. – Andreas Wenzel Jun 24 '23 at 12:04
  • @ErrorEliminator: Note, however, that the rule that I mentioned in my previous comment only applies to **variable declarations/definitions**. It does not apply to **type definitions**. For example, the definition `typedef struct book{ int ID; SHELF shelfNum; float price; } BOOK;` is not a variable declaration, but a type definition that belongs in a header file. The one-definition rule only applies to variables and functions definitions, but not to type definitions. Therefore, it is correct that you have the type definitions in the header files. – Andreas Wenzel Jun 24 '23 at 12:14