Библиотека, использующая Tcl C API, выходит из строя, возможно, из-за плохого использования рефкаунта

#c #tcl #c-api

Вопрос:

Я новичок в Tcl C API и пытаюсь понять, как его использовать. У меня есть этот код, который получает список tcl от get_my_list proc, а затем я повторяю его , чтобы отправить некоторую информацию, в данном случае информацию, связанную с атрибутами A , B и C которую я получаю с get_attr_info помощью proc. Когда я запускаю его в очень простом примере, в котором в основном есть только этот код, все работает идеально, но когда я добавляю эту библиотеку в большой проект tcl, в конечном итоге он выходит из строя. Я подозреваю, что это связано с плохим использованием подсчета ссылок на объекты tcl, я имею в виду их время жизни. Что я могу сделать не так в приведенном ниже примере? Я использую Tcl 8.6.

 Tcl_Obj* Get_Info(Tcl_Interp *interp, const char* info, const char* attr) {
    char cmd[256];
    sprintf(cmd, "get_attr_info %s %s", info, attr);
    Tcl_Eval(interp, cmd);
    return Tcl_GetObjResult(interp);
}

static int Copy_Info(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
    Tcl_Eval(interp, "get_my_list");
    Tcl_Obj *const my_list = Tcl_GetObjResult(interp);
    int my_list_size;
    Tcl_ListObjLength(interp, my_list, amp;my_list_size);
    Tcl_IncrRefCount(my_list);

    for (int i = 0; i < my_list_size;   i) {
        Tcl_Obj* current_info_obj;
        Tcl_ListObjIndex(interp, my_list, i, amp;current_info_obj);
        const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);

        /* getting info A */
        Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
        const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
        Copy_Info_A(info_a);

        /* getting info B */
        Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
        const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
        Copy_Info_B(info_b);

        /* getting info C */
        Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
        const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
        Copy_Info_C(info_c);
    }

    Tcl_DecrRefCount(my_list);
    Tcl_FreeResult(interp);

    return TCL_OK;
}
 

Ответ №1:

Хитрость заключается в том , что вы вызываете Tcl_Eval() inside Get_Info() , который может выполнять всевозможные действия с управлением количеством ссылок на значения , на которые вы не ссылаетесь, включая удаление представления списка значения, на которое вы ссылаетесь my_list , что может вывести ковер из-под его элементов. В частности, количество ссылок на объект, на который ссылается in, current_info_obj должно увеличиваться с момента после Tcl_ListObjIndex() до конца тела цикла.

 // ...
for (int i = 0; i < my_list_size;   i) {
    Tcl_Obj* current_info_obj;
    Tcl_ListObjIndex(interp, my_list, i, amp;current_info_obj);
    // HOLD THE REFERENCE; IT OWNS THE current_info_ctr STRING FOR US
    Tcl_IncrRefCount(current_info_obj);
    const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);

    /* getting info A */
    Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
    const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
    Copy_Info_A(info_a);

    /* getting info B */
    Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
    const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
    Copy_Info_B(info_b);

    /* getting info C */
    Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
    const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
    Copy_Info_C(info_c);

    // RELEASE THE REFERENCE
    Tcl_DecrRefCount(current_info_obj);
}
// ...
 

Улучшение вашего кода

Вы также можете сделать короткую стрижку и использовать Tcl_GetString(objPtr) вместо Tcl_GetStringFromObj(objPtr, NULL) . Вы могли бы положить это внутрь Get_Info . Вероятно , вам также следует подумать о переходе на использование Tcl_EvalObjv() , так как это более быстрый API (он позволяет избежать уровня синтаксического анализа), хотя и более сложный в использовании.

 const char *Get_Info(Tcl_Interp *interp, Tcl_Obj* info, const char* attr) {
    Tcl_Obj *argv[3], *resu<

    argv[0] = Tcl_NewStringObj("get_attr_info", -1); // cacheable
    argv[1] = info;
    argv[2] = Tcl_NewStringObj(attr, -1);
    Tcl_IncrRefCount(argv[0]);
    Tcl_IncrRefCount(argv[1]);
    Tcl_IncrRefCount(argv[2]);

    // It's very bad form to omit error handling
    if (Tcl_EvalObjv(interp, 3, argv, 0) != TCL_OK) {
        Tcl_Panic("problem: %s", Tcl_GetString(Tcl_GetObjResult(interp)));
    }
    result = Tcl_GetObjResult(interp);
    Tcl_DecrRefCount(argv[0]);
    Tcl_DecrRefCount(argv[1]);
    Tcl_DecrRefCount(argv[2]);
    return Tcl_GetString(result);
}
 

Бывают случаи, когда можно уменьшить объем обработки количества ссылок. Не беспокойтесь об этом с самого начала! Лучше определенно избегать аварий!

Tcl_EvalObjv это (фактически) то, что Tcl_Eval вызывает фактическую отправку команды после синтаксического анализа в интерактивном режиме, и это то, что механизм байт-кода вызывает для оценки любой не встроенной команды. Это гораздо эффективнее, чем разбор строки на слова.

Комментарии:

1. Отладка управления количеством ссылок довольно сложна. Может помочь использование сборки Tcl с включенной отладкой памяти (опция времени настройки).

2. Кроме того, я бы, вероятно, поместил такой цикл в код Tcl в первую очередь и просто нашел способ для кода C сделать Copy_Info_A(info_a) это без кода обнаружения.

3. Закончил тесты и внес предложенные вами улучшения. Все работало идеально. Большое спасибо!