Я моделирую доступ к памяти программы с помощью Z3, и у меня есть сомнения относительно производительности, которыми я хотел бы поделиться.
Я хотел компактно смоделировать что-то вроде:
memset(dst, 0, 1000);
Моя первая попытка состояла в том, чтобы использовать теорию массивов, но это означало либо создать тысячу терминов, таких как (assert (and (= (select mem 0) 0) (= (select mem 1) 0) ...
, или тысячу подобных хранилищ, либо количественную формулу:
(forall (x Int) (implies (and (>= x 0) (< x 1000)) (= (select mem x) 0))
Но мне сказали избегать квантификаторов при использовании массивов.
Следующей идеей было определить UF:
(define-fun newmemfun ((idx Int)) Int (
ite (and (>= idx 0) (< idx 1000)) 0 (prevmemfun idx)
))
Но это означает, что мне нужно определить новую функцию для каждой операции записи в память (даже для отдельных операций сохранения, а не только для нескольких хранилищ, таких как memset или memcpy). Что в конечном итоге привело бы к созданию очень вложенной структуры ITE, которая даже сохраняла бы «старые» значения для одного и того же индекса. то есть:
mem[0] = 1;
mem[0] = 2;
would be:
(ite (= idx 0) 2 (ite (= idx 0) 1 ...
Что функционально правильно, но размер выражения (и, я думаю, сгенерированный для него AST) имеет тенденцию накапливаться очень быстро, и я не уверен, оптимизирован ли Z3 для обнаружения и обработки этого случая.
Итак, вопрос в том, какой будет наиболее эффективный способ кодирования операций с памятью, который может работать с большим количеством хранилищ, как в приведенном выше примере, и с отдельными хранилищами одновременно.
Спасибо, пабло.
PS: предполагается незакрытая и несоответствующая скобка: P.