После просмотра CppCons Выдержит ли ваш код атаку зомби-указателей? Я немного озадачен временем жизни указателя и нуждаюсь в некотором уточнении.
Сначала немного базового понимания. Пожалуйста, исправьте меня, если какие-либо комментарии неправильны:
int* p = new int(1);
int* q = p;
// 1) p and q are valid and one can do *p, *q
delete q;
// 2) both are invalid and cannot be dereferenced. Also the value of both is unspecified
q = new int(42); // assume it returns the same memory
// 3) Both pointers are valid again and can be dereferenced
Я озадачен 2). Очевидно, что они не могут быть разыменованы, но почему нельзя использовать их значения (например, сравнивать одно с другим, даже несвязанный и действительный указатель?). Это указано примерно в 25: 38 . Я ничего не могу найти по этому поводу в cppreference , откуда я и получил 3).
Примечание. Предположение о том, что возвращается та же память, не может быть обобщено, как можетили не может случиться. Для этого примера следует принять как должное то, что он «случайно» возвратил ту же память, что и в примере с видео, и (возможно?), Необходимый для разрыва приведенного ниже кода.
Многопоточный примеркод из списка LIFO может быть смоделирован в одном потоке как:
Node* top = ... //NodeA allocated before;
Node* newnodeC = new Node(v);
newnodeC->next = top;
delete top; top = nullptr;
// newnodeC->next is a zombie pointer
Node* newnodeD = new Node(u); // assume same memory as NodeA is returned
top = newnodeD;
if(top == newnodeC->next) // true
top = newnodeC;
// Now top->next is (still) a zombie pointer
Это должно быть допустимо, если только Node
не содержит нестатических константных членов или ссылок в соответствии с правилами в
Если новый объект создается по адресу, который был занят другим объектом, то все указатели, ссылки и имя исходного объекта будут автоматически ссылаться на новый объект и, как только начнется время жизни нового объекта,может использоваться для манипулирования новым объектом, но только если выполняются следующие условия [которыми они являются]
Так почему же это указатель зомби и предположительно UB?
Может ли(однопоточный) сжатый код, указанный выше, должен быть исправлен (при наличии констант): newnodeC->next = std::launder(newnodeC->next)
с
Если tЕсли вышеперечисленные условия не выполнены, действительный указатель на новый объект все еще может быть получен путем применения барьера оптимизации указателя std :: launder
Я ожидаю, что это исправит «указатель зомби»и компиляторы, чтобы они не выдавали инструкции для назначения, а просто воспринимали их как барьер оптимизации (например, когда функция встроена и снова вызывается константный член)
Итак, в общем: я не слышал о "зомби указатели "раньше. Я прав, что любой указатель на уничтоженный / удаленный объект не может использоваться (для чтения [значения указателей] и разыменования [чтения указателя]), если указатель не переназначен или память не перераспределена с тем же типом объекта, воссозданным там (ибез постоянных / референтных членов)? Разве это не может быть исправлено C ++ 17 std::launder
уже? (исключая многопоточные проблемы)
Также: при 3)
в первом коде if(p==q)
будет вообще вообще допустимым? Поскольку из моего понимания (второй части) видео недопустимо читать p
.
Редактировать: В качестве объяснения, где я почти уверен, что происходит UB: Опять предположим, что по чистой случайностита же память возвращается с new
:
// Global
struct Node{
const int value;
};
Node* globalPtr = nullptr;
// In some func
Node* ptr = new Node{42};
globalPtr = ptr;
const int value = ptr->value;
foo(value);
// Possibly on another thread (if multi-threaded assume proper synchronisation so that this scenario happens)
delete globalPtr;
globalPtr = new Node{1337}; // Assume same memory returned
// First thread again (and maybe on serial code too)
if(ptr == globalPtr)
foo(ptr->value);
else
foo(globalPtr->value);
Согласно видео, после delete globalPtr
также ptr
является «указателем зомби» и не может быть использован (иначе «будет UB»)). Достаточно оптимизирующий компилятор может использовать это и предполагать, что pointee никогда не освобождается (особенно, когда удаление / новое происходит в другой функции / потоке / ...), и оптимизировать от foo(ptr->value)
до foo(42)
Примечание. также упомянутый отчет о дефектах 260 :
Если значение указателя становится неопределенным, поскольку объект, на который указывает указатель, достиг конца своего времени жизни, все объекты, чей эффективный тип является указателем, иэта точка на один и тот же объект приобретает неопределенное значение. Таким образом, p в точке X и p, q и r в точке Z могут изменить свое значение.
Я думаю, что это является окончательным объяснением: после delete globalPtr
значение ptr
также является неопределенным. Но как это можно совместить с
Если новый объект создается по адресу, который был занят другим объектом, то все указатели [...] исходного объекта будут автоматически ссылаться на новый объект и [...] могут использоваться для манипулированияновый объект