C ++: функциональность компилятора и компоновщика - PullRequest
5 голосов
/ 02 марта 2012

Я хочу точно понять, на какую часть компилятора программы смотрится и на что смотрит компоновщик. Поэтому я написал следующий код:

#include <iostream>
using namespace std;
#include <string>

class Test {
private:
    int i;

public:
    Test(int val) {i=val ;}
    void DefinedCorrectFunction(int val);
    void DefinedIncorrectFunction(int val);
    void NonDefinedFunction(int val);

    template <class paramType>
    void  FunctionTemplate (paramType val) { i = val }
};

void Test::DefinedCorrectFunction(int val)
{
    i = val;
}

void Test::DefinedIncorrectFunction(int val)
{
    i = val
}

void main()
{
    Test testObject(1);
    //testObject.NonDefinedFunction(2);
    //testObject.FunctionTemplate<int>(2);

}

У меня есть три функции:

  • DefinedCorrectFunction - это нормальная функция, объявленная и определенная правильно.
  • DefinedIncorrectFunction - эта функция объявлена ​​правильно, но реализация неверна (отсутствует;)
  • NonDefinedFunction - только объявление. Нет определения.
  • FunctionTemplate - шаблон функции.

    Теперь, если я скомпилирую этот код, я получу ошибку компилятора для отсутствующего символа ';' в DefinedIncorrectFunction. Предположим, я исправил это, а затем закомментировал testObject.NonDefinedFunction (2). Теперь я получаю ошибку компоновщика. Теперь закомментируйте testObject.FunctionTemplate (2). Теперь я получаю сообщение об ошибке компилятора для отсутствующего символа ';'.

Для шаблонов функций я понимаю, что компилятор не затрагивает их, если они не вызваны в коде. Так что пропавшая без вести ';' компилятор не жаловался, пока я не вызвал testObject.FunctionTemplate (2).

Для функции testObject.NonDefinedFunction (2) компилятор не жаловался, а компоновщик -. Насколько я понимаю, все, что волновало компилятор, должны были знать, что объявлена ​​функция NonDefinedFunction. Это не заботилось о реализации. Затем компоновщик пожаловался, потому что не смог найти реализацию. Пока все хорошо.

Я запутался, когда компилятор пожаловался на DefinedIncorrectFunction. Он не искал реализацию NonDefinedFunction, но прошел через DefinedIncorrectFunction.

Так что мне немного неясно, что именно делает компилятор и что делает компоновщик. Насколько я понимаю, линкер связывает компоненты с их вызовами. Поэтому, когда вызывается NonDefinedFunction, он искал скомпилированную реализацию NonDefinedFunction и жаловался. Но компилятор не заботился о реализации NonDefinedFunction, но сделал это для DefinedIncorrectFunction.

Буду очень признателен, если кто-нибудь сможет это объяснить или дать какую-то ссылку.

Спасибо.

Ответы [ 8 ]

5 голосов
/ 02 марта 2012

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

Если компиляция продолжается без помех, создаются объектные файлы ,Объектные файлы имеют сложную структуру, но в основном содержат пять вещей:

  1. Заголовки - информация о файле
  2. Код объекта - Код на машинном языке (Этот код не может выполняться сам по себе в большинствеслучаи)
  3. Информация о перемещении - какие части кода должны иметь адреса, измененные при фактическом выполнении
  4. Таблица символов - Символы, на которые ссылается код.Они могут быть определены в этом коде, импортированы из других модулей или определены компоновщиком
  5. Отладочная информация - используется отладчиками

Компилятор компилирует код и заполняеттаблица символов с каждым знакомым символом.Символы относятся как к переменным, так и к функциям.Ответ на Этот вопрос объясняет таблицу символов.

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

Замечание

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

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

Так в вашем конкретномcase -

  1. DefinedIncorrectFunction () - Компилятор получает определение функции и начинает его компилировать, чтобы создать объектный код и вставить соответствующую ссылку в таблицу символов.Сбой компиляции из-за синтаксической ошибки, поэтому компилятор прерывается с ошибкой.
  2. NonDefinedFunction () - Компилятор получает объявление, но не определяет, поэтому он добавляет запись в таблицу символов и отмечает компоновщик для добавления соответствующих значений (так каккомпоновщик будет обрабатывать кучу объектных файлов, возможно, это определение присутствует в каком-либо другом объектном файле).В вашем случае вы не указываете какой-либо другой файл, поэтому компоновщик прерывается с ошибкой undefined reference to NonDefinedFunction, поскольку он не может найти ссылку на соответствующую запись таблицы символов.

Чтобы понять это далее, допустим, что ваш код структурирован следующим образом

File-try.h

#include<string>
#include<iostream>


class Test {
private:
    int i;

public:
    Test(int val) {i=val ;}
    void DefinedCorrectFunction(int val);
    void DefinedIncorrectFunction(int val);
    void NonDefinedFunction(int val);

    template <class paramType>
    void  FunctionTemplate (paramType val) { i = val; }
};

Файл try.cpp

#include "try.h"


void Test::DefinedCorrectFunction(int val)
{
    i = val;
}

void Test::DefinedIncorrectFunction(int val)
{
    i = val;
}

int main()
{

    Test testObject(1);
    testObject.NonDefinedFunction(2);
    //testObject.FunctionTemplate<int>(2);
    return 0;
}

Давайте сначала толькоСкопируйте и соберите код, но не связывайте его

$g++ -c try.cpp -o try.o
$

Этот шаг выполняется без проблем.Итак, у вас есть объектный код в try.o.Давайте попробуем скомпоновать это

$g++ try.o
try.o: In function `main':
try.cpp:(.text+0x52): undefined reference to `Test::NonDefinedFunction(int)'
collect2: ld returned 1 exit status

Вы забыли определить Test :: NonDefinedFunction.Давайте определим это в отдельном файле.

File-try1.cpp

#include "try.h"

void Test::NonDefinedFunction(int val)
{
    i = val;
}

Давайте скомпилируем его в объектный код

$ g++ -c try1.cpp -o try1.o
$

Снова это успешно.Давайте попробуем связать только этот файл

$ g++ try1.o
/usr/lib/gcc/x86_64-redhat-linux/4.4.5/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: ld returned 1 exit status

Нет основной ссылки, поэтому ссылка не будет !!

Теперь у вас есть два отдельных объектных кода со всеми необходимыми компонентами.Просто передайте ОБА из них компоновщику, и пусть он сделает все остальное

$ g++ try.o try1.o
$

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

4 голосов
/ 02 марта 2012

Скажи, что хочешь съесть немного супа, иди в ресторан.

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

Вы звоните официанту, чтобы принести вам суп.Однако то, что оно есть в меню, не означает, что оно есть на кухне.Это может быть устаревшее меню, возможно, кто-то забыл сказать шеф-повару, что он должен готовить суп.Итак, вы снова уходите.(как ошибка компоновщика, что он не может найти символ)

4 голосов
/ 02 марта 2012

Я думаю, что это ваш вопрос:

Я запутался, когда компилятор пожаловался на DefinedIncorrectFunction. Он не искал реализацию NonDefinedFunction, но прошел через функцию DefinedIncorrectFunction.

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

2 голосов
/ 02 марта 2012

То, что делает компилятор и что делает компоновщик, зависит от реализации: легальная реализация может просто хранить токенизированный источник в «компиляторе» и делать все в компоновщике.Современные реализации do откладывают все больше и больше компоновщику для лучшей оптимизации.И многие ранние реализации шаблонов даже не смотрели код шаблона до времени ссылки, кроме того, что соответствовали скобкам, чтобы знать, где закончился шаблон.С точки зрения пользователя, вас больше интересует, является ли ошибка «требующей диагностики» (которая может быть выдана компилятором или компоновщиком) или она не определена.

В случае DefinedIncorrectFunction у вас есть исходный текст, который реализация должна проанализировать.Этот текст содержит ошибку, для которой требуется диагностика.В случае NonDefinedFunction: если используется функция, отказ предоставить определение (или предоставить более одного определения) в полной программе является нарушением правила одного определения, которое является неопределенным поведением.Диагностика не требуется (но я не могу представить реализацию, которая не обеспечивала ее для отсутствующего определения функции, которая использовалась).

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

Это несколько модифицируется в случаях, таких как встроенные функции, где вам разрешено повторять определение в каждой единице перевода, и крайне изменено шаблонами, поскольку многие ошибки не могутбыть обнаруженным до момента создания экземпляра.В случае шаблонов стандарт оставляет реализациям большую свободу: по крайней мере, компилятор должен проанализировать шаблон достаточно, чтобы определить, где заканчивается шаблон.Однако в стандарт добавлены такие вещи, как typename, что позволяет гораздо больше разбирать перед созданием экземпляра.Однако в зависимых контекстах некоторые ошибки не могут быть обнаружены до создания экземпляра, что может иметь место во время компиляции или во время соединения - ранние реализации предпочитали создание экземпляра во время соединения;Инстанцирование времени компиляции доминирует сегодня и используется VC ++ и g ++.

2 голосов
/ 02 марта 2012

Компилятор проверяет, что исходный код соответствует языку и придерживается семантики языка. Выходными данными компилятора является объектный код.

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

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

1 голос
/ 02 марта 2012

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

Если, с другой стороны, в вашем коде есть синтаксическая ошибка, компилятор не может даже скомпилировать, и вы получитеошибка на данном этапе.Макросы и шаблоны могут вести себя немного иначе, не вызывая ошибок, если они не используются (шаблоны примерно такие же, как макросы с несколько более приятным интерфейсом), но это также зависит от серьезности ошибки.Если вы запутались настолько, что компилятор не может понять, где заканчивается шаблонный / макрос-код и начинается обычный код, он не сможет скомпилироваться.

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

1 голос
/ 02 марта 2012

Ах, но вы могли бы иметь NonDefinedFunction (int) в другом модуле компиляции.

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

  • Какие символы (функции / переменные / и т. Д.) Определены.
  • На какие символы есть ссылки, но они не определены.В этом случае компоновщик должен разрешить ссылки путем поиска в других связанных модулях.Если это невозможно, вы получите ошибку компоновщика.
1 голос
/ 02 марта 2012

Отсутствующая точка с запятой является синтаксической ошибкой, и поэтому код не должен компилироваться.Это может произойти даже в реализации шаблона.По сути, есть стадия синтаксического анализа, и, хотя для человека очевидно, что «исправлять и восстанавливать» компилятор не должен этого делать.Он не может просто «представить точку с запятой, потому что это то, что вы имели в виду» и продолжить.

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

...