Понимание компиляции C ++ - PullRequest
4 голосов
/ 17 марта 2011

Недавно я осознал, что вообще не знаю, как работает компилятор ac / 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 не # включается там, где существует метод main, где в следующем фрагменте кода

Фрагмент 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, например,

Snippet 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 обрабатывает файлы и исходный код в процессе компиляции.Что здесь происходит?

Ответы [ 7 ]

6 голосов
/ 17 марта 2011

Ваши вопросы на самом деле не о компиляторе, а о том, как ваша IDE обрабатывает всю систему сборки.Системы сборки для большинства проектов 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 file - не просто main.cpp и включенные в него файлы, а затем связывание получающихся объектов.

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

3 голосов
/ 17 марта 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 и т. Д .:)

2 голосов
/ 17 марта 2011

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

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

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

1 голос
/ 17 марта 2011

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

Это будет , а не компилировать файлы с именем * .h (хотя может, если вы откроете это сказать.

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

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

Фрагмент 1

Bote a1.cpp и main.cpp скомпилированы системой сборки отдельно, поэтому, когда она обнаруживает ошибку om a1.cpp, она сообщает об этом.

Фрагмент 2

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

Фрагмент 3

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

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

1 голос
/ 17 марта 2011

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

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

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

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

0 голосов
/ 17 марта 2011

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

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

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

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

0 голосов
/ 17 марта 2011

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

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

...