Каковы эти так называемые "бедствия", которые могут быть вызваны неправильным использованием указателей?
Я все больше и больше использую указатели в своих программах, и, читая об указателях, каждое найденное мной руководство или учебник говорили, что неправильное использование указателей может привести к "катастрофическим" результатам.
Теперь у меня было несколько случаев больших утечек памяти, и указатели разыменовывали неверную переменную-указатель, возвращая неверное значение, но кроме этого ничего "катастрофического" никогда не происходило; как мой компьютер и / или другие программы сбой.
Может ли кто-нибудь дать мне простой пример кода, который определенно даст "катастрофические" результаты, возможно, с некоторой предысторией того, что произошло, если вы когда-нибудь случайно использовали этот кусок кода? Под "катастрофическими" результатами я подразумеваю код, который может мешать другим программам или ОС и, возможно, приводить к их аварийному завершению.
4 ответа
Неправильная арифметика указателей также может привести к бедствиям, потому что неправильное определение границ приводит к переполнению буфера, а переполнение буфера приводит к повреждению данных, например к разрушению стека:
void test_fun(int i)
int x[5];
for (int *p = x; p < x+10; ++p) { // obvious error, some are more subtle
*p = i;
}
return; // execution may resume at address `i`, with entertaining results
}
Конечно, вы можете сделать ту же ошибку, просто позвонив strcpy
или же memcpy
[*], вам не нужно делать арифметику указателя самостоятельно. Если злоумышленник контролирует значение i
(возможно, из-за того, что он читается из входного файла, а злоумышленник создает вредоносный файл), у вас могут быть проблемы с аварией. В сочетании с более специфичными для платформы трюками злоумышленник может организовать возвращение к i
в конце концов выполняется код, предоставленный злоумышленником.
[*] или же strncpy
, или же strlcpy
, или же strcpy_s
, или же std::copy
прежде, чем кто-либо начнет. Если у вас есть неправильная привязка, то указание этой неправильной привязки в функцию проверки границ все еще неверно...
Существует два основных вида бедствий - висячие указатели и утечки памяти.
Повисший указатель - это когда указатель хранит адрес, который не является адресом объекта:
T* first;
T* second; //somewhere in another piece of code
first = new T();
second = first;
delete first;
first = 0; //second still stores the address of an already deleted object
Утечка памяти происходит, когда нет указателей, хранящих адрес выделенного в куче объекта:
T* object;
for( int i = 0; i < 10; i++ ) {
object = new T();
}
delete object; // now the first nine objects are unreacheable
Висячие указатели плохи, потому что их использование приводит к неопределенному поведению - программа может аварийно завершить работу или изменить некоторые несвязанные данные, что впоследствии вызовет проблемы. Утечки памяти очень плохи, потому что выделенная память не может быть повторно использована, и поэтому программа может стать нехваткой памяти через некоторое время.
Самым неприятным, что я видел, являются "отложенные сбои", когда неправильно выполненный доступ для записи повреждает структуру данных, которая используется только позже, производя неправильный вывод совершенно неактуального кода. Во время отладки вы наблюдаете "подъем машин" - структура данных таинственным образом получает неправильные значения, которые никогда не назначались, против программистов. Вы можете искать ошибку в тысячах LOC от того места, где она есть на самом деле.
Несколько случаев приходят на ум:
- продолжение доступа к памяти free()/delete()d или локальным переменным после того, как они покинули область видимости
- утечка памяти кучи из-за неясного владения
- непреднамеренный общий доступ к данным, когда изменения в указанных значениях могут запутать некоторые алгоритмы, работающие с ними
- случайные мелкие копии
- неполная / некорректная сериализация из-за наивной двоичной записи предполагаемых данных POD, которые фактически содержат указатели на другие данные
- многопроцессорные небезопасные данные в разделяемой памяти, где указатели (особенно указатели виртуальной диспетчеризации) должны различаться в разных процессах, обращающихся к ним
- циклическое связывание данных, которое заставляет код зацикливаться на неопределенное время
- перемещение указателей по данным таким образом, что они случайно перемещаются за пределы данных (проблема аналогична индексам массивов, но более сложна, поскольку необязательно какая-либо постоянная ссылка и неизменно безопасный относительный диапазон индексации)
- не удается правильно проверить / обработать значения часового
- проблемы с типом данных, на которые указывает указатель
- использование небезопасных / ошибочных приведений (например, переинтерпретация приведения, где требуется динамическое приведение)
- неспособность понять, что индексирование из указателей выполняется в единицах размера указателя на тип
- недопустимые преобразования из указателей в / из более коротких целочисленных типов