1

I've been experimenting with faking C a bit in my free time, and I came across some behavior I find interesting. I was hoping someone could help answer some questions I came up with. For the sake of simplicity, let's limit the discussion to compiling with gcc. First, the code (the .h files all have include guards):

a.h

void print_a();

b.h

void print_b();

c.h

void print_c();

a.c

#include "a.h"
#include "c.h"

#include <stdio.h>

void print_c(){
  printf("Printing c from a");
}

void print_a(){
  print_c();
}

b.c

#include "b.h"
#include "c.h"

#include <stdio.h>

void print_c(){
  printf("Printing c from b");
}

void print_b(){
  print_c();
}

main.c

#include "a.h"
#include "b.h"

int main(int argc, char **argv){
  print_a();
  print_b();
  return 0;
}

So first of all, I understand that both a.c and b.c have implementations of c.h. Because of this I would expect that compiling like this would fail, since the compiler wouldn't know which implementation of print_c to bind to the interface:

gcc main.c a.c b.c

But you'll notice main.c has no dependencies on c.h. Therefore, when compiling each component separately, I was a bit surprised to see this fail at the linker stage:

gcc -c a.c
gcc -c b.c
gcc -c main.c
gcc main.o a.o b.o
b.o: In function `print_c':
b.c:(.text+0x0): multiple definition of `print_c'
a.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

It quickly became obvious that since the linker hadn't linked the definitions of print_c yet, so we should still expect these errors. That said, I can think of a several realistic use cases where this would be problematic. For example, what if I used some custom implementation of a log.h interface in my program, but I wanted to include a library that internally implements the same log.h interface? This leads to my questions:

  • Is there a way to run the linker on individual files to produce some sort of intermediate linking files, and then combine them in another linking step?
  • As an alternative to the above, would a well-defined C application just avoid using any shared header files like this?
    • If so, what if multiple dependencies have methods with the same definitions but different implementations? If compiling dependencies from source it's obviously possible to rename functions, but how would this work in cases where that's not possible?
  • Is this even a problem in C? I come from an OO background, where problems like this don't really exist, but it seems like this a clear hurdle to abstraction. It's very possible that I'm just missing something common (like method naming conventions, etc) that would prevent this problem.

2 Answers2

0

The header files contain function prototypes. A function prototype tells the compiler

  • the name of the function
  • the arguments that the function takes
  • the return type of the function

By convention, a header file only contains function prototypes for functions that are defined in one source file, but used in other source files.

So c.h should not exist, and the function prototypes in a.h and b.h need argument lists. The header files should be:

a.h

void print_a( void );

b.h

void print_b( void );

The source files a.c and b.c can be improved as follows. First, always include the standard headers before your own headers (see the note below). Second, there's no need to include c.h because print_c is defined before being used. So the function definition serves as the function prototype. Third, print_c needs an argument list: void print_c( void ). Fourth, and this is the key point, the print_c function should be declared as static. The static keyword indicates that the function is only visible in the file where it is defined, i.e. it is not globally visible. Using the static keyword allows you to redefine the function in each .c file.

So your source files should look like this

a.c

#include <stdio.h>
#include "a.h"

static void print_c( void ){
    printf("Printing c from a\n");
}

void print_a( void ){
    print_c();
}

b.c

#include <stdio.h>
#include "b.h"

static void print_c( void ){
    printf("Printing c from b\n");
}

void print_b( void ){
    print_c();
}

main.c

#include "a.h"
#include "b.h"

int main( void ){
    print_a();
    print_b();
    return 0;
}

Note: The reason for including the standard headers first is that the standard headers should be error free, whereas as your header files may not be. If there is a syntax error in your header, and your header is included before a standard header, the error might end up being reported as an error in the standard header, which gets very confusing.

user3386109
  • 34,287
  • 7
  • 49
  • 68
0

Your program has a mistake: there are two externally-visible functions called print_c.

This causes undefined behaviour with no diagnostic required.

Trying to learn about C by trial and error doesn't work very well, especially in this area: compilers/linkers do tend to take advantage of their permission to not tell you what you did wrong. In this case you were lucky that it did.


Addressing specific points:

implementations of c.h.

That is not a thing. Each function is considered separately to other functions.

As an alternative to the above, would a well-defined C application just avoid using any shared header files like this?

There is no problem with sharing header files. The problem is that you had two bodies of a function with the same name.

what if multiple dependencies have methods with the same definitions but different implementations?

You probably mean same declarations, not same definitions. The answer is that you can't do that in C; but there is no need to do it anyway.

Sometimes two different libraries will define a function of the same name, and this leads to errors along the lines of this question. To avoid this, it's conventional for libraries to use a prefix before all of their names.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365