realloc () висячие указатели и неопределенное поведение

Когда вы освобождаете память, что происходит с указателями, указывающими на эту память? Они сразу становятся недействительными? Что произойдет, если они снова станут действительными снова?

Конечно, обычный случай, когда указатель становится недействительным, а затем снова становится «действительным», будет каким-то другим объектом, получающим выделение в том, что происходит с памятью, которая использовалась ранее, и если вы используете указатель для доступа к памяти, это, очевидно, неопределенное поведение. Перетаскивание памяти указателя переписывает урок 1, в значительной степени.

Но что, если память снова станет действительной для того же распределения? Для этого существует только один стандартный способ: realloc() . Если у вас есть указатель на место в блоке памяти malloc() ‘d в смещении > 1 , используйте realloc() чтобы уменьшить размер блока до меньшего, чем ваш смещение, очевидно, что ваш указатель становится недействительным. Если вы затем используете realloc() снова увеличьте блок, чтобы по крайней мере охватить тип объекта, на который указывает оборванный указатель, и ни в одном случае realloc() переместил блок памяти, снова ли висит висячий указатель?

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

 #include  #include  #include  int main(void) { static const char s_message[] = "hello there"; static const char s_kitty[] = "kitty"; char *string = malloc(sizeof(s_message)); if (!string) { fprintf(stderr, "malloc failed\n"); return 1; } memcpy(string, s_message, sizeof(s_message)); printf("%p %s\n", string, string); char *overwrite = string + 6; *overwrite = '\0'; printf("%p %s\n", string, string); string[4] = '\0'; char *new_string = realloc(string, 5); if (new_string != string) { fprintf(stderr, "realloc #1 failed or moved the string\n"); free(new_string ? new_string : string); return 1; } string = new_string; printf("%p %s\n", string, string); new_string = realloc(string, 6 + sizeof(s_kitty)); if (new_string != string) { fprintf(stderr, "realloc #2 failed or moved the string\n"); free(new_string ? new_string : string); return 1; } // Is this defined behavior, even though at one point, // "overwrite" was a dangling pointer? memcpy(overwrite, s_kitty, sizeof(s_kitty)); string[4] = s_message[4]; printf("%p %s\n", string, string); free(string); return 0; } 

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

Определенно да. Из раздела 6.2.4 стандарта C:

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

А из раздела 7.22.3.5:

Функция realloc освобождает старый объект, на который указывает ptr, и возвращает указатель на новый объект, размер которого задан по размеру. Содержимое нового объекта должно быть таким же, как и у старого объекта до освобождения, вплоть до меньшего размера нового и старого. Любые байты нового объекта за пределами размера старого объекта имеют неопределенные значения.

Обратите внимание на ссылку на старый объект и новый объект … по стандарту, то, что вы возвращаете из realloc, является другим объектом, чем у вас раньше; это ничем не отличается от free а затем и malloc , и нет гарантии, что два объекта имеют один и тот же адрес, даже если новый размер <= старый размер ... и в реальных реализациях они часто не будут, потому что объекты разных размеров извлекаются из разных бесплатных списков.

Что произойдет, если они снова станут действительными снова?

Нет такого животного. Валидность – это не какое-то событие, которое имеет место, это абстрактное условие, установленное стандартом C. Ваши указатели могут работать в некоторой реализации, но все ставки отключены, как только вы освободите память, на которую они указывают.

Но что, если память снова станет действительной для того же распределения? Для этого существует только один стандартный способ: realloc ()

Извините, нет, стандарт C не содержит каких-либо языков для этого.

Если вы затем используете realloc (), снова увеличьте блок, чтобы хотя бы покрыть тип объекта, на который указывает оборванный указатель, и ни в одном случае realloc () не переместил блок памяти

Вы не можете знать, будет ли это … стандарт не гарантирует такую ​​вещь. И особенно, когда вы перераспределяете меньший размер, большинство реализаций изменяют память сразу после укороченного блока; перераспределение обратно к исходному размеру будет содержать мусор в добавленной части, это будет не то, что было до того, как оно было сжато. В некоторых реализациях некоторые размеры блоков хранятся в списках для этого размера блока; перераспределение до другого размера даст вам совершенно другую память. И в программе с несколькими streamами любая освобожденная память может быть выделена в другом streamе между двумя reallocs, и в этом случае realloc для большего размера будет вынужден переместить объект в другое место.

снова висит висячий указатель?

См. Выше; Недопустимый недопустимый; нет обратного пути.

Это такой угловой случай, что я действительно не знаю, как интерпретировать стандарты C или C ++, чтобы понять это.

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

Обратите внимание, что современные оптимизирующие компиляторы записываются, чтобы знать о неопределенном поведении и использовать его. Как только вы перезагрузите строку, overwrite будет недопустимой, и компилятор будет свободен для ее удаления … например, может быть в регистре, что компилятор перераспределяет для временного доступа или передачи параметров. Независимо от того, делает ли это какой-либо компилятор, он может , именно потому, что стандарт совершенно ясен в отношении указателей на объекты, которые становятся недействительными, когда срок жизни объекта заканчивается.

Если вы затем используете realloc (), снова увеличьте блок, чтобы по крайней мере охватить тип объекта, на который указывает оборванный указатель, и ни в одном случае realloc () не переместил блок памяти, снова ли висит висячий указатель?

Нет. Если realloc() возвращает нулевой указатель, вызов завершает время жизни выделенного объекта, подразумевая, что все указатели, указывающие на него, становятся недействительными. Если realloc() успешно завершен, он возвращает адрес нового объекта.

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

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

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

Это зависит от вашего определения «действительный». Вы прекрасно описали ситуацию. Если вы хотите считать, что «действительный», то он действителен. Если вы не хотите считать, что это «действительный», значит это неверно.