Заголовочные файлы C и компиляция/привязка

Я знаю, что в заголовочных файлах есть форвардные объявления различных функций, структур и т.д., которые используются в файле .c, который "вызывает" #include, правильно? Насколько я понимаю, "разделение сил" происходит следующим образом:

Заголовочный файл: func.h

  • содержит следующее объявление функции

    int func(int i);
    

Исходный файл C: func.c

  • содержит фактическое определение функции

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

исходный файл C source.c ( "актуальная" программа):

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

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

Мой вопрос: видеть, что #include - это просто директива компилятора, которая копирует содержимое .h в файл, в котором находится #include, как файл .c знает, как реально выполнить функционировать? Все, что он получает, это int func(int i);, так как же он может выполнять функцию? Как он получает доступ к фактическому определению func? В заголовке есть какой-то "указатель", в котором говорится "что мое определение, там!"?

Как это работает?

Ответы

Ответ 1

Учиния Итачи дал ответ. Это компоновщик.

Используя компилятор GNU C gcc, вы должны скомпилировать однофайльную программу, например

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

Но компилируя две (или более) файловые программы, как описано в вашем примере, вам нужно будет сделать следующее:

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

Каждый объектный файл имеет внешние символы (вы можете думать об этом как о публичных членах). Функции по умолчанию являются внешними, а глобальные переменные по умолчанию являются внутренними. Вы можете изменить это поведение, указав

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

или

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

При встрече с вызовом функции, не определенной в основном модуле, компоновщик выполняет поиск всех объектных файлов (и библиотек), предоставляемых в качестве входных данных для модуля, в котором определена вызываемая функция. По умолчанию у вас, вероятно, есть некоторые библиотеки, связанные с вашей программой, что вы можете использовать printf, он уже скомпилирован в библиотеку.

Если вам действительно интересно, попробуйте программировать сборку. Эти имена эквивалентны ярлыкам в ассемблере.

Ответ 2

Это компоновщик, который обрабатывает все это. Компилятор просто испускает специальную последовательность в объектном файле, говоря: "У меня есть этот внешний символ func, пожалуйста, разрешите его" для компоновщика. Тогда линкер видит это и ищет все остальные объектные файлы и библиотеки для символа.

Ответ 3

Объявление символа без определения в одном модуле компиляции сообщает компилятору скомпилировать с заполнителем для этого символьного адреса в файл объекта.

Компонент увидит, что требуется определение символа, и будет искать внешние определения символа в библиотеках и других объектных файлах.

Если компоновщик находит определение, местозаполнитель в исходном объектном файле будет заменен на найденный адрес в конечном исполняемом файле.

Ответ 4

Заголовок обеспечивает доступ не только к другим файлам .c в одной и той же программе, но также к библиотекам, которые могут быть распределены в двоичной форме. Связь одного файла .c с другим является точно такой же, как библиотека, которая зависит от другой.

Поскольку интерфейс программирования должен быть в текстовой форме независимо от формата реализации, файлы заголовков имеют смысл как разделение проблем.

Как уже упоминалось, программа, которая разрешает вызовы функций и обращения между библиотеками и источниками (единицы перевода), называется компоновщиком.

Компонент не работает с заголовками. Он просто создает большую таблицу всех имен, которые определены во всех единицах перевода и библиотеках, а затем связывает эти имена с строками кода, которые обращаются к ним. Архаичное использование C even позволяет вызывать функцию без объявления реализации; предполагалось, что каждый тип undefined был int.

Ответ 5

Обычно, когда вы компилируете такой файл:

gcc -o program program.c

Вы действительно вызываете программу драйвера, которая выполняет следующие действия:

  • preprocessing (если вы попросили его сделать отдельный шаг) с помощью cpp.
  • компиляция (может быть интегрирована с предварительной обработкой) с помощью cc1
  • используя as (газ, Ассемблер GNU).
  • используя collect2, который также использует ld (компоновщик GNU).

Как правило, в течение первых трех этапов вы создаете простой объектный файл (.o extension), который создается путем компиляции единицы компиляции (это файл .c, С#include и другими директивами, замененными препроцессор).

Четвертый этап - это тот, который создает окончательный исполняемый файл. После компиляции единицы компилятор отмечает несколько фрагментов кода в качестве ссылок, которые должны быть решены компоновщиком. Задача компоновщика заключается в поиске среди многих единиц компиляции и разрешении ссылок на внешние единицы компиляции.