Сочетание C ++ и C - как работает #ifdef __cplusplus? - PullRequest
284 голосов
/ 24 сентября 2010

Я работаю над проектом, в котором много устаревшего C кода. Мы начали писать на C ++ с намерением в конечном итоге также преобразовать унаследованный код. Меня немного смущает, как взаимодействуют C и C ++. Я понимаю, что, оборачивая код C в extern "C", компилятор C ++ не искажает имена кода C , но я не совсем уверен, как это реализовать.

Итак, в верхней части каждого заголовочного файла C (после включаемых охранников) мы имеем

#ifdef __cplusplus
extern "C" {
#endif

и внизу пишем

#ifdef __cplusplus
}
#endif

Между ними у нас есть все наши включенные, typedefs и прототипы функций. У меня есть несколько вопросов, чтобы понять, правильно ли я это понимаю:

  1. Если у меня есть файл C ++ A.hh, который включает заголовочный файл C B.h, включает в себя еще один C заголовочный файл C.h, как это работает? я думаю что когда компилятор входит в B.h, __cplusplus будет определено, так это обернет код с extern "C"__cplusplus не будет определяется внутри этого блока). Так, когда он вступает в C.h, __cplusplus не будет определено и код не будет завернут в extern "C". Это правильно?

  2. Что-то не так с завернуть кусок кода с extern "C" { extern "C" { .. } }? Что будет второй extern "C" делать?

  3. Мы не помещаем эту обертку в файлы .c, а только в файлы .h. Итак, что произойдет, если функция не имеет прототипа? Считает ли компилятор, что это функция C ++?

  4. Мы также используем сторонние код, который написан в C , и делает не иметь такого рода обертку вокруг Это. Каждый раз, когда я включаю заголовок из той библиотеки, я ставлю extern "C" вокруг #include. Это правильный способ борьбы с что?

  5. Наконец, это хорошая идея? Есть ли что-то еще, что мы должны сделать? Мы собираемся смешивать C и C ++ в обозримом будущем, и я хочу убедиться, что мы покрываем все наши базы.

Ответы [ 4 ]

255 голосов
/ 24 сентября 2010

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", и я не вижу ничего плохого в том, чтобы делать это таким образом - просто убедитесь, что вы понимаете, что делаете.

37 голосов
/ 24 сентября 2010
  1. extern "C" не изменяет наличие или отсутствие макроса __cplusplus. Он просто меняет связь и сортировку имен завернутых объявлений.

  2. Вы можете счастливо вкладывать extern "C" блоков.

  3. Если вы скомпилируете свои .c файлы как C ++, то все, что не находится в блоке extern "C" и без прототипа extern "C", будет рассматриваться как функция C ++. Если вы скомпилируете их как C, тогда, конечно, все будет функцией C.

  4. Да

  5. Таким образом вы можете безопасно смешивать C и C ++.

20 голосов
/ 26 августа 2014

Несколько уловок, которые являются коллаборацией к отличному ответу Эндрю Шелански и немного не согласны с , на самом деле не изменяют способ, которым компилятор читает код

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

Ошибки компилятора будут возникать, если вы попытаетесь использовать функции C ++ объявления прототипа, такие как перегрузка.

Ошибки компоновщика возникнут позже, потому что ваша функция окажется не найденной, если у вас нет есть оболочка extern "C" вокруг объявлений и заголовок включен в смесь источника С и С ++.

Одна из причин, по которой людям не рекомендуется использовать компиляцию C в качестве параметра C ++ , заключается в том, что их исходный код больше не переносим. Этот параметр является параметром проекта, поэтому, если файл .c добавлен в другой проект, он не будет скомпилирован как c ++. Я бы предпочел, чтобы люди потратили время на переименование файловых суффиксов в .cpp.

2 голосов
/ 14 декабря 2018

Речь идет о ABI, чтобы позволить приложениям C и C ++ без проблем использовать интерфейсы C.

Поскольку язык C очень прост, генерация кода была стабильной в течение многих лет для различных компиляторов, таких какGCC, Borland C \ C ++, MSVC и т. Д.

В то время как C ++ становится все более популярным, в новый домен C ++ необходимо добавить много вещей (например, в конечном итоге Cfront был заброшен в AT & T, потому что C не мог покрытьвсе функции, которые ему нужны).Например, функция template и генерация кода во время компиляции, различные поставщики компиляторов фактически делали фактическую реализацию компилятора C ++ и компоновщика отдельно, фактические ABI вообще не совместимы с программой C ++.на разных платформах.

Люди могут по-прежнему хотеть реализовать настоящую программу на C ++, но при этом сохранить старый интерфейс C и ABI как обычно, заголовочный файл должен объявить extern "C" {} он сообщает компилятору генерирует совместимый / старый / простой / легкий C ABI для функций интерфейса , если компилятор является компилятором C, а не компилятором C ++.

...