Мое золотое правило для подсчета ссылок: всякий раз, когда моя программа получает указатель на объект Z3, я сразу увеличиваю счетчик ссылок и сохраняю объект где-то в безопасности (т. Е. Теперь я владею 1 ссылкой на этот объект). Только когда я абсолютно уверен, что мне больше не понадобится объект, я назову Z3_dec_ref; с этого момента любой доступ к этому объекту вызовет неопределенное поведение (не обязательно segfault), потому что я больше не владею ссылками - Z3 владеет всеми репертуарами, и он может делать все, что захочет сделать с ними.
Объекты Z3 всегда освобождаются, когда счетчик ссылок обращается в нуль; он находится в вызове dec_ref(), который происходит при освобождении. Если Z3_dec_ref() никогда не вызывается (как в приведенном примере), то объект может оставаться в памяти, поэтому доступ к этой части памяти может, возможно, по-прежнему давать результаты «ok look», но эта часть памяти также может быть перезаписана другими процедурами, чтобы они содержали мусор.
В примере программы данного, мы должны были бы добавить вкл/dec_ref звонки следующим образом:
void rctry(context & c)
{
expr x = c.int_const("x");
expr y = c.int_const("y");
Z3_ast res = Z3_mk_eq(c,x,y);
Z3_inc_ref(c, res); // I own 1 ref to res!
#ifdef FAULT_CLAUSE
expr z = c.int_const("z");
expr u = c.int_const("u");
Z3_ast fe = Z3_mk_eq(c,z,u);
Z3_inc_ref(c, fe); I own 1 ref to fe!
#endif
std::cout << Z3_ast_to_string(c, res) << std::endl;
#ifdef FAULT_CLAUSE
Z3_dec_ref(c, fe); // I give up my ref to fe.
#endif
Z3_dec_ref(c, res); // I give up my ref to res.
}
Объяснение для выхода (= z u)
является то, что второй вызов Z3_mk_eq
повторно использует часть памяти что ранее состоял res
, потому что, видимо, только сама библиотека имела ссылку на него, поэтому можно свободно выбирать, что делать с с памятью. Следствием этого является то, что вызов Z3_ast_to_string
читается из правой части памяти (которая содержала res
), но содержимое этой части памяти в то же время изменилось.
Это было длинное объяснение для тех, кто нуждается в управлении отсчеты реф в C. В случай C++ есть и гораздо более удобным способом: аст/выражение/и т.д. объекты содержат конструктор, который принимает объекты C ,Поэтому мы можем построить управляемые объекты , просто обернув их в вызовы конструктора; в этом конкретном примере, что может быть сделано следующим образом:
void rctry(context & c)
{
expr x = c.int_const("x");
expr y = c.int_const("y");
expr res = expr(c, Z3_mk_eq(c, x, y)); // res is now a managed expr
#ifdef FAULT_CLAUSE
expr z = c.int_const("z");
expr u = c.int_const("u");
expr fe = expr(c, Z3_mk_eq(c,z,u)); // fe is now a managed expr
#endif
std::cout << Z3_ast_to_string(c, res) << std::endl;
}
В деструкторе expr
есть призыв к Z3_dec_ref
, так что будет вызываться автоматически в конце функции, когда res
и fe
входить вне возможности.
Благодарим за разъяснения! Действительно полезно! Но как насчет неопределенного поведения при обращении к объектам, которые больше не нужны? Например, в исходном коде я добавляю 'Z3_dec_ref (c, res);' после 'Z3_mk_eq', не определяя' FAULT_CLAUSE', и в результате 'Z3_ast_to_string' все еще работает и возвращает правильный' (= x y) '. Это одно из неопределенных поведений? Спасибо! – cxcfan
Еще один вопрос, влияет ли функция 'Z3_push' и' Z3_pop' на подсчет объектов Z3? Если я создаю 'Z3_ast' между' Z3_push' и 'Z3_pop', этот' Z3_ast' будет освобожден после 'Z3_pop' метода? – cxcfan
Действительно, доступ к недопустимой ячейке памяти имеет неопределенное поведение, это не обязательно означает, что ваша программа выйдет из строя (это действительно вообще, а не только для Z3). В этом примере память не была перезаписана тем временем, поэтому она по-прежнему содержала правильные данные «случайно». –