Зачем нам нужен extern "C" {#include <foo.h>} в C ++? - PullRequest
129 голосов
/ 16 сентября 2008

Почему нам нужно использовать:

extern "C" {
#include <foo.h>
}

В частности:

  • Когда мы должны его использовать?

  • Что происходит на уровне компилятора / компоновщика, что требует от нас его использования?

  • Каким образом с точки зрения компиляции / компоновки это решает проблемы, которые требуют от нас его использования?

Ответы [ 11 ]

0 голосов

Декомпилируйте g++ сгенерированный двоичный файл, чтобы увидеть, что происходит

Я перехожу в этом ответе от: Каково влияние extern "C" в C ++? , поскольку этот вопрос считался дубликатом этого.

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Компиляция с GCC 4.8 Linux ELF Вывод:

g++ -c main.cpp

Декомпилировать таблицу символов:

readelf -s main.o

Вывод содержит:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

Интерпретация

Мы видим, что:

  • ef и eg были сохранены в символах с тем же именем, что и в коде

  • другие символы были искажены. Давайте разберем их:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Вывод: оба следующих типа символов были не искалеченными:

  • определяется
  • объявлено, но не определено (Ndx = UND), предоставляется по ссылке или во время выполнения из другого объектного файла

Так что вам понадобится extern "C" при звонке:

  • C из C ++: скажите g++, что следует ожидать появления неупорядоченных символов, созданных gcc
  • C ++ от C: скажите g++, чтобы сгенерировать не исправленные символы для gcc для использования

Вещи, которые не работают во внешнем C

Становится очевидным, что любая функция C ++, требующая искажения имени, не будет работать внутри extern C:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

Пример минимального запуска C из C ++

Для полноты картины и для новичков см. Также: Как использовать исходные файлы C в проекте C ++?

Вызов C из C ++ довольно прост: каждая функция C имеет только один возможный необработанный символ, поэтому никакой дополнительной работы не требуется.

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

к.ц

#include "c.h"

int f(void) { return 1; }

Пробег:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

Без extern "C" сбой ссылки:

main.cpp:6: undefined reference to `f()'

потому что g++ ожидает найти искаженный f, который gcc не дал.

Пример на GitHub .

Минимальная работоспособность C ++ из примера C

Вызов C ++ из немного сложнее: нам нужно вручную создавать не искаженные версии каждой функции, которую мы хотим представить.

Здесь мы покажем, как выставить перегрузки функций C ++ на C.

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

Пробег:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

Без extern "C" происходит сбой:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

потому что g++ генерирует искаженные символы, которые gcc не может найти.

Пример на GitHub .

Проверено в Ubuntu 18.04.

...