Даже если я получаю предупреждение, функция возвращает адрес из локальной переменной, она компилируется. Разве это не UB компилятора?
Нет, но если бы это было так, как бы вы узнали? Похоже, вы неправильно понимаете неопределенное поведение. Это не означает «компилятор должен отклонить это», «компилятор должен предупредить об этом», «программа должна завершиться» или что-то в этом роде. Эти могут быть проявлениями UB, но если спецификация языка требует такого поведения, то это не будет undefined . Обеспечение того, чтобы программа C не проявляла неопределенного поведения, является обязанностью программиста, а не реализации C. Если программист не выполняет эту ответственность, реализация C явно не имеет взаимных ответственность - он может делать все, что в его силах.
Более того, нет единого «своего» * 1064 * компилятора. Различные компиляторы могут действовать по-разному и при этом соответствовать спецификациям языка C. Вот здесь-то и появляются варианты поведения, определяемые реализацией, неопределенные и неопределенные. Разрешить такую вариативность намеренно разработчики языка C. Среди прочего, это позволяет реализациям работать способами, которые естественны для их конкретного целевого оборудования и среды исполнения.
Теперь давайте go вернемся к «нет». Вот типичный пример функции, возвращающей адрес автоматической c переменной:
int *foo() {
int bar = 0;
return &bar;
}
Что насчет того, что должно иметь неопределенное поведение? Для функции четко определено вычисление адреса bar
, и результирующее значение указателя имеет правильный тип, который должен быть возвращен функцией. После того, как время жизни bar
заканчивается, когда функция возвращается, возвращаемое значение становится неопределенным (параграф 6.2.4 / 2 стандарта), но это само по себе не приводит к какому-либо неопределенному поведению.
Или рассмотрим вызывающего:
void test1() {
int *bar_ptr = foo(); // OK under all circumstances
}
Как уже обсуждалось, возвращаемое значение нашего конкретного foo()
всегда будет неопределенным, поэтому, в частности, это может быть представление ловушки. Но это соображение во время выполнения, а не во время компиляции. И даже если значение было представлением ловушки, C не требует, чтобы реализация отказала или не сохранила его. В частности, сноска 50 к C11 является явной по этому поводу:
Таким образом, автоматическая c переменная может быть инициализирована представлением ловушки, не вызывая неопределенного поведения, но значение переменной не может быть используется до тех пор, пока в нем не будет сохранено правильное значение.
Обратите внимание также, что foo()
и test1()
могут быть скомпилированы разными запусками компилятора, так что при компиляции test1()
компилятор знает ничего о поведении foo()
, кроме того, что указано его прототипом. C не предъявляет требований к времени трансляции для реализаций, которые зависят от поведения программ во время выполнения.
С другой стороны, требования к представлениям прерываний будут применяться по-другому, если бы функция была немного изменена:
void test2() {
int *bar_ptr = NULL;
bar_ptr = foo(); // UB (only) if foo() returns a trap representation
}
Если возвращаемое значение foo()
оказывается представлением ловушки, тогда сохраняет его в bar_ptr
(в отличие от инициализации bar_ptr
с it) производит неопределенное поведение во время выполнения. Опять же, «undefined» означает именно то, что написано на банке. C не определяет какое-либо конкретное поведение для реализаций, которое должно проявляться в данных обстоятельствах, и, в частности, не требует, чтобы программы завершали работу или вообще проявляли какое-либо внешне видимое поведение. И снова это соображение во время выполнения, а не во время компиляции.
Более того, если возвращаемое значение foo()
оказывается не представлением ловушки (вместо этого является значением указателя, которое не является адресом любого живого объекта), то нет ничего плохого в чтении самого этого значения:
void test3() {
int *bar_ptr = foo();
// UB (only) if foo() returned a trap representation:
printf("foo() returned %p\n", (void *) bar_ptr);
}
Самым большим и наиболее часто используемым неопределенным поведением в этой области будет попытка разыменовать возвращаемое значение foo()
, которое, вне зависимости от того, является ли представление ловушкой или нет, почти наверняка не указывает на живой объект int
:
void test4() {
int *bar_ptr = foo();
// UB under all circumstances for the given foo():
printf("foo() returned a pointer to an int with value %d\n", *bar_ptr);
}
Но опять же, это соображение во время выполнения, а не во время компиляции. И снова undefined означает undefined. Следует ожидать, что реализация C преобразует это успешно, пока есть объявления в области видимости для задействованных функций, и хотя некоторые компиляторы могут предупреждать, они не обязаны это делать. Поведение функции test4
во время выполнения не определено, но это не означает, что программа обязательно завершится сбоем или завершится каким-либо другим образом. Возможно, но я ожидаю, что на практике неопределенное поведение, проявляющееся во многих реализациях, будет заключаться в том, чтобы напечатать "foo () вернула указатель на int со значением 0". Это никоим образом не противоречит требованиям C.