Объединение С++ и C - как работает #ifdef __cplusplus?
Я работаю над проектом с большим количеством устаревшего кода C. Мы начали писать на С++, имея в виду, наконец, преобразовать устаревший код. Я немного смущен тем, как взаимодействуют C и С++. Я понимаю, что, завернув код C с помощью extern "C"
, компилятор С++ не будет искажать имена кодов C, но я не совсем уверен, как это реализовать.
Итак, в верхней части каждого заголовочного файла C (после включения защиты),
#ifdef __cplusplus
extern "C" {
#endif
и внизу, пишем
#ifdef __cplusplus
}
#endif
В промежутке между двумя мы имеем все наши включенные, typedefs и прототипы функций. У меня есть несколько вопросов, чтобы понять, правильно ли я это понимаю:
-
Если у меня есть файл С++ A.hh, который
включает заголовочный файл C B.h,
включает в себя другой заголовок C C.h,
как это работает? я думаю что
когда компилятор переходит в B.h,
__cplusplus
будет определено, поэтому
будет завершать код с помощью extern "C"
(и __cplusplus
не будет
определенные внутри этого блока). Так,
когда он переходит в C.h,
__cplusplus
не будет определено
и код не будет завернут в
extern "C"
. Правильно ли это?
-
Есть ли что-то не так с
обертывание фрагмента кода
extern "C" { extern "C" { .. } }
?
Что будет второй extern "C"
делать?
-
Мы не помещаем эту оболочку в файлы .c, а только файлы .h. Итак, что произойдет, если функция не имеет прототипа? Компилятор считает, что это С++-функция?
-
Мы также используем сторонние
код, который написан в C, и делает
нет такой обертки вокруг
Это. Каждый раз, когда я включаю заголовок
из этой библиотеки, которую я ставил
a extern "C"
вокруг #include.
Это правильный способ справиться с
что?
-
Наконец, это хорошая идея?
Есть ли что-нибудь еще, что мы должны делать?
Мы будем смешивать C и С++
в обозримом будущем, и я
хочу, чтобы мы покрывали все
наши базы.
Ответы
Ответ 1
extern "C"
не меняет способ чтения компилятором кода. Если ваш код находится в .c файле, он будет скомпилирован как C, если он находится в .cpp файле, он будет скомпилирован как С++ (если вы не делаете что-то странное для своей конфигурации).
Что означает extern "C"
, влияет на связь. Функции С++ при компиляции имеют искаженные имена - вот что делает возможным перегрузку. Имя функции изменяется на основе типов и количества параметров, так что две функции с тем же именем будут иметь разные имена символов.
Код внутри extern "C"
- это код С++. Есть ограничения на то, что вы можете сделать во внешнем блоке "C", но все они связаны с привязкой. Вы не можете определить какие-либо новые символы, которые нельзя построить с помощью C-ссылки. Это означает, что нет классов или шаблонов, например.
extern "C"
блокирует гнездо красиво. Там также extern "C++"
, если вы оказываетесь безнадежно запертыми внутри регионов extern "C"
, но это не такая хорошая идея с точки зрения чистоты.
Теперь, в частности, относительно ваших нумерованных вопросов:
Относительно # 1: __cplusplus должен быть определен внутри блоков extern "C"
. Это не имеет значения, так как блоки должны располагаться аккуратно.
Относительно # 2: __cplusplus будет определен для любой единицы компиляции, которая выполняется через компилятор С++. Как правило, это означает, что файлы .cpp и любые файлы включены в этот файл .cpp. То же самое .h(или .hh или .hpp или what-have-you) можно интерпретировать как C или С++ в разное время, если к ним относятся разные единицы компиляции. Если вы хотите, чтобы прототипы в файле .h ссылались на имена символов C, они должны иметь extern "C"
при интерпретации С++, и они не должны иметь extern "C"
при интерпретации C - следовательно, #ifdef __cplusplus
проверка.
Чтобы ответить на ваш вопрос № 3: функции без прототипов будут иметь С++-связь, если они находятся в .cpp файлах, а не внутри блока extern "C"
. Это прекрасно, хотя, потому что, если у него нет прототипа, его можно вызвать только другими функциями в том же файле, а затем вам вообще не нравится, как выглядит ссылка, потому что вы не планируете использовать эту функцию все равно вызывается чем-либо за пределами того же модуля компиляции.
Для # 4 у вас это точно. Если вы включаете заголовок для кода, который имеет C-ссылку (например, код, который был скомпилирован компилятором C), тогда вы должны extern "C"
заголовок - таким образом вы сможете связаться с библиотекой. (В противном случае ваш линкер будет искать функции с именами типа _Z1hic
, когда вы искали void h(int, char)
5: Подобное смешивание является распространенной причиной использования extern "C"
, и я не вижу ничего плохого в этом, так что убедитесь, что вы понимаете, что вы делаете.
Ответ 2
-
extern "C"
не изменяет наличие или отсутствие макроса __cplusplus
. Это просто изменяет привязку и манипулирование именами завернутых объявлений.
-
Вы можете довольно долго вложить блокировку extern "C"
.
-
Если вы скомпилируете свои файлы .c
как С++, то все, что не в блоке extern "C"
, и без прототипа extern "C"
будет рассматриваться как функция С++. Если вы скомпилируете их как C, то, конечно, все будет функцией C.
-
Да
-
Вы можете смело смешивать C и С++ таким образом.
Ответ 3
Пару gotchas, которые являются комбинациями с Andrew Shelansky превосходным ответом и не согласны с этим, действительно не меняют способ, которым компилятор читает код
Поскольку ваши прототипы функций скомпилированы как C, вы не можете перегружать одни и те же имена функций с разными параметрами - это одна из ключевых особенностей управления именем компилятора. Он описывается как проблема связи, но это не совсем так - вы получите ошибки как от компилятора, так и от компоновщика.
Ошибки компилятора будут, если вы попытаетесь использовать функции С++ для описания прототипа, такие как перегрузка.
Ошибки компоновщика появятся позже, потому что ваша функция окажется не найденной, если вы не имеете внешнюю оболочку "C" вокруг деклараций, а заголовок включен в смесь C и Источник С++.
Одна из причин препятствовать людям использовать компиляцию C как параметр С++, потому что это означает, что их исходный код больше не переносится. Этот параметр является настройкой проекта, поэтому, если файл .c удаляется в другой проект, он не будет скомпилирован как С++. Я бы предпочел, чтобы люди потратили время на переименование суффиксов файлов на .cpp.
Ответ 4
Это о ABI, чтобы позволить и приложениям на C, и C++ использовать интерфейсы C без каких-либо проблем.
Поскольку язык C очень прост, генерация кода была стабильной в течение многих лет для различных компиляторов, таких как GCC, Borland C\C++, MSVC и т.д.
В то время как C++ становится все более и более популярным, в новый домен C++ необходимо добавить много вещей (например, наконец, Cfront был заброшен в AT & T, потому что C не может охватить все функции, в которых он нуждается). Например, из-за особенностей шаблона и генерации кода во время компиляции различные поставщики компиляторов фактически реализовали фактическую реализацию компилятора и компоновщика C++ отдельно, фактические ABI вообще не совместимы с программой C++ на разных платформ.
Люди могут по-прежнему хотеть реализовать настоящую программу в C++, но все еще сохраняют старый интерфейс C и ABI как обычно, заголовочный файл должен объявить extern "C" {}.