2

Might be a stupid (and really simple) question, but I've wanted to try since I don't know where to find an answer for that. I'm realizing some book, and I've started googling something - I was actually kinda curious why, if we have files like these:

file1.c

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

int main(void){
    printf("%s:%s:%d \n", __FILE__, __FUNCTION__, __LINE__);
    foo();
    return 0;
}

file2.h

void foo(void);

and

file2.c

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

void foo(void) {
    printf("%s:%s:%d \n", __FILE__, __func__, __LINE__);
    return;
}

compiling it with:

gcc file1.c file2.c -o file -Wall

Why is it a good practice to include the header file of file2.h which contains prototype of the foo function in the same file that foo is declared? I totally understand attaching it to file1.c, while we should use the header file to define the interface of each module, rather than writing it "raw", but why attaching header file with the prototype to the file where it is declared (file2.c)? -Wall option flag also does not say anything if I won't include it, so why people say it is "the correct way"? Does it help avoiding errors, or is it just for clearer code?

Those code samples are taken from this discussion: Compiling multiple C files in a program

Where some user said it is 'the correct way'.

todovvox
  • 170
  • 10
  • 1
    One reason is easy to demonstrate: Change file2.c to have `void foo(int)` (without changing the header), then compile both with and without including file2.h. – 1201ProgramAlarm Oct 23 '21 at 16:28
  • 1
    Another reason is that if you don't include the header, you could only call `foo()` after its definition. With the header, any function in `file2.c` could call `foo()` regardless of its position in the file (after the `#include` of the header, of course) – sj95126 Oct 23 '21 at 16:35
  • @1201ProgramAlarm I see, so it is helpful for debugging, am I right? We would literally want to pass arguments of int type to foo, but the compiler would show errors, since the prototype in the header file doesn't accept any arguments, right? So basically, we do that so we can debug our code faster? – todovvox Oct 23 '21 at 16:41
  • @sj95126 oh, you are totally right. If I would want to expand the file by adding some functions, then if some function would use the `foo()` function, then the `foo()` function should be above the function that wants to use it. Totally forgot about that. Thank you. – todovvox Oct 23 '21 at 16:44

3 Answers3

2

To answer this question, you should have a basic understanding of the difference between the compiler and the linker. In a nuttshell, the compiler, compilers each translation unit (C file) alone then it's the linker's job to link all the compiled files together.

For instance, In the above code the linker is the one who is searching where the function foo() called from main() exists and links to it.

The compiler step comes first then the linker.

Let's demonstrate an example where including file2.h in file2.c comes handy:

file2.h
void foo(void);
file2.c
#include <stdio.h>
#include "file2.h"

void foo(int i) {
    printf("%s:%s:%d \n", __FILE__, __func__, __LINE__);
    return;
}

Here the prototype of foo() is different from its definition.

By including file2.h in file2.c so the compiler can check whether the prototype of the function is equivalent to the definition of it, if not then you will get a compiler error.

What will happen if file2.h is not included in file2.c?

Then the compiler won't find any issue and we have to wait until the linking step when the linker will find that there is no matching for the function foo() called from main() and it will through an error.

Why bother then if the linker, later on, will find out the error anyway?

Because in big solutions there might be hundreds of source codes that take so much time to be compiled so waiting for the linker to raise the error at the end will waste a great amount of time.

Alaa Mahran
  • 663
  • 4
  • 12
2

This is The Only True ReasonTM:

If the compiler encounters a call of a function without a prototype, it derives one from the call, see standard chapter 6.5.2.2 paragraph 6. If that does not match the real function's interface, it's undefined behavior in most cases. At best it does no harm, but anything can happen.

Only with a high enough warning level, compilers emit diagnostics like warnings or errors. That's why you should always use the highest warning level possible, and include the header file in the implementation file. You will not want to miss this chance to let your code being checked automatically.

the busybee
  • 10,755
  • 3
  • 13
  • 30
1

C doesn’t mangle symbols usually (there are some exceptions eg. on Windows). Mangled symbols would carry type information. Without it, the linker trusts that you didn’t make mistakes.

If you don’t include the header, you can declare the symbol to be one thing, but then define it to be whatever else. Eg. in the header you might declare foo to be a function, and then in the source file you can define it to be a totally incompatible function (different calling convention and signature), or even not a function at all – say a global variable. Such a project may link but won’t be functional. The error may be in fact hidden, so if you don’t have solid tests in place, you won’t catch it until a customer lets you know. Or worse, there’s a news article about it.

In C++ the symbol carries information about its type, so if you declare one thing and then define something with same base name but an incompatible type, the linker will refuse to link the project, since a particular symbol is referenced but never defined.

So, in C you include the header to prevent mistakes that the tools can’t catch, that will result in a broken binary. In C++, you do it so that you’ll get perhaps an error during compilation instead of later in the link phase.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Could you come up with any example of broken binary? Because when I've changed the definiton of `foo()` to `void foo(int ar)` in `file2.c` and left as it was in header (`void foo(void)`), and then wrote foo(1) in `file1.c`, then it said that this function has too many arguments. So it does not let me type code like this anyways. So how can we make a broken binary by doing that? Could you come up with any example? – todovvox Oct 23 '21 at 17:30