Подтвердить что ты не робот

Почему MISRA C заявляет, что копия указателей может вызвать исключение памяти?

Директива MISRA C 2012 4.12 "Динамическое распределение памяти не должно использоваться".

В качестве примера документ содержит этот образец кода:

char *p = (char *) malloc(10);
char *q;

free(p);
q = p; /* Undefined behaviour - value of p is indeterminate */

И документ гласит, что:

Хотя значение, сохраненное в указателе, не изменяется после звоните бесплатно, на некоторых объектах возможно, что память который он указывает больше не существует и акт копирования этого указателя может вызвать исключение памяти.

Я в порядке с почти всем предложением, но в конце. Как p и q оба выделены в стеке, как может копия указателей вызвать исключение памяти?

4b9b3361

Ответ 1

В соответствии со стандартом копирование указателя q = p; выполняется undefined.

Чтение состояний состояния J.2 undefined:

Используется значение указателя на объект, срок жизни которого закончился (6.2.4).

В этой главе мы видим, что:

6.2.4 Длительность хранения объектов

Время жизни объекта - это часть выполнения программы, во время которой гарантированно будет зарезервирован для него. Объект существует, имеет постоянный адрес, 33) и сохраняет его последнего сохраненного значения на протяжении всего его жизненного цикла. 34) Если объект ссылается вне его время жизни, поведение undefined. Значение указателя становится неопределенным, если объект, на который он указывает (или только что прошлый), достигает конца своего срока службы.

Что неопределенно:

3.19.2 неопределенное значение: либо неопределенное значение, либо представление ловушки

Ответ 2

Как только вы освободите объект через указатель, все указатели на эту память становятся неопределенными. (Четный) чтение неопределенная память - это поведение undefined (UB). Ниже приведен UB:

char *p = malloc(5);
free(p);
if(p == NULL) // UB: even just reading value of p as here, is UB
{

}

Ответ 3

Во-первых, некоторая история...

Когда ISO/IEC JTC1/SC22/WG14 впервые начали формализовать язык C (для получения того, что сейчас является ISO/IEC 9899: 2011), у них возникла проблема.

Многие производители компиляторов интерпретировали вещи по-разному.

В начале они приняли решение не нарушать существующие функции... поэтому, когда реализации компилятора были расходящимися, Стандарт предлагает поведение unspecified и undefined.

MISRA C пытается уловить пит-палки, которые могут вызвать эти поведения. Так много для теории...

-

Теперь к конкретному вопросу:

Учитывая, что точка free() - освободить динамическую память обратно в кучу, было три возможных реализации, все из которых были "в дикой природе":

  • reset указатель на NULL
  • оставить указатель как
  • уничтожить указатель

Стандарт не может требовать ни одного из них, поэтому формально оставляет поведение undefined - ваша реализация может следовать по одному пути, но другой компилятор может сделать что-то еще... вы не можете предположить, и это опасно для полагайтесь на метод.

Лично я предпочел бы, чтобы стандарт был специфическим, и потребовал, чтобы free() установил указатель на NULL, но это только мое мнение.

-

Итак, TL; DR; ответ, к сожалению: потому что это!

Ответ 4

Хотя оба p и q являются обе переменными указателя в стеке, адрес памяти, возвращаемый malloc(), не находится в стеке.

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

Итак, раз free() используется для освобождения области памяти, полученной ранее с помощью malloc(), попытка использования области памяти является типом действия undefined. Вам может повезти, и это сработает. Возможно, вам повезет, и это не произойдет. Когда вы free() область памяти, вы больше не владеете ею, что-то еще делает.

Проблема здесь будет заключаться в том, какой машинный код задействован при копировании значения из одного места памяти в другое. Помните, что MISRA нацелена на разработку встроенного программного обеспечения, поэтому всегда задан вопрос о том, какие фэнк-процессоры существуют там, которые делают что-то особенное с копией.

Стандарты MISRA касаются надежности, надежности и устранения риска отказа программного обеспечения. Они довольно придирчивы.

Ответ 5

Плохая формулировка в примере кода отбрасывает вас.

В нем говорится: "значение p неопределенно", но это не значение p, которое является неопределенным, поскольку p все еще имеет одно и то же значение (адрес освобожденного блока памяти).

Вызов free (p) не изменяется. p - p изменяется только после того, как вы покинете область, в которой определяется p.

Вместо этого это значение того, что p указывает на неопределенное значение, поскольку блок памяти был освобожден, и он также может быть отключен операционной системой. Доступ к ней либо через p, либо через указатель с псевдонимом (q) может привести к нарушению доступа.

Ответ 6

Существует две причины, по которым код, который проверяет указатель после его освобождения, является проблематичным, даже если указатель никогда не разыменован:

  • Авторы стандарта C не хотели вмешиваться в реализацию языка на платформах, где указатели содержат информацию о окружающих блоках памяти и которые могут проверять такие указатели всякий раз, когда что-либо делается с ними, независимо от того, являются ли они разыменован или нет. Если такие платформы существуют, код, который использует указатели в нарушение Стандарта, может не работать с ними.

  • Некоторые компиляторы работают над тем, что программа никогда не получит комбинацию входов, которые будут вызывать UB, и, следовательно, любая комбинация входов, которые создавали бы UB, должна считаться невозможной. В результате этого даже формы UB, которые не окажут отрицательного воздействия на целевую платформу, если компилятор просто проигнорирует их, могут в конечном итоге иметь произвольные и неограниченные побочные эффекты.

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

Ответ 7

Значение p невозможно использовать как таковое после того, как память, на которую указывает она, была освобождена. В общем случае значение неинициализированного указателя имеет тот же статус: даже просто чтение его с целью копирования для вызова поведения undefined.

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

Я помню одну такую ​​цель, еще в начале 1990-х годов, которая вела себя так. Вместо этого встроенная цель и, скорее, широко используется: Windows 2.x. Он использовал архитектуру Intel в 16-битном защищенном режиме, где указатели были 32-битными в ширину, с 16-разрядным селектором и 16-битным смещением. Чтобы получить доступ к памяти, указатели были загружены в пару регистров (регистр сегмента и адресный регистр) с определенной инструкцией:

    LES  BX,[BP+4]   ; load pointer into ES:BX

Загрузка селекторной части значения указателя в регистр сегмента имела побочный эффект проверки значения селектора: если селектор не указывал на допустимый сегмент памяти, исключение было бы запущено.

Компиляция невинно выглядящего оператора q = p; может быть скомпилирована различными способами:

    MOV  AX,[BP+4]    ; loading via DX:AX registers: no side effects
    MOV  DX,[BP+6]
    MOV  [BP-6],AX
    MOV  [BP-4],DX

или

    LES  BX,[BP+4]    ; loading via ES:BX registers: side effects
    MOV  [BP-6],BX
    MOV  [BP-4],ES

Второй вариант имеет 2 преимущества:

  • Код более компактный, 1 инструкция меньше

  • Значение указателя загружается в регистры, которые могут использоваться непосредственно для разыменования памяти, что может привести к меньшему количеству команд, сгенерированных для последующих операторов.

Освобождение памяти может отменить выбор сегмента и сделать селектор недействительным. Значение становится значением ловушки и загружает его в ES:BX вызывает исключение, также называемое ловушкой на некоторых архитектурах.

Не все компиляторы будут использовать инструкцию LES для простое копирование значений указателя, потому что это было медленнее, но некоторые делали, когда им было поручено генерировать компактный код, что является общим выбором, поскольку память была довольно дорогой и скудной.

Стандарт C допускает это и описывает форму поведения undefined код, где:

Используется значение указателя на объект, срок жизни которого закончился (6.2.4).

поскольку это значение стало неопределенным, как определено таким образом:

3.19.2 неопределенное значение: либо неопределенное значение, либо представление ловушки

Обратите внимание, что вы все равно можете манипулировать значением с помощью псевдонимов с помощью символьного типа:

/* dumping the value of the free'd pointer */
unsigned char *pc = (unsigned char*)&p;
size_t i;
for (i = 0; i < sizeof(p); i++)
    printf("%02X", pc[i]);   /* no problem here */

/* copying the value of the free'd pointer */
memcpy(&q, &p, sizeof(p));   /* no problem either */

Ответ 8

Важным понятием интернализации является значение "неопределенного" или "undefined" поведения. Это точно: неизвестно и непознаваемо. Мы часто говорили студентам: "Совершенно законно, чтобы ваш компьютер таял в бесформенном блобе или диск, чтобы улететь на Марс". Когда я прочитал исходную документацию, я не видел ни одного места, где он сказал, чтобы не использовать malloc. Он просто указывает, что ошибочная программа потерпит неудачу. Фактически, если программа принимает исключение памяти, это Good Thing, потому что она сразу сообщает вам, что ваша программа повреждена. Почему документ предполагает, что это может быть Bad Thing, убегает от меня. Что такое Bad Thing, так это то, что на большинстве архитектур он не займет памяти. Продолжая использовать этот указатель, вы получите ошибочные значения, потенциально сделайте кучу непригодной для использования, и если тот же самый блок хранения выделяется для другого использования, развращая действительные данные этого использования или интерпретируя его значения как ваши собственные. Итог: не используйте "устаревшие" указатели! Или, говоря иначе, писать дефектный код означает, что он не будет работать.

Кроме того, акт назначения p на q наиболее определенно НЕ "undefined" . Биты, хранящиеся в переменной p, которые представляют собой бессмысленную бессмыслицу, довольно легко и корректно копируются в q. Все это означает, что теперь любое значение, к которому обращается p, может также получить доступ через q, а так как p является undefined глупостью, q теперь undefined бессмыслен. Поэтому использование одного из них для чтения или записи приведет к результатам "undefined" . Если вам посчастливилось работать на архитектуре, которая может привести к ошибке памяти, вы легко обнаружите неправильное использование. В противном случае использование указателя означает, что ваша программа повреждена. Планируйте потратить много часов на то, чтобы найти его.