Каковы эти так называемые "бедствия", которые могут быть вызваны неправильным использованием указателей?

Я все больше и больше использую указатели в своих программах, и, читая об указателях, каждое найденное мной руководство или учебник говорили, что неправильное использование указателей может привести к "катастрофическим" результатам.

Теперь у меня было несколько случаев больших утечек памяти, и указатели разыменовывали неверную переменную-указатель, возвращая неверное значение, но кроме этого ничего "катастрофического" никогда не происходило; как мой компьютер и / или другие программы сбой.

Может ли кто-нибудь дать мне простой пример кода, который определенно даст "катастрофические" результаты, возможно, с некоторой предысторией того, что произошло, если вы когда-нибудь случайно использовали этот кусок кода? Под "катастрофическими" результатами я подразумеваю код, который может мешать другим программам или ОС и, возможно, приводить к их аварийному завершению.

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, которые фактически содержат указатели на другие данные
  • многопроцессорные небезопасные данные в разделяемой памяти, где указатели (особенно указатели виртуальной диспетчеризации) должны различаться в разных процессах, обращающихся к ним
  • циклическое связывание данных, которое заставляет код зацикливаться на неопределенное время
  • перемещение указателей по данным таким образом, что они случайно перемещаются за пределы данных (проблема аналогична индексам массивов, но более сложна, поскольку необязательно какая-либо постоянная ссылка и неизменно безопасный относительный диапазон индексации)
  • не удается правильно проверить / обработать значения часового
  • проблемы с типом данных, на которые указывает указатель
    • использование небезопасных / ошибочных приведений (например, переинтерпретация приведения, где требуется динамическое приведение)
    • неспособность понять, что индексирование из указателей выполняется в единицах размера указателя на тип
    • недопустимые преобразования из указателей в / из более коротких целочисленных типов
Другие вопросы по тегам