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

319

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

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

#ifdef __cplusplus
extern "C" {
#endif

а внизу пишем

#ifdef __cplusplus
}
#endif

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

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

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

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

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

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

dublev
источник
2
Вкратце, это лучшее объяснение: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (я получил по ссылке )
anhldbk
Вам не нужно
Эдвард Карак,

Ответы:

290

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

Андрей Шеланский
источник
10
Хорошо для упоминания, extern "C++"когда ваш заголовок / код C ++ захвачен глубоко внутри некоторого кода на C
deddebme
1
Я написал простую программу на Си. Внутри я добавил блок #ifdef __cplusplus и добавил printf ("__cplusplus определен \ n"); в этом. Если я скомпилирую его с помощью gcc, «__cplusplus определенный» не будет напечатан, но если я скомпилирую его с помощью g ++, он будет напечатан. Итак, я понимаю, что __cplusplus означает, что компилятор - это компилятор C ++ (вы это сказали) Разве это не правильно? (потому что я видел, как вы говорили, что «__cplusplus должен быть определен внутри внешних блоков« C »». Можем ли мы явно определить __cplusplus?
Чан Ким
1
В то время как вы должны быть в состоянии определить (почти) все, что вы хотите, весь смысл в __cplusplusтом, чтобы определить C++, используется ли он против C, поэтому определение его вручную / явно не поддается его цели ...
nurchi
Extern "C" действительно не о том, как компилятор просматривает исходный файл, а о том, как он просматривает заголовочный файл. Структуры могут иметь разные размеры при компиляции как C против C ++, конечно, есть искажение имен и, возможно, другие различия.
Ник
39
  1. extern "C"не меняет наличие или отсутствие __cplusplusмакроса. Он просто меняет связь и сортировку имен завернутых объявлений.

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

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

  4. да

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

Энтони Уильямс
источник
Если вы компилируете .cфайлы как C ++, то все компилируется как код C ++, даже если он находится в extern "C"блоке. extern "C"Код не может использовать функции , которые зависят от C ++ , призывающих конвенций (например , перегрузка операторов) , но тело функции по - прежнему скомпилированные в C ++, со всем , что влечет за собой.
Дэвид С.
21

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

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

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

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

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

Энди Дент
источник
1
Это была таинственная причина, вырвать мои волосы. На самом деле нужно опубликовать где-то.
Митчелл Керри
3

Речь идет о 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 ++.

Бо Чжоу
источник