![]()
Сообщение от Kenny_Dalglish

Как это?
линкеру не сообщили, что нужно линковаться с библиотекой.
![]()
Сообщение от Kenny_Dalglish

зве ошибок на этапе компиляции не должно было появится?
нет. ошибки линкера — это ошибки линковки, а не компиляции.
![]()
Сообщение от Kenny_Dalglish

Вроде бы все подключено.
нет, не подключенно.
и вообще, прекращайте мыслить в такой манере.
очевидно жеж: если бы было подключенно,
этой темы бы не существовало.
зайдите в свойства проекта (у вас ведьвижуал студия),
в раздел Linker
и укажите там имя библиотеки,
и путь, где её можно найти.
![]()
Сообщение от Kenny_Dalglish

Если у Вас депрессия не обязательно это показывать в публичном месте.
а если у меня нет депрессии,
что тогда обязательно/не обязательно
мне делать в публичном/любом другом месте?
вы находитесь на форуме программистов, а не блондинок.
ваше условие — дырявое насквозь.
любой компилятор ткнул бы вас носом в:
«результат возвращается не на всех ветках if»
Добавлено 17 марта 2021 в 22:08
Продолжим обсуждение диаграммы из предыдущей статьи «0.4 – Введение в разработку на C++»:

Обсудим шаги 4–7.
Шаг 4. Компиляция исходного кода
Чтобы скомпилировать программу на C++, мы используем компилятор C++, который последовательно просматривает каждый файл исходного кода (.cpp) в вашей программе и выполняет две важные задачи:
Сначала он проверяет ваш код, чтобы убедиться, что он соответствует правилам языка C++. В противном случае компилятор выдаст вам ошибку (и номер соответствующей строки), чтобы помочь точно определить, что нужно исправить. Процесс компиляции будет прерван, пока ошибка не будет исправлена.
Во-вторых, он переводит исходный код C++ в файл машинного кода, называемый объектным файлом. Объектные файлы обычно имеют имена name.o или name.obj, где name совпадает с именем файла .cpp, из которого он был создан.
Если бы в вашей программе было бы 3 файла .cpp, компилятор сгенерировал бы 3 объектных файла:

Компиляторы C++ доступны для многих операционных систем. Мы скоро обсудим установку компилятора, поэтому сейчас нет необходимости останавливаться на этом.
Шаг 5. Компоновка (линковка) объектных файлов и библиотек
После того, как компилятор создал один или несколько объектных файлов, включается другая программа, называемая компоновщиком (линкером). Работа компоновщика состоит из трех частей:
Во-первых, взять все объектные файлы, сгенерированные компилятором, и объединить их в единую исполняемую программу.

Во-вторых, помимо возможности связывать объектные файлы, компоновщик (линкер) также может связывать файлы библиотек. Файл библиотеки – это набор предварительно скомпилированного кода, который был «упакован» для повторного использования в других программах.
Ядро языка C++ на самом деле довольно небольшое и лаконичное (и вы узнаете многое о нем в последующих статьях). Однако C++ также поставляется с обширной библиотекой, называемой стандартной библиотекой C++ (обычно сокращенно «стандартная библиотека», или STL), которая предоставляет дополнительные функции, которые вы можете использовать в своих программах. Одна из наиболее часто используемых частей стандартной библиотеки C++ – это библиотека iostream, которая содержит функции для печати текста на мониторе и получения от пользователя ввода с клавиатуры. Почти каждая написанная программа на C++ в той или иной форме использует стандартную библиотеку, поэтому она часто подключается к вашим программам. Большинство компоновщиков автоматически подключают стандартную библиотеку, как только вы используете какую-либо ее часть, так что, как правило, вам не о чем беспокоиться.
Вы также можете при желании выполнить линковку с другими библиотеками. Например, если вы собрались написать программу, которая воспроизводит звук, вы, вероятно, не захотите писать свой собственный код для чтения звуковых файлов с диска, проверки их правильности или выяснения, как маршрутизировать звуковые данные к операционной системе или оборудованию для воспроизведения через динамик – это потребует много работы! Вместо этого вы, вероятно, загрузили бы библиотеку, которая уже знала, как это сделать, и использовали бы ее. О том, как связывать библиотеки (и создавать свои собственные!), мы поговорим в приложении.
В-третьих, компоновщик обеспечивает правильное разрешение всех межфайловых зависимостей. Например, если вы определяете что-то в одном файле .cpp, а затем используете это в другом файле .cpp, компоновщик соединит их вместе. Если компоновщик не может связать ссылку с чем-то с ее определением, вы получите ошибку компоновщика, и процесс линковки будет прерван.
Как только компоновщик завершит линковку всех объектных файлов и библиотек (при условии, что всё идет хорошо), вы получите исполняемый файл, который затем можно будет запустить!
Для продвинутых читателей
Для сложных проектов в некоторых средах разработки используется make-файл (makefile), который представляет собой файл, описывающий, как собрать программу (например, какие файлы компилировать и связывать, или обрабатывать какими-либо другими способами). О том, как писать и поддерживать make-файлы, написаны целые книги, и они могут быть невероятно мощным инструментом. Однако, поскольку make-файлы не являются частью ядра языка C++, и вам не нужно их использовать для продолжения изучения, мы не будем обсуждать их в рамках данной серии статей.
Шаги 6 и 7. Тестирование и отладка
Это самое интересное (надеюсь)! Вы можете запустить исполняемый файл и посмотреть, выдаст ли он ожидаемый результат!
Если ваша программа работает, но работает некорректно, то пора немного ее отладить, чтобы выяснить, что не так. Мы обсудим, как тестировать ваши программы и как их отлаживать, более подробно в ближайшее время.
Интегрированные среды разработки (IDE)
Обратите внимание, что шаги 3, 4, 5 и 7 включают в себя использование программного обеспечения (редактор, компилятор, компоновщик, отладчик). Хотя для каждого из этих действий вы можете использовать отдельные программы, программный пакет, известный как интегрированная среда разработки (IDE), объединяет все эти функции вместе. Мы обсудим IDE и установим одну из них в следующем разделе.
Теги
C++ / CppLearnCppДля начинающихКомпиляторЛинкерОбучениеПрограммирование
Что такое компиляция?

Компиляция — преобразование одностороннее, нельзя восстановить исходный код.
Для того, чтобы скомпилировать программу на C++ для некоторой архитектуры X, необязательно устанавливать компилятор С++ на компьютер с архитектурой X.
Не каждая программа, написанная на компилируемом языке, переносима. Т.е. не любая программа, написанная на компилируемом языке, будет работать везде одинаково.
Плюсы и минусы компилируемости в машинный код
Плюсы:
- эффективность: программа компилируется и оптимизируется для конкретного процессора;
- нет необходимости устанавливать сторонние приложения, такие как интерпретатор или виртуальная машина (т.е. для запуска программы, написаной на компилируемом языке, не требуется установка компилятора).
Минусы:
- нужно компилировать для каждой платформы (т.е. программу, написанную на языке, который компилируется в машинный код, недостаточно скомпилировать однажды чтобы её можно было запускать на любой платформе);
- сложность внесения изменения в программу — нужно перекомпилировать заново.
Общая схема

g++ — это такая обёртка над несколькими программами:
- над препроцессором;
- над непосредственно компилятором;
- и над линковщиком.
g++ может сама решать, что вызывать, если не просить что-то делать специально, а просто изначально дать ей файлы, то g++ сама догадается что с ними нужно сделать.
g++ main.cpp square.cpp -o program
Этап 1: препроцессор
Язык препроцессора – это специальный язык программирования, встроенный в C++. Препроцессор работает с кодом на C++ как с текстом.
Команды языка препроцессора называют директивами, все директивы начинаются со знака #. Директива #include позволяет подключать заголовочные файлы к файлам кода.
- #include <foo.h> — библиотечный заголовочный файл,
- #include «bar.h» — локальный заголовочный файл.
Препроцессор заменяет директиву #include «bar.h» на содержимое файла bar.h.
Можно попросить компилятор вызвать только препроцессор и посмотреть что получится. Для компилятора g++ можно использовать ключ -E.
- g++ -E square.cpp -o square_preprocessed.cpp
- g++ -E main.cpp -o main_preprocessed.cpp
Этап 2: компиляция
На вход компилятору поступает код на C++ после обработки препроцессором.
Каждый файл с кодом компилируется отдельно и независимо от других файлов с кодом. Компилируется только файлы с кодом (т.е. *.cpp).
Заголовочные файлы сами по себе ни во что не компилируются, только в составе файлов с кодом.
На выходе компилятора из каждого файла с кодом получается “объектный файл” — бинарный файл со скомпилированным кодом (с расширением .o или .obj).
Если в коде C++ вы вызывает не объявленную функцию, то это ошибка этапа компиляции.
Можно «скормить» файлы с кодом непосредственно компилятору. Для компилятора g++ можно использовать ключ -c.
- g++ -c main.cpp
- g++ -c square.cpp
На выходе получается файлы с расширением .o — это объектные файлы.
- main.o
- square.o
Можно попросить компилятор g++ показать некоторое содержимое объектных файлов, но сам по себе смотреть объектный файл не интересно (там бинарная информация), но можно посмотреть его ассемблированный вид.
g++ -S square.cpp
Получится файл square.s.
Этап 3: линковка (компоновка)
На этом этапе все объектные файлы объединяются в один исполняемый (или библиотечный) файл. При этом происходит подстановка адресов функций в места их вызова.
По каждому объектному файлу строится таблица всех функций, которые в нём определены.
На этапе компоновки важно, что каждая функция имеет уникальное имя. В C++ может быть две функции с одним именем, но разными параметрами. Имена функций искажаются (mangle) таким образом, что в их имени кодируются их параметры.
Например, компилятор GCC превратит имя функции foo
в _Z3fooid. Компилятор g++ также предоставляет возможность обратного преобразования.
c++filt -n _Z3fooid
foo(int, double)
Заметим, что в полученной сигнатуре не участвует возвращаемое значение, потому что в C++ не может быть двух функций с одинаковым именем и одинаковыми параметрами, но разными возвращаемыми значениями.
Аналогично функциям в линковке нуждаются глобальные переменные.
Точка входа — функция, вызываемая при запуске программы. По умолчанию — это функция main:
или
int main(int argc, char ** argv) { return 0; }
Даже для программы, состоящей всего из одного файла и из одной пустой функции int main() { return 0; } все равно требуется ликовка.
Если в коде C++ вы вызываете функцию, которая была объявлена, но не была определена, то это ошибка этапа линковки.
Для того чтобы собрать объектные файлы в один файл их нужно «скормить» компилятору и указать имя исполняемого файла:
g++ square.o main.o -o program
Лекция 3
Многофайловая компоновка
Говорят, что переменная описана (определена), если она объявлена и под неё
выделена память. Рассмотрим следующий пример, пусть в проекте есть два файла:
/* a.cpp */
extern int n; // "Где-то дальше определено"
void f(int); // Предварительное объявление
void main() {
n = 5; f(n);
}
/* b.cpp */
#include <iostream>
int i; // Описание переменной
// Определение функции
void f(int i) {
std::cout << i;
}
В C++ имеет место независимая компиляция: все файлы проекта компилируются
независимо один от другого. Компиляция состоит из этапа собственно компиляции
и этапа линковки.
Заголовочные файлы
Содержат заголовки всех функций и объявления переменных, обычно имеют
расширение *.h (header). Теперь можно вынести объявления функций из всех
файлов в один (заголовочный):
/* myheader.h */
extern int n;
void f(int);
/* myheader.cpp */
#include <iostream>
int n;
void f(int i) {
std::cout << i;
}
/* a.cpp */
#include "myheader.h"
void main() {
n = 5; f(n);
}
/* b.cpp */
#include "myheader.h"
void makeZero() {
n = 0;
}
Имена пользовательских заголовочных файлов в директиве include
заключаются в двойные кавычки, а имена стандартных заголовочных файлов —
в угловые скобки. Стандартные заголовочные файлы расположены в /INCLUDE.
Поиск пользовательских файлов производится в текущем каталоге.
Замечание: inline-функции не сохраняются в исходном коде, так как больше не
используются (а сразу встраиваются на место вызова). Чтобы воспользоваться
такими функциями в другой единице компиляции, их нужно поместить в
заголовочный файл.
Содержимое заголовочных файлов
Что может содержать заголовочный файл:
| Пример | |
|---|---|
| Определения типов | struct point { int x, y; }; |
| Шаблоны типов | template<class T> class V { /* ... */ } |
| Описания функций | extern int strlen(const char*); |
| Определения встраиваемых функций | inline char get() { return *p++; } |
| Описания данных | extern int a; |
| Определения констант | const float pi = 3.141593; |
| Перечисления | enum bool { false, true }; |
| Описания имен | class Matrix; |
| Команды включения файлов | #include <signal.h> |
| Макроопределения | #define Case break;case |
| Комментарии | /* проверка на конец файла */ |
В заголовочном файле никогда не должно быть:
| Пример | |
|---|---|
| Определений обычных функций | char get() { return *p++; } |
| Определений данных | int a; |
| Определений составных констант | const tb[i] = { /* ... */ }; |
Глобальные описания в C++ и необходимость пространств имен
При простом определении глобальных сущностей они объединяются в глобальном пространстве имен. Для создания локального пространства имен используется ключевое слово namespace.
namespace MyNamespace{
... // содержимое пространства имён: типы, функции, что-угодно…
}
Пространства имен и заголовочные файлы
Прототипы функций и глобальные переменные в заголовочных файлах, особенно в крупных проектах, необходимо заключать в пространства имен.
/* header.h */
namespace MyNamespace{
extern int n;
void f();
}
Основные этапы сборки проекта
- Препроцессирование.
- Компиляция каждого
*.cpp-файла в объектный код (файлы*.objили*.o). - Линковка — сборка всех объектных файлов в один исполняемый
(*.exeилиELF).
Ошибки во время линковки
- Одинаковые объявления в одном пространстве имен.
- Ошибки при использовании
#include "*.cpp"(грубая ошибка). - Отсутствие описания функции.
- Отсутствие
main()во всех файлах проектах. - Несколько объявлений
main().
Особенности линковки
- Константы имеют внутреннюю линковку.
inline-функции «погибают» при компиляции.
При сборке программы с включенными опциями для GCOV можно получить ошибку на этапе линковки следующего вида:
/usr/bin/ld: ./a.out: hidden symbol `__gcov_merge_add’ in /usr/lib/gcc/i486-linux-gnu/4.1.3/libgcov.a(_gcov_merge_add.o) is referenced by DSO
/usr/bin/ld: final link failed: Nonrepresentable section on output
Скорее всего причина заключается в том, что к исполняемому модулю линкуются библиотеки, которые тоже были скомпилированы с опциями -fprofile-arcs -ftest-coverage.
Простой пример, иллюстрирующий проблему.
Даны два файла:
=== a.cpp
#include <stdio.h>
extern unsigned long myfunc ();
int main (int argc, char **argv)
{
unsigned long z = myfunc();
printf("%08xn", z);
return 0;
}
=== b.cpp
#include <time.h>
unsigned long myfunc ()
{
return time(0);
}
Из файла b.cpp делаем статическую библиотеку libmy.a, а из файла a.cpp — исполняемый модуль a.out:
g++ -c -fprofile-arcs -ftest-coverage b.cpp
g++ -shared -o libmy.a ./b.o
g++ -c -fprofile-arcs -ftest-coverage a.cpp
g++ -o a.out a.o -L. -lmy
Получаем ошибку вида «undefined reference to `__gcov_init’» — забыли подключить библиотеку libgcov.a.
Подключаем к сборке требуемую библиотеку как обычно:
g++ -c -fprofile-arcs -ftest-coverage b.cpp
g++ -shared -o libmy.a ./b.o
g++ -c -fprofile-arcs -ftest-coverage a.cpp
g++ -o a.out a.o -L. -lmy -lgcov
Вот здесь-то и получаем странную ошибку:
/usr/bin/ld: ./a.out: hidden symbol `__gcov_merge_add' in /usr/lib/gcc/i486-linux-gnu/4.1.3/libgcov.a(_gcov_merge_add.o) is referenced by DSO
/usr/bin/ld: final link failed: Nonrepresentable section on output
Исправить это просто. Нужно подключить библиотеку libgcov.a не только к сборке исполняемого модуля, но и к сборке статической библиотеки:
g++ -c -fprofile-arcs -ftest-coverage b.cpp
g++ -shared -o libmy.a ./b.o -lgcov
g++ -c -fprofile-arcs -ftest-coverage a.cpp
g++ -o a.out a.o -L. -lmy -lgcov
Вот и всё. Так нужно поступить с каждой библиотекой, исходный код которой скомпилирован с опциями -fprofile-arcs -ftest-coverage.