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