Понимание компиляции C++

Недавно я понял, что понятия не имею, вообще говоря, как работает компилятор c/c++. Я признаю, что изначально это произошло из-за попытки понять защиту заголовков, но пришел к выводу, что мне не хватает понимания того, как работает компиляция.

Возьмем, к примеру, Visual C++; Там есть папка «Файлы заголовков», папка «Файлы ресурсов» и папка «Исходные файлы». Есть ли какое-то значение в разделении этих папок и того, что вы в них кладете? Для меня все они являются исходными файлами. Возьмите фрагменты кода:

Фрагмент 1

//a1.h
int r=4;

а также

//a1.cpp
int b  //<--semicolon left out on purpose

а также

//main.cpp
#include <iostream>
#include "a1.h"
void main()
{
   cout << r;
}

Компилятор выдает ошибку a1.cpp(3): фатальная ошибка C1004: обнаружен неожиданный конец файла там, где я ожидал, что этого не произойдет, потому что файл a1.cpp не #included, где существует основной метод, где в следующем фрагмент кода

Фрагмент 2

//a1.h
int r=4 //<--semicolon left out on purpose

а также

//a1.cpp
int b = 4;  

а также

//main.cpp
#include <iostream>
void main()
{
   cout << b;
}

Ошибки, потому что main.cpp(6): ошибка C2065: 'b': необъявленный идентификатор. Если вы включите a1.cpp вот так

Фрагмент 3

//a1.h
int r=4 //<--semicolon left out on purpose

а также

//a1.cpp
int b = 4;  

а также

//main.cpp
#include <iostream>
#include "a1.cpp"
void main()
{
   cout << b;
}

компилятор жалуется a1.obj: ошибка LNK2005: int b (?b@@3HA) уже определен в main.obj. Оба фрагмента 2 и 3 игнорируют тот факт, что int r = 4 не имеет пропущенной точки с запятой, поскольку я подозреваю, что это как-то связано с файлом xxxx.h. Если я удалю файл a1.cpp из проекта на фрагменте 1, он отлично скомпилируется. Ясно, что я ожидал не то, что я получаю. Существует множество книг и руководств о том, как кодировать в cpp, но мало о том, как cpp обрабатывает файлы и исходный код в процессе компиляции. Что здесь происходит?


person Chad Harrison    schedule 17.03.2011    source источник
comment
Вам нужно иметь базовое понимание разницы между файлами заголовков и исходными файлами, а также значение проекта (или Makefile). Не могли бы вы начать с чтения книги K&R? Это не C++, но и не ваш код — это обычный C (за исключением ‹‹).   -  person    schedule 17.03.2011
comment
Я всегда рекомендую людям, плохо знакомым с C/C++, использовать компилятор командной строки и вручную связывать код перед переходом в IDE. Понимание процесса компиляции/связывания/выполнения очень важно в C/C++. Сядьте поудобнее, расслабьтесь и немного поработайте над GCC.   -  person Brandon    schedule 17.03.2011
comment
@Brandon Вы имеете в виду в среде Linux / Unix? Или есть порт для Windows? Кроме того, какую литературу я хотел бы посмотреть?   -  person Chad Harrison    schedule 18.03.2011
comment
Вы можете использовать MinGW или Cygwin, но Visual Studio построена поверх компиляторов MS. C++ называется cl.exe. msdn.microsoft.com/en-us/library /f35ctcxw(v=vs.71).aspx Что касается литературы (stackoverflow.com/questions/388242/)   -  person Brandon    schedule 18.03.2011


Ответы (7)


Ваши вопросы на самом деле не о компиляторе, а о том, как ваша среда разработки обрабатывает всю систему сборки. Системы сборки для большинства проектов C/C++ компилируют каждый файл .c или .cpp отдельно, а затем связывают полученные объектные файлы вместе в окончательный исполняемый файл. В вашем случае ваша IDE компилирует любой файл, который у вас есть в проекте, с расширением имени файла .cpp, а затем связывает полученные объекты. Поведение, которое вы видите, можно объяснить следующим образом:

  1. a1.cpp отсутствует ;, поэтому, когда IDE пытается скомпилировать этот файл, вы получаете сообщение об ошибке «неожиданный конец файла».

  2. b нигде не объявлено в модуле компиляции main.cpp, поэтому вы получаете ошибку о неопределенном идентификаторе.

  3. b существует как в модулях компиляции main.cpp, так и в a1.cpp (очевидно, в a1.cpp и через ваш #include для main.cpp). Ваша IDE компилирует оба этих файла — теперь каждый из a1.o и main.o содержит объект с именем b. При связывании вы получаете ошибку повторяющегося символа.

Важным моментом, который следует усвоить, который объясняет все наблюдаемое вами поведение, является то, что ваша среда IDE компилирует каждый .cpp файл, а не только main.cpp и файлы, которые он включает, а затем связывает полученные объекты.

Я рекомендую настроить тестовый проект командной строки с make-файлом, который вы создадите сами, — это научит вас всему внутреннему устройству систем сборки, а затем вы сможете применить эти знания к внутреннему устройству вашей IDE.

person Carl Norum    schedule 17.03.2011

  1. заголовочные файлы не компилируются
  2. директива #include буквально вставляет содержимое включаемого файла вместо строки #include
  3. Все исходные файлы (независимо от основного) компилируются в файлы .o или .obj.
  4. Все файлы obj связаны между собой вместе с внешними файлами .lib, если таковые имеются.
  5. Вы получаете исполняемый файл.

По пункту 2:

example
//a.h
int

//b.h
x = 

//c.h
5

//main.cpp
#include <iostream>
int main()
{
#include "a.h"
#include "b.h"
#include "c.h"
;

std::cout << x << std::endl; //prints 5 :) 
}

Это не полный ответ, но hth, my2c и т. д. :)

person Armen Tsirunyan    schedule 17.03.2011

Поскольку кажется, что есть два способа понять ваш вопрос, я отвечу на часть понимания компиляции С++.

Я предлагаю вам начать с чтения определения «компилятор» в Википедии. После этого попробуйте поискать в Google учебники по компиляторам, чтобы улучшить свое понимание компиляторов. Если говорить более конкретно о C++, вы можете прочитать о директивах #include и препроцессора (попробуйте поискать эти термины в Google).

Если вы все еще хотите глубже понять компиляторы, я предлагаю книгу по компиляторам. Вы найдете хороший список книг на StackOverflow.

person plmaheu    schedule 17.03.2011
comment
Собственно заказал книгу драконов, на которую ссылается другая публикация, жду на почте :) - person Chad Harrison; 17.03.2011
comment
@hydroparadise: Хороший выбор. Это плотно, но хорошо объяснено. - person plmaheu; 17.03.2011

Оператор #include вставляет этот файл в файл, создающий #include. Таким образом, ваш фрагмент 3 main.cpp становится следующим перед компиляцией.

    // main.cpp    
    // All sorts of stuff from iostream
    //a1.cpp
    int b = 4;
    void main()
    {
        cout << b;
    }

Причина, по которой вы получаете ошибку компоновщика, заключается в том, что вы определяете b дважды. Он определен в файлах a.cpp и main.cpp.

Вы можете прочитать об объявлении и определении.

person David V    schedule 17.03.2011

Вы указываете системе сборки, какие файлы компилировать. В случае Visual C++ он автоматически компилирует любой файл с именем "*.cpp", который вы добавляете в проект. Хотя вы можете зайти в настройки проекта и запретить это делать.

Он не компилирует файлы с именами *.h (хотя может, если вы прямо укажете это).

Директива #include обрабатывается компилятором до какой-либо компиляции (она называется препроцессором). По сути, он берет файл, на который указывает, и вставляет его в компилируемый исходный файл в тот момент, когда в файле появляется директива #include. Затем компилятор компилирует все это как единое целое.

Итак, в ваших примерах:

Фрагмент 1

Bote a1.cpp и main.cpp компилируются системой сборки отдельно. ТАК, когда он сталкивается с ошибкой a1.cpp, он сообщает об этом.

Фрагмент 2

Обратите внимание, что он компилирует эти файлы отдельно, не зная друг друга, поэтому, когда вы указываете b в main.cpp, он не знает, что b определен в a1.cpp.

Фрагмент 3

Теперь вы включили a1.cpp в main.cpp, поэтому он компилирует main.cpp, видит определение для b и говорит: «Хорошо, у меня есть b в глобальной области видимости». Затем он компилирует a1.cpp и говорит: «ОК, у меня есть b в глобальной области видимости».

Теперь компоновщик вмешивается и = пытается соединить a1 и main вместе, теперь он говорит вам, эй, у меня есть 2 b ate глобальной области видимости. Не хорошо.

person zdan    schedule 17.03.2011

Компилятор берет исходные файлы оттуда, где вы ему говорите. В случае Visual C++ есть IDE, сообщающая компилятору, что делать, и разные папки существуют, потому что именно так IDE упорядочивает файлы.

Кроме того, ошибка во фрагменте 2 связана с компоновщиком, а не с компилятором. Компилятор скомпилировал main.cpp и a1.cpp в объектные файлы main.obj и a1.obj, а затем компоновщик пытается создать исполняемый файл, объединяющий эти объектные файлы, но переменная b находится как в a1.obj (напрямую), так и в main.obj (через включение a1.cpp), поэтому вы получаете «уже определенную» ошибку.

person Chris Card    schedule 17.03.2011

Проблемы, которые вы видите в случае 1 и 3, специфичны для VS. VS, по-видимому, пытается скомпилировать как main.cpp, так и a1.cpp.

Случай 1: когда VS пытается скомпилировать a1.cpp, в котором есть синтаксическая ошибка (отсутствует точка с запятой), компиляция завершается неудачно.

Случай 2: Вы не объявили переменную b в файле main.cpp или во включенных файлах. Таким образом, компиляция не выполняется.

Случай 3: Это ошибка компоновщика. Из-за включения int b было объявлено как в main.cpp, так и в a1.cpp. Поскольку ни одна из них не является ни статической, ни внешней, две глобальные переменные с одинаковым идентификатором были объявлены в одной области видимости. Это не разрешено.

person swegi    schedule 17.03.2011