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

«Что нужно учитывать при взаимодействии кода на C и C++?» — вопрос из категории C++ Core, который задают на 25% собеседований 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).