Заголовочные файлы C и компиляция/привязка
Я знаю, что в заголовочных файлах есть форвардные объявления различных функций, структур и т.д., которые используются в файле .c
, который "вызывает" #include
, правильно? Насколько я понимаю, "разделение сил" происходит следующим образом:
Заголовочный файл: func.h
Исходный файл C: func.c
исходный файл 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 и другими директивами, замененными препроцессор).
Четвертый этап - это тот, который создает окончательный исполняемый файл. После компиляции единицы компилятор отмечает несколько фрагментов кода в качестве ссылок, которые должны быть решены компоновщиком. Задача компоновщика заключается в поиске среди многих единиц компиляции и разрешении ссылок на внешние единицы компиляции.