Как управлять памятью в интеграции Embeddable Common LISP и с++?

Рейтинг: 4Ответов: 0Опубликовано: 27.07.2025

Пробую создать приложение c++, которое умеет "выполнять" lisp код. Для интеграции использую Embeddable Common LISP

Абстрактный пример

код на лисп:

(setq my_class (create-myclass))

будет вызывать код в c++:

cl_object get_my_class()
{
    MyClass*  myClass = new MyClass;
    cl_object cl_myClass = ecl_make_foreign_data(ecl_make_simple_base_string("*", 1), 0, myClass);
    return cl_myClass;
}

Переменная LISP - my_class в c++ будет соответствовать указателю на MyClass*

Но есть проблема.

(setq my_class nil) 
(gc)

Этот lisp код не освобождает память указателю на MyClass* в c++.

Для решения проблемы можно создать лисп функцию

(delete-myclass my_class)

Она будет соответствовать вот такому коду в c++:

cl_object del_my_class(cl_object my_class)
{
    auto myClass_fptr = ecl_foreign_data_pointer_safe(my_class);
    MyClass* myClass = static_cast<MyClass*>(myClass_fptr);  
    delete myClass;
    return Cnil;
}

Но тут возникает проблема. В отладчике cl_object my_class и MyClass* myClass имеют один и тот же адрес.

введите сюда описание изображения

Это значит delete myClass; освобождает и cl_object my_class. То-есть вместе с ними переменная в лисп получает неопределённое значение.

Возникает вопрос: delete myClass в c++ удалит данные на которые указывает my_class в лиспе. Когда в лиспе закончится жизненный цикл переменной my_class, не произойдет ли Undefined Behavior или Access Violation когда с ней будет работать GC лиспа? Потому-что my_class в лиспе указывает на удаленные данные.

Нигде не нашел информации по этой теме.

В Debug режиме c++ ошибки подобного рода могут и не возникать. А вот в релизной версии мне кажется GC лиспа может совершить Access Violation если данные lisp переменной были удалены или совершить double delete.


Вот этот вариант я не хочу использовать:

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

cl_object create_myclass()
{
    MyClass* myClass = new MyClass;

    // add MyClass* in container and return int class_id
    int class_id = Container->add(myClass);  

    return ecl_make_int(class_id);
}

и тогда

(setq my_class (create-myclass))

сохранит в переменной my_class число при помощи которого можно будет обращаться к контейнеру класса:

(myclass-set-value my_class "new value")

В c++ будет работать примерно так:

cl_object myclass_set_value(cl_object my_class, cl_object new_value)
{
    int class_id = ecl_to_int(my_class);

    // get MyClass* from the container by class_id
    MyClass* myClass = Container->get(class_id);

    string str = ecl_to_string(new_value);
    myClass->setValue(str);
    return Cnil;
}

Тут мы полностью избавляемся от проблем управления памятью. Но я бы не хотел использовать этот способ. Работать с указателями на объекты намного проще, чем создавать прокси-контейнеры или фабрики.

Хотелось бы четко понимать как работает управление памятью при интеграции работы с указателями.

Ответы

Ответов пока нет.