Что нужно учитывать при взаимодействии кода на C и C++?

Ответ

При связывании кода на C и C++ необходимо учитывать различия в компоновке (linking), обработке исключений и управлении ресурсами. Основные правила:

  1. Соглашение о компоновке (Name Mangling): C++ компилятор "декорирует" имена функций, кодируя информацию о типах параметров для поддержки перегрузки. C компилятор этого не делает. Чтобы C++ код мог вызывать C-функции, их объявления нужно обернуть в extern "C".

    header.h (общий для C и C++):

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void c_library_function(int param); // Объявление C-функции
    
    #ifdef __cplusplus
    }
    #endif
  2. Исключения: Исключения C++ не должны пересекать границу C-кода. C-функции не знают о механизме раскрутки стека C++. Любые исключения должны быть перехвачены и обработаны внутри C++ кода до возврата в C.

  3. Совместимость типов и ABI: Используйте только POD-типы (Plain Old Data), которые гарантированно одинаковы в C и C++: базовые типы (int, double), структуры без методов, объединения, указатели. Избегайте передачи std::string, std::vector или объектов с виртуальными функциями.

  4. Управление памятью: Память, выделенная с помощью malloc в C, должна освобождаться free. Память, выделенная new в C++, должна освобождаться delete. Нельзя смешивать аллокаторы. Лучшая практика — предоставлять из C-библиотеки парные функции создания/уничтожения.

    // C-библиотека
    struct MyCStruct* create_struct() { return (struct MyCStruct*)malloc(sizeof(struct MyCStruct)); }
    void destroy_struct(struct MyCStruct* p) { free(p); }
    
    // C++ код
    extern "C" {
        #include "my_c_lib.h"
    }
    void foo() {
        MyCStruct* obj = create_struct(); // Вызов C-функции
        // ... работа с obj ...
        destroy_struct(obj); // Освобождение через C-функцию
    }
  5. Инициализация и завершение: Если C-библиотека требует вызова функций инициализации (init_lib()) и деинициализации (shutdown_lib()), C++ код должен явно их вызывать, обычно в конструкторе/деструкторе класса-обёртки (RAII).

Ответ 18+ 🔞

Да ты посмотри, какая тема интересная подъехала! Смешивать C и C++ — это как пытаться скрестить ужа с ежом, результат предсказуемо будет пиздопроебибна. Но если очень надо, то можно, главное — знать, где подстелить соломки, а то доверия ебать ноль к этому процессу.

Ну, первое и самое главное — это соглашение о композитных именах, или как это там умно называется. Короче, C++ компилятор — хитрая жопа, он имена функций так исковеркает, что родная мать не узнает. Всё ради своей перегрузки. А C-компилятор тупо оставляет как есть. Чтобы они друг друга поняли, C-шные объявления надо в extern "C" завернуть. Делается это обычно прямо в хидере, чтобы и тем, и другим было удобно.

#ifdef __cplusplus
extern "C" {
#endif

void c_library_function(int param); // Объявление C-функции

#ifdef __cplusplus
}
#endif

Второй момент — исключения. Вот это, блядь, ядрёна вошь. Представь: летит твоё красивое C++ исключение через границу в чистый C код. А там — пустота, вакуум, им про раскрутку стека ничего не известно. Всё, накрылось медным тазом, креш. Поэтому ловить и давить все исключения надо ещё на нашей, C++ стороне. Не выпускай этого джинна из бутылки.

Дальше — типы данных. Тут надо быть проще, как хуй с горы. Кидай через границу только POD (Plain Old Data): обычные int, double, структуры без всяких наворотов — только данные. Никаких std::string, std::vector или классов с виртуальными функциями туда-сюда передавать нельзя. C код посмотрит на это и сам от себя охуеет. ABI — штука тонкая, вы ходите по охуенно тонкому льду.

Следующее — память. А вот это, ёпта, вообще отдельная песня. Если память выделила C-библиотека через malloc(), то и освобождать её надо через free(). Если C++ код наваял через new — пусть delete вызывает. Мешать это — верный путь к тому, чтобы всё бздело и сыпалось. Лучшая практика — пусть C-библиотека сама даёт функции для создания и уничтожения своих объектов.

// C-библиотека
struct MyCStruct* create_struct() { return (struct MyCStruct*)malloc(sizeof(struct MyCStruct)); }
void destroy_struct(struct MyCStruct* p) { free(p); }

// C++ код
extern "C" {
    #include "my_c_lib.h"
}
void foo() {
    MyCStruct* obj = create_struct(); // Вызов C-функции
    // ... работа с obj ...
    destroy_struct(obj); // Освобождение через C-функцию
}

Ну и последнее — инициализация. Бывает, что старая C-библиотека требует, чтобы её торжественно запустили вызовом init_lib() и так же торжественно закрыли shutdown_lib(). Не игнорируй это, распиздяй! В C++ мире для этого идеально подходит паттерн RAII: заверни вызов инита в конструктор класса-обёртки, а вызов деинита — в деструктор. Тогда даже если где-то посередине вылетит хиросима с исключением, деструктор вызовется и библиотека корректно приберётся. Красота!

В общем, связывать можно, но осторожно. Главное — чётко понимать, где чья ответственность, и не надеяться на авось. И тогда, может, и получится хуй в пальто, а не полная катастрофа.