Ответ
Линкер — это программа, которая принимает на вход один или несколько объектных файлов (.o, .obj), полученных от компилятора, и библиотеки, а на выходе создаёт единый исполняемый файл или библиотеку. Его ключевая задача — разрешение внешних ссылок.
Конкретные функции линкера:
- Связывание (Resolution): Объектный файл содержит «ссылки» на имена функций и глобальных переменных, определённых в других файлах или библиотеках (например,
coutиз стандартной библиотеки). Линкер находит эти определения и подставляет правильные адреса. - Объединение секций: Код из разных
.oфайлов собирается в секцию.text, данные — в.dataили.bssи т.д. - Распределение адресов: Назначает окончательные виртуальные адреса для всех частей программы.
Типичные ошибки линкера:
undefined reference to 'symbol': Линкер не нашёл определение для объявленного символа. Причина: забыли подключить объектный файл или библиотеку, опечатка в имени.multiple definition of 'symbol': Обнаружено более одного определения одного символа. Частая причина — определение функции в заголовочном файле безinlineилиstatic, который включили в несколько.cpp-файлов.
Пример:
// math_utils.h
#pragma once
extern int global_counter; // Объявление глобальной переменной
int add(int a, int b); // Объявление функции
// math_utils.cpp
#include "math_utils.h"
int global_counter = 0; // Определение глобальной переменной
int add(int a, int b) { // Определение функции
return a + b;
}
// main.cpp
#include "math_utils.h"
#include <iostream>
int main() {
global_counter++;
std::cout << add(5, 3); // Вызов функции, определённой в другом файле
return 0;
}
Линкер соберёт main.o и math_utils.o, связав вызов add и обращение к global_counter с их определениями.
Ответ 18+ 🔞
Давай разберёмся с этим линкером, а то у меня от этих ошибок уже волосы дыбом встают. Представь, что у тебя есть куча деталек от конструктора — это твои объектные файлы (.o). Компилятор их наштамповал и вывалил тебе на ковёр. А линкер — это такой мужик с похмелья, который должен из этой кучи собрать одну работающую машинку, то есть исполняемый файл.
Его главная работа — разрешить внешние ссылки. Это когда одна деталька кричит: «Эй, а где тут болтик М5?», а другая орет: «А мне нужна гаечка от cout!». Линкер ползает по всей куче, сопит, ищет эти болтики-гаечки в других объектниках или в библиотеках, и скручивает всё вместе. Если не находит — начинается: «undefined reference», и всё, пиздец, машинка не едет.
Что он конкретно делает, этот чувак:
- Связывает (Resolution): Это самое главное. Ты в
main.cppнаписалadd(5, 3), а где тело этойadd? В другом файле,math_utils.cpp. Компилятор вmain.oоставил записку: «Найти функциюadd». Линкер берёт эту записку, идёт вmath_utils.o, находит там готовый кусок кода и говорит: «Вот же она, сука, бери адрес и подставляй». То же самое с глобальными переменными. - Объединяет секции: Код из всех
.oфайлов он сгребает в одну кучу (секция.text), глобальные переменные — в другую (.data), неинициализированные — в третью (.bss). Получается аккуратный расклад, а не свалка. - Раздаёт адреса: Он как почтальон в новом районе — назначает каждой функции и каждой переменной их окончательный, уникальный адрес в памяти будущей программы. Чтобы когда программа запустится, процессор знал, куда прыгать.
Типичные косяки, которые он выдает (и от которых хочется биться головой об стену):
undefined reference to 'symbol': Классика жанра! Это значит линкер облазил всё, но так и не нашёл, где определён этот самыйsymbol. Причины — ебанько: либо забыл добавить в сборку нужный.cppфайл или библиотеку (-lm, например), либо опечатался в имени, либо объявил функцию какextern, но так и не написал её реализацию. Чувство доверия к себе после такой ошибки — ноль ебать.multiple definition of 'symbol': А вот это обратная, не менее весёлая ситуация. Линкер нашёл аж несколько определений одного и того же символа. Чаще всего так бывает, когда ты зачем-то определение функции (то есть её тело с{ }) запихнул в заголовочный файл (*.h), и этот хедер включили (#include) в несколько.cppфайлов. В итоге каждый.cppфайл скомпилировал свою копию этой функции, и линкер охуел: «Мужики, а какую из них мне брать-то?». Спасение — делать функциюinlineилиstatic, или просто выносить определение в.cpp.
Ну и пример, чтобы совсем понятно стало, ёпта:
// math_utils.h
#pragma once
extern int global_counter; // Просто объявление: "Где-то есть такая переменная"
int add(int a, int b); // Объявление: "Где-то есть такая функция"
// math_utils.cpp
#include "math_utils.h"
int global_counter = 0; // А вот это уже определение. Вот она, переменная!
int add(int a, int b) { // И это определение. Вот оно, тело функции!
return a + b;
}
// main.cpp
#include "math_utils.h"
#include <iostream>
int main() {
global_counter++; // Ссылка на переменную из другого файла
std::cout << add(5, 3); // Вызов функции из другого файла
return 0;
}
Компилятор честно сделает два объектника: main.o и math_utils.o. В main.o будут дырки: «Ищу global_counter» и «Ищу add». А потом приползёт линкер, возьмёт оба файла, найдёт в math_utils.o и переменную, и функцию, заткнёт эти дырки правильными адресами и склеит всё в один исполняемый файл. Если math_utils.o ему не дать — будет истерика undefined reference, ядрёна вошь. Вот и вся магия, без ебушки-воробушки.