Поєднання C ++ та C - як працює #ifdef __cplusplus?


319

Я працюю над проектом, який має багато застарілого коду С. Ми почали писати на C ++ з наміром врешті-решт перетворити застарілий код. Я трохи розгублений у взаємодії C і C ++. Я розумію , що обгортання C коду extern "C"на C ++ компілятор буде спотворювати C кодових імен, але я не зовсім впевнений , як це реалізувати.

Отже, у верхній частині кожного файлу заголовка C (після включення охоронців) у нас є

#ifdef __cplusplus
extern "C" {
#endif

а внизу пишемо

#ifdef __cplusplus
}
#endif

Між ними, у нас є всі наші прототипи включає в себе, typedefs та функції. У мене є кілька запитань, щоб побачити, чи я правильно це розумію:

  1. Якщо у мене є файл C ++ A.hh, який включає файл заголовка C Bh, включає ще один файл заголовка C Ch, як це працює? Я думаю, що коли компілятор крокує в Bh, __cplusplusбуде визначено, тому він оберне код 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 ++ в осяжному майбутньому, і я хочу переконатися, що ми покриваємо всі наші бази.



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. Це означає, наприклад, що немає класів чи шаблонів.

extern "C"блоки красиво гніздяться. Є також, extern "C++"якщо ви опинилися в безнадійному захопленні всередині extern "C"регіонів, але це не така гарна ідея з точки зору чистоти.

Тепер, зокрема, щодо нумерованих питань:

Щодо № 1: __cplusplus залишатиметься визначеним всередині extern "C"блоків. Це не має значення, оскільки блоки повинні гніздо акуратно.

Щодо № 2: __cplusplus буде визначений для будь-якого блоку компіляції, який запускається через компілятор C ++. Як правило, це означає .cpp файли та будь-які файли, що входять до цього .cpp-файлу. Той самий .h (або .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", і я не бачу нічого поганого в тому, щоб зробити це таким чином - просто переконайтеся, що ви розумієте, що ви робите.


10
Добре згадати, extern "C++"коли ваш C ++ заголовок / код потрапляє всередину якогось коду С
deddebme

1
Я написав просту програму на С. Всередині нього я додав #ifdef __cplusplus блок і додав printf ("__ cplusplus визначений \ n"); в цьому. Якщо я компілюю його з gcc, "визначено __cplusplus" не друкується, але якщо я компілюю його з g ++, він надрукується. Отже, я вважаю, що __cplusplus означає, що компілятор є компілятором C ++ (ви це сказали). Чи не правильно? (тому що я бачив, як ви говорите: "__cplusplus слід визначати всередині зовнішніх" C "блоків". Чи можемо ми чітко визначити __cplusplus?
Чан Кім

1
Хоча ви маєте змогу визначити (майже) все, що хочете, вся суть __cplusplusполягає у тому, щоб визначити, чи C++використовується він проти C, тому визначення його вручну / явно не відповідає цілі цього ...
nurchi

Зовнішній "C" насправді не в тому, як компілятор переглядає вихідний файл, це все про те, як він переглядає файл заголовка. Структури можуть мати різний розмір при компіляції як C vs 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-інтерфейси без жодних проблем.

Оскільки мова С дуже проста, генерація коду була стабільною протягом багатьох років для різних компіляторів, таких як GCC, Borland C \ C ++, MSVC тощо.

Хоча C ++ стає все більш популярним, у новий домен C ++ потрібно додати багато речей (наприклад, нарешті, Cfront був залишений у AT&T, оскільки C не міг охопити всі необхідні йому функції). Такі, як функція шаблону та генерування коду часу компіляції, минулі часи різні постачальники компіляторів фактично виконували реалізацію компілятора C ++ та лінкера окремо, власне ABI зовсім не сумісні з програмою C ++ на різних платформах.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.