46

I know that header files have forward declarations of various functions, structs, etc. that are used in the .c file that 'calls' the #include, right? As far as I understand, the "separation of powers" occurs like this:

Header file: func.h

  • contains forward declaration of function

    int func(int i);
    

C source file: func.c

  • contains actual function definition

    #include "func.h"
    
    int func(int i) {
        return ++i ;
    }
    

C source file source.c (the "actual" program):

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

int main(void) {
    int res = func(3);
    printf("%i", res);
}

My question is: seeing that the #include is simply a compiler directive that copies the contents of the .h in the file that #include is in, how does the .c file know how to actually execute the function? All it's getting is the int func(int i);, so how can it actually perform the function? How does it gain access to the actual definition of func? Does the header include some sort of 'pointer' that says "that's my definition, over there!"?

How does it work?

4

5 回答 5

48

Uchia Itachi gave the answer. It's the linker.

Using GNU C compiler gcc you would compile a one-file program like

gcc hello.c -o hello # generating the executable hello

But compiling the two (or more) file program as described in your example, you would have to do the following:

gcc -c func.c # generates the object file func.o
gcc -c main.c # generates the object file main.o
gcc func.o main.o -o main # generates the executable main

Each object file has external symbols (you may think of it as public members). Functions are by default external while (global) variables are by default internal. You could change this behavior by defining

static int func(int i) { # static linkage
    return ++i ;
}

or

/* global variable accessible from other modules (object files) */
extern int global_variable = 10; 

When encountering a call to a function, not defined in the main module, the linker searches all the object files (and libraries) provided as input for the module where the called function is defined. By default you probably have some libraries linked to your program, that's how you can use printf, it's already compiled into a library.

If you are really interested, try some assembly programming. These names are the equivalent of labels in assembly code.

于 2013-08-31T12:52:44.520 回答
18

It's the linker that handles all that. The compiler just emits a special sequence in the object file saying "I have this external symbol func, please resolve it" for the linker. Then linker sees that, and searches all other object files and libraries for the symbol.

于 2013-08-31T12:35:32.273 回答
5

A declaration of a symbol without a definition within the same compilation unit tells the compiler to compile with a placeholder for that symbol's address into an object file.

The linker will see that a definition for the symbol is required, and will look for external definitions of the symbol in libraries and other object files.

If the linker finds a definition, the placeholder in the original object file will be replaced with the found address in the final executable.

于 2013-08-31T12:41:27.797 回答
2

The header provides access not only to other .c files in the same program, but likewise to libraries that may be distributed in binary form. The relationship of one .c file to another is exactly the same as a library that depends on another.

Since a programming interface needs to be in text form no matter the format of the implementation, header files make sense as a separation of concerns.

As others have mentioned, the program that resolves function calls and accesses between libraries and sources (translation units) is called the linker.

The linker does not work with headers. It just makes a big table of all the names that are defined in all the translation units and libraries, then links those names to the lines of code that access them. Archaic usage of C even allows for calling a function without any implementation declaration; it was just assumed that every undefined type was an int.

于 2013-08-31T12:39:20.050 回答
2

Generally when you compile a file like this:

gcc -o program program.c

You really are calling a driver program, which does the following:

  • preprocessing (if you asked for it to be a seperate step) using cpp.
  • compiling (may be integrated with preprocessing) using cc1
  • assembling, using as (gas, the GNU Assembler).
  • linking using collect2, which also uses ld (the GNU linker).

Typically, during the first 3 stages, you create a simple object file (.o extension), which gets created by compiling a compilation unit (that is a .c file, with the #include and other directives replaced by the preprocessor).

The 4th stage is the one that creates the final executable. After compilation of a unit, the compiler marks several pieces of code as references that need to be resolved by the linker. The linker's job is to search among many compilation units and resolve references to external compilation units.

于 2013-08-31T13:14:13.800 回答