Объединение С++ и 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" {}.