it-swarm.com.ru

Сочетание C++ и C - как работает #ifdef __cplusplus?

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

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

#ifdef __cplusplus
extern "C" {
#endif

а внизу пишем

#ifdef __cplusplus
}
#endif

Между ними у нас есть все наши include, 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"do?

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

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

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

274
dublev

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

239
Andrew Shelansky
  1. extern "C" не изменяет наличие или отсутствие макроса __cplusplus. Он просто меняет связь и сортировку имен завернутых объявлений.

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

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

  4. Да

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

36
Anthony Williams

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

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

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

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

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

19
Andy Dent

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

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

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

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

0
Bo Zhou