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

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

Поэтому, если мы решим разделить нашу программу на несколько файлов C++, что очень распространено, нам нужно связать эти файлы вместе в одну программу. Даже если у вас нет функций во внешнем файле, например, вы написали всю свою программу в одном файле, приложению все равно нужно знать, где находится точка входа, другими словами, где находится основная функция.

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

Ниже мы модифицировали версию math.cpp из наших предыдущих частей. У нас есть только 2 функции войти и умножить. Удалите любой другой файл из проекта ( console.cpp, outro.cpp). Для этой части нам нужен только math.cpp.

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

Это потому, что нам не хватает основной функции точки входа. Так как наша компиляция разделена на 2 этапа компиляцияи связывание, мы получаем разные типы сообщений об ошибках, связанных с каждым этапом. Если мы допустим синтаксическую ошибку, с которой должен иметь дело компилятор, мы получим ошибку вроде C2143. Помните, что ошибка компилятора начинается с C, а компоновщик — с LNK.

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

Если мы перейдем к настройкам компоновщика и перейдем в расширенный раздел, вы увидите, что мы можем указать пользовательскую точку входа.

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

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

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

Теперь, если мы вернемся к math.cpp и попытаемся скомпилировать код, мы получим ошибку, и вы будете знать, что это ошибка компиляции, потому что код ошибки начинается с буквы C, говорящей нам, что журнал не найден, потому что этот файл не знает, что функция под названием log вообще существует.

Итак, мы продолжим и добавим объявление функции журнала в math.cpp. Идите вперед и скомпилируйте его снова (Ctrl+f7), вы увидите, что на этот раз компиляция сработала.

Теперь давайте продолжим и соберем весь наш проект, мы получим несколько ошибок компиляции, говорящих нам, что cout не найден. Чтобы исправить это, просто включите iostream, и ваш проект будет работать нормально.

Теперь давайте рассмотрим один тип ошибки компоновки, который очень распространен, и мы можем назвать его «неразрешенный внешний символ». Это то, что происходит, когда компоновщик не может найти то, что ему нужно. Чтобы продемонстрировать это, вернитесь к log.cpp и измените имя функции с log на что-то другое, например logger.

Если мы вернемся назад к math.cpp, мы все еще оставим наше объявление как log, поэтому он все еще ожидает, что функция будет называться log. поэтому этот файл math.cpp, конечно, все равно будет компилироваться, потому что это не связывает.

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

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

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

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

Мы получим ошибку связывания, и вы можете подумать, ПОЧЕМУ ??? . Мы нигде не вызываем функцию умножения, разве она не должна была просто полностью удалить функцию, поскольку это, по сути, мертвый код, который никогда не используется. НЕПРАВИЛЬНО!!!!

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

Если мы напишем слово «статический» перед «умножить», это в основном означает, что эта функция умножения объявлена ​​только для этой единицы перевода, которой является этот файл math.cpp. Так как умножение никогда не вызывается внутри этого файла. Если мы создадим его сейчас, мы не получим никаких ошибок связывания.

В этом случае мы фактически изменили имя функции (log to logger), однако важно не только имя функции. Если мы изменим его на журнал из регистратора и снова построим его. У нас не будет ошибок

Но затем, например, мы меняем тип возвращаемого значения на int, а затем просто возвращаем какое-то значение. Если мы создадим его сейчас, мы получим ошибку, потому что в math.cpp мы указали, что функция журнала была функцией void, и поэтому он будет искать функцию с именем log, которая возвращает пустоту, а также принимает один параметр. .

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

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

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

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

Теперь, если мы скомпилируем этот math.cpp, мы не получим ошибки. Но если мы сейчас создадим проект, мы получим ошибку связывания, а та, которую мы получим, говорит нам, что у нас есть «функция журнала, уже определенная в log.obj, найдена одна или несколько определенных функций»

Таким образом, в этом случае компоновщик не знает, какую функцию журнала связать с math.cpp или с функцией log.cpp. Теперь вы можете подумать, что такого рода ошибки случаются нечасто, и что вы умнее этого :p . Однако иногда это может быть сложно, давайте посмотрим на пример.

Сначала удалите определение журнала в math.cpp. Теперь давайте создадим заголовочный файл, назовем его log.h. Теперь вырежьте функцию журнала и вставьте ее в log.h. Измените свой файл следующим образом

Так здорово, что мы вызываем функцию журнала как из функции умножения внутри файла math.cpp, так и из файла log.cpp. Теперь идите вперед и создайте свой проект.

log уже определен в log.obj, поэтому мы получаем сообщение об ошибке с повторяющимися символами, однако выше вы можете видеть, что у нас есть только одно определение log, оно находится внутри файла log.h.

Почему он жалуется на несколько символов?
Теперь мы возвращаемся к тому, как работают операторы включения. Помните, когда мы включаем заголовочный файл, мы просто берем содержимое этого заголовочного файла и помещаем его туда, где наш оператор включения таков, что на самом деле произошло. Он берет функцию журнала из log.h, переходит в log.cpp и вставляет ее туда.

а затем он также вставит его в main.cpp

Теперь вы можете видеть, что, конечно же, у нас есть 2 функции журнала. Итак, как мы это исправим? Ну, у нас есть несколько вариантов здесь

  • Мы можем перейти к log.h и пометить функцию журнала как статическую. Это означает, что связывание, которое происходит с этой функцией журнала, должно быть только внутренним.

это означает, что эта функция журнала, когда она будет включена в log.cpp и math.cpp, будет только внутренней для этого файла (внутренней для math.cpp и аналогично для log.cpp). Это похоже на то, что мы сделали с Multiple в предыдущем примере. Таким образом, у log и math будут свои собственные версии этой функции, называемой log, и она не будет видна никаким другим объектным файлам, поэтому, если мы создадим проект сейчас, мы получим успешную сборку.

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

встроенный просто означает, что он возьмет наше фактическое тело функции и заменит им вызов. Таким образом, в log.cpp вызов журнала станет таким:

Это тоже будет хорошо строиться. Теперь есть еще один способ исправить это, и это, вероятно, мы должны сделать в этой ситуации, и это

  • просто переместите определение функции журнала в одну единицу перевода, потому что сейчас происходит то, что эта функция журнала включается в две единицы перевода log.cpp и math.cpp, что в первую очередь вызывает ошибку. Таким образом, мы могли бы переместить его в одну из этих существующих единиц перевода. Так как эта функция называется log и именно это она и делает, мы поместим ее в log.cpp.

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

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

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

Есть предложения? Подключим Twitter, Github, LinkedIn