Увеличивает ли PyDict_SetItem счетчик ссылок ключа, и если да, то где в коде это происходит? - PullRequest
1 голос
/ 22 февраля 2020

TLDR: PyDict_SetItem увеличивает ключ и значение, но где в коде это происходит?

PyDict_SetItem делает вызов insertdict.

insertdict немедленно выполняет Py_INCREF как для ключа, так и для значения. Однако в конце пути успеха он делает Py_DECREF для ключа (но не для значения). Должна быть некоторая часть этого кода, которую я пропускаю, когда он делает дополнительный PY_INCREF для ключа, прежде чем он сделает это PY_DECREF. У меня вопрос, где и почему происходит этот дополнительный PY_INCREF? Почему начальный Py_INCREF в начале insertdict недостаточен?

Из этого на первый взгляд кажется, что PyDict_SetItem только увеличивает счетчик ссылок значения, но не ключа. Это не правда, конечно. Например, в PyDict_SetItemString , который принимает char *, преобразует его в PyObject через PyUnicode_FromString (который возвращает новое значение), выполняет Py_DECREF для этого нового значения после вызова PyDict_SetItem , Если PyDict_SetItem не увеличивает ключ, а PyDict_SetItemString уменьшает ключ, который он только что создал, программа может в конечном итоге выполнить segfault. Учитывая, что этого не происходит, кажется, что я что-то здесь упускаю.


Наконец, этот код должен доказать, что PyDict_SetItem увеличивает и ключ, и значение, и что вызывающая сторона должна преобразовать оба ключ и значение, если они не были заимствованными ссылками / или не передадут ключ и значения кому-либо еще.

#include <Python.h>
#include <stdio.h>

int main(void)
{
    Py_Initialize();
    PyObject *dict = NULL, *key = NULL, *value = NULL;
    int i = 5000;
    char *o = "foo";

    if (!(dict = PyDict_New())) {
        goto error;
    }
    if (!(key = Py_BuildValue("i", i))) {
        goto error;
    }
    if (!(value = Py_BuildValue("s", o))) {
        goto error;
    }
    printf("Before PyDict_SetItem\n");
    printf("key is %i\n", key->ob_refcnt);  /* Prints 1 */
    printf("value is %i\n", value->ob_refcnt);  /* Prints 1 */

    printf("Calling PyDict_SetItem\n");
    if (PyDict_SetItem(dict, key, value) < 0) {
        goto error;
    }
    printf("key is %i\n", key->ob_refcnt);  /* Prints 2 */
    printf("value is %i\n", value->ob_refcnt);  /* Prints 2 */

    printf("Decrefing key and value\n");
    Py_DECREF(key);
    Py_DECREF(value);
    printf("key is %i\n", key->ob_refcnt);   /* Prints 1 */
    printf("value is %i\n", value->ob_refcnt);   /* Prints 1 */

    Py_Finalize();
    return 0; // would return the dict in normal code
error:
    printf("error");
    Py_XDECREF(dict);
    Py_XDECREF(key);
    Py_XDECREF(value);
    Py_Finalize();
    return 1;
}

Вы можете скомпилировать как:

gcc -c -I/path/to/python/include/python3.7m dict.c
gcc dict.o -L/path/to/python/lib/python3.7/config-3.7m-i386-linux-gnu -L/path/to/python/lib -Wl,-rpath=/path/to/python/lib -lpython3.7m -lpthread -lm -ldl -lutil -lrt -Xlinker -export-dynamic -m32

1 Ответ

1 голос
/ 22 февраля 2020

Py_DECREF(key); в insertdict не бывает для всех успехов. Это происходит при успехе , когда равный ключ уже присутствовал , либо потому, что существовала запись, либо потому, что диктат был диктом разделения таблицы, разделяющим ключи с другими диктовками, которые имели этот ключ. По этому пути предоставленный ключ не вставляется, поэтому исходный Py_INCREF(key); необходимо отменить.

По пути без ключа insertdict соответствует другому return оператор и не расшифровывает ключ:

if (ix == DKIX_EMPTY) {
    /* Insert into new slot. */
    assert(old_value == NULL);
    if (mp->ma_keys->dk_usable <= 0) {
        /* Need to resize. */
        if (insertion_resize(mp) < 0)
            goto Fail;
    }
    Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
    ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
    dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
    ep->me_key = key;
    ep->me_hash = hash;
    if (mp->ma_values) {
        assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL);
        mp->ma_values[mp->ma_keys->dk_nentries] = value;
    }
    else {
        ep->me_value = value;
    }
    mp->ma_used++;
    mp->ma_version_tag = DICT_NEXT_VERSION();
    mp->ma_keys->dk_usable--;
    mp->ma_keys->dk_nentries++;
    assert(mp->ma_keys->dk_usable >= 0);
    ASSERT_CONSISTENT(mp);
    return 0;
}
...