Объединение C++ и C - как работает #ifdef cplusplus?

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

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

#ifdef __cplusplus
extern "C" {
#endif

а внизу пишем

#ifdef __cplusplus
}
#endif

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

  1. если у меня есть файл C++ A. hh, который включает в себя C заголовочный файл Б. Ч., включает в себя другой C заголовочный файл С. Ч., как это работает? Я думаю, что когда компилятор переходит в 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++ в обозримом будущем, и я хочу убедиться, что мы покрываем все наши базы.

3 ответов


extern "C" на самом деле не меняет способ, которым компилятор читает код. Если ваш код находится в .C файл, он будет скомпилирован как C, если он находится в a .cpp-файл, он будет скомпилирован как C++ (если вы не сделаете что-то странное для своей конфигурации).

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

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

extern "C" блоки красиво гнезда. Есть также extern "C++" если вы окажетесь безнадежно в ловушке внутри extern "C" регионы, но это не такая хорошая идея с точки зрения чистоты.

теперь, в частности, относительно ваших нумерованных вопросов:

относительно #1: __cplusplus должен быть определен внутри extern "C" блоки. Впрочем, это не имеет значения, поскольку блоки должны быть аккуратно уложены.

относительно #2: __cplusplus будет определен для любой единицы компиляции, которая выполняется через компилятор C++. Как правило, это означает .cpp-файлы и любые файлы, включенные в это .файл cpp. Тот же.ч (или. hh или .hpp или что у вас есть) можно интерпретировать как 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" и я не вижу в этом ничего плохого делать это таким образом, просто убедитесь, что вы понимаете, что вы делаете.


  1. extern "C" не изменяет наличие или отсутствие __cplusplus макрос. Это просто изменяет связь и искажение имен завернутых деклараций.

  2. вложения extern "C" блоки вполне счастливо.

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

  4. да

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


пара gotchas, которые являются коллорариями к превосходному ответу Андрея Шеланского и немного не согласны с на самом деле не меняет способ, которым компилятор читает код

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

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

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

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