-3

Im building a simple error checking system and get the error:

/tmp/ccNY7emi.o:(.data+0x0): multiple definition of `error_desc'
/tmp/ccMI89tg.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

using gcc on Linux, the code is next, i believe is very self explanatory, im not an expert on C, must be error_desc declared as extern? and how?

error.h

#ifndef ERROR_H_
#define ERROR_H_

typedef enum error_e {
  ERR_DB_CONN_FAIL, ERR_BAD_PARAM, ERR_NET_CONN, ERR_STR_TO_LONG
} error_e;

char *error_desc[] = {
  "Fail to connect database",
  "invalid parameter",
  "Cannot connect to the network",
  "String length exceed established limit",
};

char *error_str(error_e err);

#endif

error.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "error.h"

unsigned get_arr_size(char **arr) {
  int count = 1;
  while (*++arr) {
    ++count;
  }
  return count;
}

char *error_str(error_e err) {                                                                                               
  unsigned arr_size = get_arr_size(error_desc);
  if ((unsigned)err < arr_size) {
    return error_desc[err];
  }
  return "Invalid error code";
}

use-error.c

#include <stdio.h>

#include "error.h"                                                                                                         

int main(void){
  printf("Error: %s\n", error_str(ERR_NET_CONN));
  printf("Error: %s\n", error_str(ERR_STR_TO_LONG));
  printf("%s\n", error_str(6));
}

If i move declaration/initialization of error_str to error.c it works, but i prefer have error_e and error_str due to "mapping" relation between them.

I modify main.c:

#include "error.h"                                                                                                         

int main(void){}

And examine the preprocessor output and get this:

gcc use-error.c error.c -E > prep.c

prep.c

# 1 "use-error.c"
# 1 "<command-line>"
# 1 "use-error.c"


# 1 "error.h" 1

typedef enum error_e {
  ERR_DB_CONN_FAIL, ERR_BAD_PARAM, ERR_NET_CONN, ERR_STR_TO_LONG
} error_e;

static char *error_desc[] = {
  "Fail to connect database",
  "invalid parameter",
  "Cannot connect to the network",
  "String length exceed established limit",
};

char *error_str(error_e err);
# 4 "use-error.c" 2

int main(void){
}
# 1 "error.c"
# 1 "<command-line>"
# 1 "error.c"
# 1 "error.h" 1

typedef enum errors {
  ERR_DB_CONN_FAIL, ERR_BAD_PARAM, ERR_NET_CONN, ERR_STR_TO_LONG
} errors_e;

static char *error_desc[] = {
  "Fail to connect database",
  "invalid parameter",
  "Cannot connect to the network",
  "String length exceed established limit",
};

char *error_str(errors_e err);
# 2 "error.c" 2

static unsigned get_arr_size(char **arr) {
  int count = 1;
  while (*++arr) {
    ++count;
  }
  return count;
}

char *error_str(error_e err) {
  unsigned arr_size = get_arr_size(error_desc);
  if ((unsigned)err < arr_size) {
    return error_desc[err];
  }
  return "Invalid error code";
}

It looks like preprocessor is ignoring the header guards, but why?

By the way, if declare static (that doesn't make many sense, i'll make it only to explore) the variable error_desc the code gets built, but if i use -Wall option on gcc i get the message:

warning: ‘error_desc’ defined but not used [-Wunused-variable]

If the code on error.c isn't using error_desc from error.h, then from where?

anewb33
  • 81
  • 7
  • 2
    Seems it's time for you to learn about the concept of [*translation units*](https://en.wikipedia.org/wiki/Translation_unit_(programming)). In short, a translation unit (or TU for short) is a ***single*** source file with all its included header files. This is the "unit" that the compiler works with. It doesn't know anything about other possible TU's. It's the *linkers* job to take all the TU's (in the form of object files and libraries) and generate the final executable. – Some programmer dude Feb 01 '22 at 15:39
  • You also need to learn the difference between *declaring* something, and *defining* it. Variables in the global scope needs to be *defined* only once through all TU's. It can be *declared* multiple times though. So to solve your problem you *declare* the variable `error_desc` in the header file, then in a single source file you *define* it. – Some programmer dude Feb 01 '22 at 15:40
  • I also recommend you invest in some decent beginners books which will explain these rather basic things in detail (plus much more). – Some programmer dude Feb 01 '22 at 15:42

1 Answers1

0

Header guards only protect a header from being included more than once in the same file. In this case it means that "error.h" will be included a maximum of once in "prep.c" and "error.c" leading to each of them having their own copy of error_desc which leads to your compile error.

The reason making error_desc static fixes this is because it tells the compiler to make it not visible outside of the file it's compiled in, removing the duplicate definition conflict. You get the warning because you have a version of error_desc in both "prep.c" and "error.c" but the version in "prep.c" is never used.

To fix this you should either move the definition of error_desc to "error.c" or if you want error_desc to be usable by any file that includes "error.h" change

static char *error_desc[] = {
  "Fail to connect database",
  "invalid parameter",
  "Cannot connect to the network",
  "String length exceed established limit",
};

in "error.h" to

extern char *error_desc[4];

and in "error.c" add

char *error_desc[] = {
  "Fail to connect database",
  "invalid parameter",
  "Cannot connect to the network",
  "String length exceed established limit",
};
rdtsc
  • 303
  • 2
  • 10