Прототипы функций и заголовочные файлы
Может пожалуйста кто нибудь мне объяснить? К примеру я пишу свою библиотеку(очень грубо сказано). Так вот, у меня есть три файла "main.cpp", "f.cpp", "f.h". Так вот в "f.cpp" у меня лежит реализация функции foo() и в ней я подключаю "f.h".
#include"f.h"
int foo()
{
return 1;
}
В файле "f.h" у меня подключаются необходимые библиотеки, и лежит прототип функции foo().
#include<iostream>
#include<Windows.h>
int foo();
И в третьем файле мы подключаем
"f.h" и выполняем определенные действия.
#include"f.h"
int main()
{
std::cout << foo();
return 0;
}
Почему мы подключаем только .h-файл с прототипами функций, ведь при подключении ТОЛЬКО .h-файла компилятор не поймет где лежит реализация этих функций и по идее должен выдать ошибку, но он не выдает никаких ошибок.
Ответы (3 шт):
При компиляции в заголовках находятся предварительные объявления функций. Это даёт компилятору понять типы входящие и типы результата функции, но в оъектном файле (.obj или .o) только прописываются имена этих функций.
После создания этих объектных файлов начинается линковка. Она связывает эти имена с реальными указателями внутри выполняемого файла.
По сути процесс простой. Сначала компилятор компилирует каждый файл и для каждого файла создает обьектный файл, в котором вместо вызовов функций находятся какие-то идентификаторы (зависят от компилятора). После компиляции все эти обьектные файлы передаются линковщику, который заменяет эти идентификаторы адресами уже скомпилированных функций. Потом добавляет код запуска и (если есть) библиотеки и создает исполняемый файл.
То, что для "простого программиста" работа компилятора, на самом деле три больших этапа - препроцессор, собственно компиляция и линковка.
На первом этапе компилятор подставляет include (да, грубо говоря, он просто копипастит содержимое по месту) и разворачивает макросы.
На втором этапе полученные "большие" cpp файлы (единицы компиляции) переводятся в бинарный вид. На этот момент те функции, для которых нет тела ещё не страшны. Компилятору нужно знать только их сигнатуру, что бы организовать правильный вызов (то есть, заполнить стек/регистры и подставить call). Правда самого адреса ещё может не быть.
И тут вступает линковщик, который берет все эти скомпилированные файлы, нужные внешние библиотеки, копирует в один большой файл, добавляет туда код/данные, что бы операционная система смогла это запустить и вместо адресов-заглушек подставляет реальные адреса. И вот здесь уже нужно знать, где реально находится вызываемая функция.
BTW, включение windows.h в свои заголовочные файлы - плохая идея, особенно, если это библиотека.