Является ли UB возвратом указателя на локальную переменную?

Да, я прекрасно знаю, что тебе не следует этого делать. Если у нас есть этот код:

int *foo() {
    int a = 42;
    return &a;
}

Как известно большинству кодеров C, это поведение undefined: Использование указателя после free()

int *p = foo();
printf("%d\n", *p);

Просто чтобы будущие читатели не приняли это за правду. Все нижеизложенное до вопроса было основано на ложном предположении от меня. Это не УБ. Это просто плохо.

Как некоторые кодеры C знают, но меньше, чем выше, что это также UB, хотя разыменование не выполняется: (пытался найти хороший вопрос об этом, но никого не нашел)

int *p = foo();
printf("%p\n", p); // Should be printf("%p\n", (void*) p);
                   // Eric P clarified this in his answer, but this missing cast
                   // is not a part of the primary question

И по той же причине это тоже UB, потому что происходит то, что указатель становится неопределенным, как неинициализированная переменная. Так называемый висячий указатель.

int *p = foo();
int *q = p;

И несколько удивительно для некоторых, даже это не нормально:

free(ptr);
if(ptr == NULL) // Just as bad as if ptr is not initialized

Вопрос

Что мне интересно, так это то, что эта единственная строка вызывает UB:

int *p = foo();

Или, может быть, даже это?

foo();

Другими словами, p становится висячим указателем или он назначается висячему указателю?

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


person klutt    schedule 12.03.2021    source источник
comment
Мне нужно будет тщательно изучить Стандарт (или, может быть, это сделает кто-то другой), но, насколько я могу судить, int *p = foo(); не является UB, как и последующий printf("%p\n", p);. Этот указатель будет иметь значение (адрес), и этот адрес можно будет распечатать. Только когда вы пытаетесь разыменовать его, срабатывает UB.   -  person Adrian Mole    schedule 12.03.2021
comment
Связано: stackoverflow.com/q/51083356/1606345   -  person David Ranieri    schedule 12.03.2021
comment
Значение указателя становится неопределенным, когда объект, на который он указывает (или только что прошедший), достигает конца своего времени существования. - port70.net/~nsz/c/c11/n1570.html#6.2.4   -  person Eugene Sh.    schedule 12.03.2021
comment
Так что я не думаю, что какое-либо из утверждений в разделе «Вопрос» является НБ, но p не имеет практического применения, которое не вызовет НБ.   -  person Eugene Sh.    schedule 12.03.2021
comment
@ЕвгенийШ. Я тут подумал, указывает ли вообще p на объект? Разве не &a становится неопределенным перед присваиванием?   -  person klutt    schedule 12.03.2021
comment
@AdrianMole: Нет, после окончания срока службы объекта любые указатели на него становятся недействительными, то есть у них нет четко определенного значения. Они не обязательно указывают на адрес. Первоначальным мотивом для этого были реализации C, которые должны были использовать вспомогательные данные для доступа к объектам, так что free не только освобождал память для объекта, но также освобождал или изменял память, используемую для вспомогательных данных. Эти данные могут понадобиться для использования значения указателя, даже для его печати.   -  person Eric Postpischil    schedule 12.03.2021
comment
@klutt Но присвоение неопределенного значения p делает его неопределенным само по себе? Или вы имеете в виду, что это может быть UB?   -  person Eugene Sh.    schedule 12.03.2021
comment
@ЕвгенийШ. Да, потому что p = foo(); q = p; вызовет UB.   -  person klutt    schedule 12.03.2021
comment
@EricPostpischil Хорошо, это хорошее объяснение. Может быть, я имел в виду вас, когда упомянул кого-то другого? :-)   -  person Adrian Mole    schedule 12.03.2021
comment
Я всегда доверяю @EricPostpischil, когда дело доходит до подобных вопросов. У нас очень разные характеры и мнения, но он действительно мастер, когда дело доходит до мелких деталей языка C, и его ответы всегда чрезвычайно информативны. Я их люблю.   -  person klutt    schedule 12.03.2021
comment
@ЕвгенийШ. Вы могли бы спросить это так. p становится висячим указателем или он назначается висячему указателю?   -  person klutt    schedule 12.03.2021
comment
@klutt Да, я понял. Таким образом, вопрос сводится к тому, является ли UB присвоением висячего указателя другой переменной указателя. Я думаю, что у нас были подобные обсуждения несколько раз ..   -  person Eugene Sh.    schedule 12.03.2021
comment
@ЕвгенийШ. Хм, я почти уверен, что присвоение висячего указателя другому указателю - это UB, так что вопрос не сводится к этому.   -  person klutt    schedule 12.03.2021
comment
@klutt Ответ ниже утверждает обратное, если я правильно понимаю ...   -  person Eugene Sh.    schedule 12.03.2021
comment
Что касается того, делает ли AnyType x = y; x неопределенным значением, если y является неопределенным значением, я думаю, что сам стандарт C ничего не говорит об этом в отношении его открытого текста, но есть некоторая история в ответах на отчеты о дефектах комитета C и другие заявления, которые это делает x неопределенным. Это основано на воспоминаниях о предыдущих обсуждениях Stack Overflow; Я не ожидал, что смогу найти его в отчетах о дефектах или в комментариях Stack Overflow.   -  person Eric Postpischil    schedule 12.03.2021
comment
@ЕвгенийШ. Тогда единственное, что я могу сказать наверняка, это то, что по крайней мере один из нас не до конца понимает ответ. :D   -  person klutt    schedule 12.03.2021
comment
Помимо просто UB, чтобы вернуть указатель на локальную переменную? озабоченность, int *p = foo(); printf("%p\n", p); имеет 2) присвоение неопределенного указателя, 2.5) возможное преобразование сомнительного указателя в void * 3) передачу сомнительного указателя в printf() и 4) печать сомнительного указателя. klutt, Вы ищете UB в любом из этих шагов?   -  person chux - Reinstate Monica    schedule 13.03.2021
comment
@chux-ReinstateMonica Ну, в этом случае у меня нет конкретной проблемы, которую нужно решить, поэтому я думаю, что лучше всего просто позволить богам этого сайта разбить все на мелкие детали. Но я пропустил добавление пустоты. Я знаю, что он должен быть там, но забыл его. Причина, по которой я не отредактировал его, заключается в том, что это может частично аннулировать ответ Эрика. Как вы думаете, это было бы?   -  person klutt    schedule 13.03.2021
comment
Когда уточнение может сделать хороший ответ недействительным, добавьте его или пропустите.   -  person chux - Reinstate Monica    schedule 13.03.2021
comment
@chux-ReinstateMonica Я сделал правку   -  person klutt    schedule 13.03.2021


Ответы (1)


C 2018 6.2.4 говорит: «... Значение указателя становится неопределенным, когда объект, на который он указывает (или только что прошедший), достигает конца своего времени жизни». 3.19.2 говорит нам, что неопределенное значение является «либо неопределенным значением, либо представлением ловушки». 3.19.3 говорит нам, что неуказанное значение является «действительным значением соответствующего типа, если этот документ не налагает требований на то, какое значение выбирается в любом случае» (это означает, что значение может казаться другим каждый раз, когда мы его используем, даже если нет в него вносятся очевидные изменения).

Таким образом, в:

int *p = foo();
printf("%p\n", (void *) p); // "(void *)" added to pass correct type for %p.

Мы не знаем, какое значение будет напечатано для p. Если реализация C не имеет представления ловушки для указателей, то ее неопределенное значение не может быть представлением ловушки, поэтому неопределенное поведение отсутствует. Однако согласно 3.19.3 это допустимое значение.

Этот ответ не отвечает на другие вопросы в посте, например:

int *p = foo();
int *q = p;

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

person Eric Postpischil    schedule 12.03.2021
comment
Этот ответ не относится к другим вопросам в посте, — мне ОЧЕНЬ хотелось бы узнать ответ, если вы его знаете. Но действительно хороший ответ до сих пор. - person klutt; 12.03.2021
comment
Если реализация C не имеет представления ловушки для указателей, то ее неопределенное значение не может быть представлением ловушки, поэтому неопределенное поведение отсутствует. --> То есть, если в реализации есть значение ловушки указателя, это назначение должен выполнять UB? - person chux - Reinstate Monica; 13.03.2021
comment
Было бы представлением-ловушкой иметь указатель, указывающий на адрес, не выровненный для типа? - person Christian Gibbons; 13.03.2021
comment
@chux-ReinstateMonica: если реализация имеет представление ловушки и значение указателя является представлением ловушки, то поведение не определяется стандартом C. - person Eric Postpischil; 13.03.2021
comment
@ChristianGibbons: Возможно, но, возможно, придется интерпретировать формулировку в 6.2.8, касающуюся выравнивания, ограничивающего «распределение» объекта кратным требованию выравнивания, как означающее, что невыровненные указатели не являются значениями ловушки (и поэтому представления ловушек , хотя они не должны ловушки, так как поведение не определено), и это не совсем вопрос здесь, поэтому я не думаю, что мы должны вдаваться в это. Можно ввести отдельный вопрос. - person Eric Postpischil; 13.03.2021
comment
@EricPostpischil Ой. Звучит действительно мета, если один и тот же код иногда является UB - person klutt; 13.03.2021