Понимание компиляторов C ++ с точки зрения Java / C # - PullRequest
5 голосов
/ 10 февраля 2009

Я довольно опытный программист на Java / C #, и недавно я начал изучать C ++. Проблема в том, что у меня проблемы с пониманием того, как структурировать различные файлы заголовков и кодов. Похоже, это в основном из-за моего непонимания того, как компилятор связывает все вместе. Я пытался читать некоторые учебники, но мои предубеждения сильно окрашены моими знаниями Java и C #. Например, мне трудно осознать тот факт, что методы и тому подобное могут быть определены в пространстве имен, а не только в определении класса.

Я нашел множество руководств по C ++ -> Java / C #, но практически ничего другого не получилось. Есть ли какие-либо хорошие ресурсы для облегчения перехода Java / C # -> C ++, особенно в отношении понимания процесса компиляции?

Ответы [ 4 ]

4 голосов
/ 10 февраля 2009

FAQ C ++ - отличный ресурс обо всех особенностях C ++, но он, вероятно, немного более продвинут, чем вы ищете - большинство вопросов (не только ответов) - загадки даже довольно опытным разработчикам C ++.

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

Я могу попытаться ответить на ваши конкретные вопросы, заданные выше, но я не знаю, насколько хорошо я сделаю.

Одним из ключей к пониманию взаимосвязи между заголовочными файлами и файлами cpp является понимание идеи «модуля перевода». Файл класса Java может рассматриваться как единица перевода, поскольку это базовая единица, которая компилируется в двоичную форму. В C ++ почти каждый файл cpp является единицей перевода (есть исключения, если вы делаете странные вещи).

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

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

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

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

4 голосов
/ 10 февраля 2009

Это то, что смутило меня, когда я впервые начал использовать C. Книги не очень хорошо описывают правильное использование заголовков и файлов кода.

Компилятор работает, загружая каждый файл .cpp и компилируя его независимо от всех остальных. Первым шагом в компиляции является загрузка всех заголовков, на которые ссылаются операторы #include. Вы можете думать об этом, делая текстовую вставку всего файла foo.h, где есть #include "foo.h".

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

1 голос
/ 11 февраля 2009

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

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

1 голос
/ 10 февраля 2009

Я бы на самом деле рекомендовал держаться подальше от объяснений о компиляторах C ++ и смотреть на объяснения компиляторов C. По моему опыту, они объясняются лучше и не путают вас с проблемами ООП. Ищите материал о С отдельной сборке. Я бы сослал вас на отличный слайд-буклет из моей alma mater, но он не на английском языке.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...