Требуется ли extern C только в объявлении функции?

Я написал функцию C ++, которую мне нужно вызвать из программы C. Чтобы сделать его вызываемым из C, я указал extern "C" в объявлении функции . Затем я скомпилировал код C ++, но компилятор (Dignus Systems / C ++) сгенерировал искаженное имя для функции. Таким образом, очевидно, что extern "C" не соблюдается.

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

Технически extern "C" нужно указывать только в объявлении функции. Это правильно? (Хороший пример приведен в C ++ FAQ об этом.) Следует ли также указать это в определении функции?

Вот пример, чтобы продемонстрировать это:

/* ---------- */
/* "foo.h"    */
/* ---------- */

#ifdef __cplusplus
extern "C" {
#endif

/* Function declaration */
void foo(int);

#ifdef __cplusplus
}
#endif

/* ---------- */
/* "foo.cpp"  */
/* ---------- */

#include "foo.h"

/* Function definition */
extern "C"               // <---- Is this needed?
void foo(int i) {
  // do something...
}

Моя проблема может быть результатом неправильного кодирования чего-либо или может быть обнаружена ошибка компилятора. В любом случае, я хотел проконсультироваться с stackoverflow, чтобы убедиться, что я знаю, какой технически "правильный" путь.


person bporter    schedule 04.09.2009    source источник
comment
Вы уверены, что действительно получите искажение foo, если оставите extern C в foo.c в примере кода, который вы показываете? Или это просто что-то произошло в другом, более сложном коде? Я довольно часто видел эту проблему как симптом того, что я забыл включить foo.h в foo.c.   -  person Brooks Moses    schedule 04.09.2009
comment
@ Брукс Мозес: Отличный момент. В моем фактическом коде, который немного сложнее, чем в этом примере foo, я включаю заголовок в исходный файл cpp. Что заставило меня подумать, что компилятор искажает имя, было следующее: если я не включу «extern C» в определение функции, то в листинге компилятора будет показан внешний символ, foo_FPFPCc_v. Когда включен extern C, в листинге отображается внешний символ foo.   -  person bporter    schedule 04.09.2009
comment
@bporter - может быть интересно поэкспериментировать с тем, что делает компилятор, на вашем упрощенном примере. Если он показывает такое же поведение, вы можете отправить уведомление поставщику. Если он не показывает то же самое, вам следует отследить, что происходит в вашей реальной сборке, потому что это будет указывать на то, что вводится неправильный заголовок (или что-то еще заставляет пропустить объявление функции).   -  person Michael Burr    schedule 04.09.2009
comment
@Michael Burr - это хороший момент, что это может быть проблема сборки (например, добавляется какой-то другой заголовок и т. Д.). Я попробую, и если я узнаю что-то новое, что может быть полезно другим, я оставлю здесь комментарий. Спасибо!   -  person bporter    schedule 05.09.2009


Ответы (6)


'extern "C"' не должен требоваться в определении функции, если он есть в объявлении и уже виден при компиляции определения. В стандарте конкретно указано (спецификации связи 7.5 / 5):

Функция может быть объявлена ​​без спецификации связывания после того, как была обнаружена явная спецификация связывания; на связь, явно указанную в предыдущем объявлении, такое объявление функции не влияет.

Тем не менее, я обычно помещаю 'extern "C"' в определение, потому что на самом деле это функция с внешней связью «C». Многие люди ненавидят, когда в объявлениях используется ненужный, избыточный материал (например, добавление virtual в переопределение методов), но я не один из них.

person Michael Burr    schedule 04.09.2009
comment
@Michael Burr - Спасибо за ответ. Похоже, что «extern C» не требуется в определении, если он указан в предыдущем объявлении. Однако, как вы упомянули, я могу продолжить и указать это в обоих местах. (На самом деле, мне может понадобиться, если я не пойму, что моя проблема связана с чем-то другим, кроме ошибки компилятора. Если она действительно похожа на ошибку компилятора, я сообщу об этом поставщику, чтобы они могли разобраться в ней.) - person bporter; 05.09.2009
comment
Я думал, что я единственный, кто ставит virtual на переопределения метода :-) - person Mawg says reinstate Monica; 16.03.2017

Изменить:
Похоже, я неправильно понял вопрос. В любом случае, я пробовал:


// foo.cpp
/* Function definition */

#include "foo.h"

void foo(int i) {
 //do stuff
}
void test(int i)
{
// do stuff
}

// foo.h
#ifdef __cplusplus
extern "C" {
#endif

/* Function declaration */
void foo(int);

#ifdef __cplusplus
}
#endif

void test(int);

Используя команду nm для просмотра символов из скомпилированного файла:


linuxuser$ nm foo.o
00000006 T _Z4testi
         U __gxx_personality_v0
00000000 T foo

Это ясно указывает на то, что имя функции, объявленной как extern «C», не искажается, а ключевое слово extern «C» не требуется при определении.
Если бы это требовало, каждый код библиотеки C, написанный без extern «C», был бы непригоден для использования в программах на C ++.

person sud03r    schedule 04.09.2009
comment
@Neeraj - Спасибо за ваш пример. Это то, что я ожидал увидеть и в моем случае. Похоже, мой компилятор искажает имя функции foo, если я не укажу extern C в объявлении и определении. Я думаю, что вы и некоторые другие авторы правы в том, что «extern C» не требуется в определении, если он указан в предыдущем объявлении. - person bporter; 05.09.2009

Только что столкнулся с такой ситуацией ... Не из приятных впечатлений.

В одном из моих c файлов было объявлено следующее:

void unused_isr(void) {}
void ADC_IRQHandler(void)     __attribute__ ((weak, alias("unused_isr"))); 

Далее где-то в cpp файле, который я определил:

void ADC_IRQHandler(void) {                                                                                  
    ...
}

И я забыл изменить предварительное объявление на:

void ADC_IRQHandler(void);

Мне потребовалось некоторое время, прежде чем я понял, что делаю все правильно в отношении преобразования AD, но мне не удалось добавить «extern C» в определение!

extern "C" void ADC_IRQHandler(void) {                                                                                  
    ...
}

Только мои два цента, почему в определенных обстоятельствах может быть полезно иметь привычку также добавлять это к определению.

person Anne van Rossum    schedule 07.11.2014

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

Таким образом, заголовок может сбить вас с толку, все, что видит компилятор, - это файл cpp, и если заголовок не включен с extern "C" обратно в ваш cpp (что я обычно вижу), тогда extern "C" потребуется быть где-то в файле cpp (либо в определении, либо в другом объявлении), чтобы компилятор CXX мог знать, как сделать это с помощью связи C, компилятор не заботится о заголовке, а только о компоновщике.

person othane    schedule 21.01.2015

extern "C" вокруг определения не требуется. Вы можете уйти, просто поместив это в декларацию. Одно примечание в вашем примере ...

#ifdef __cplusplus
extern "C" {
#endif

/* Function declaration */
void foo(int);

#ifdef __cplusplus
}
#endif

Ваш код ищет макрос препроцессора "__cplusplus".

Хотя это обычно реализуется, в зависимости от вашего компилятора это может быть определено или не определено. В вашем примере вы также используете extern "C" вокруг объявления, но там вы не проверяете макрос «__cplusplus», поэтому я подозреваю, что он сработал, как только вы это сделали.

См. комментарии ниже. Стандартный C ++ требует, чтобы макрос __cplusplus был определен препроцессором.

person Community    schedule 04.09.2009
comment
@whitej - Макрос __cplusplus определяется моим конкретным компилятором, но вы правы, что он не является стандартным. Кроме того, я использую этот макрос в заголовке, потому что именно здесь я объявляю функцию (и именно здесь большинство людей хотели бы узнать об интерфейсе), и он позволяет мне включать заголовок как в файлы C, так и в файлы C ++. Компиляторы C не поддерживают extern C, поэтому этот макрос вставляет extern C только тогда, когда это уместно. Обратите внимание, что макрос не нужен в foo.cpp, если указан extern C, потому что foo.cpp всегда будет компилироваться компилятором C ++. - person bporter; 05.09.2009
comment
По стандарту макрос __cplusplus должен быть определен при компиляции модуля C ++ - нет абсолютно никаких проблем с его использованием. - person Michael Burr; 05.09.2009
comment
И хотя __cplusplus часто требуется в заголовках (поскольку они могут быть включены модулями C или C ++), обычно это не требуется в файлах .c или .cpp, поскольку они обычно предназначены для компиляции как C или C ++, но не обоих ( конечно бывают исключения, но обычно их нет). - person Michael Burr; 05.09.2009
comment
@Michael Burr - я всегда воспринимал __cplusplus как должное, но не понимал, что это требуется стандартом. Спасибо за разъяснение. - person bporter; 06.09.2009

Должно быть и то, и другое. Компилятору необходимо знать, как использовать имя символа C и соглашения о вызовах при компиляции сайтов вызовов (которые могут видеть только объявление), и компилятор также должен знать, как генерировать имя символа C и использовать соглашения о вызовах C при компиляции само определение функции (которое может не видеть никаких других объявлений).

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

person Tyler McHenry    schedule 04.09.2009
comment
Это неверно в описанном случае, когда foo.h включен в foo.c, и это вводит в заблуждение при использовании поведения, требуемого стандартом. Если кто-то включает foo.h в foo.c - как всегда нужно делать в любом случае, чтобы компилятор мог проверить, что объявление действительно точное! - тогда нет необходимости (кроме, возможно, ясности для читателей) - помещать extern C в определение в foo.c. - person Brooks Moses; 04.09.2009