Почему эта программа на C возвращает правильное значение в VC ++ 2008? - PullRequest
0 голосов
/ 10 декабря 2011

Мы знаем, что автоматические переменные уничтожаются при возврате функции.

Тогда, почему эта программа на C возвращает правильное значение?

#include <stdio.h>
#include <process.h>

int * ReturningPointer()
{
    int myInteger = 99;

    int * ptrToMyInteger = &myInteger;

    return ptrToMyInteger;
}

main()
{
    int * pointerToInteger = ReturningPointer();

    printf("*pointerToInteger = %d\n", *pointerToInteger);

    system("PAUSE");
}

выход

*pointerToInteger = 99

Редактировать

Тогда почему это дает значения мусора?

#include <stdio.h>
#include <process.h>

char * ReturningPointer()
{
    char array[13] = "Hello World!";

    return array;
}

main()
{
    printf("%s\n", ReturningPointer());

    system("PAUSE");
}

выход

xŤ

Ответы [ 6 ]

4 голосов
/ 10 декабря 2011

На этот вопрос нет ответа: ваш код демонстрирует неопределенное поведение. Он может напечатать «правильное значение», как вы видите, он может напечатать что-нибудь еще, может вызвать ошибку, он может заказать пиццу онлайн с помощью вашей кредитной карты.

Разыменование этого указателя в main недопустимо, оно не указывает на действительную память в этой точке. Не делай этого.

Между двумя примерами есть большая разница: в первом случае *pointer оценивается перед вызовом printf. Таким образом, учитывая, что между строкой, в которой вы получаете значение указателя, и printf нет вызовов функций, высока вероятность того, что расположение стека, на которое указывает pointer, не будет перезаписано. Таким образом, значение, которое было сохранено там до вызова printf, скорее всего, будет выведено (что значение будет передано в стек printf, а не в указатель).

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

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

2 голосов
/ 10 декабря 2011

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

Подумайте об этом, это имеет смысл - чего еще вы ожидали?Должен ли он дать вам ноль?Если это так, то компилятор должен вставить специальные инструкции в конце области действия, чтобы стереть содержимое переменной - пустая трата ресурсов.Самое естественное, что должен сделать компилятор, - это оставить содержимое без изменений, чтобы вы случайно получили правильный вывод из неопределенного поведения.

Можно сказать, что это поведение определяется реализацией.Например.Другой компилятор (или тот же компилятор в режиме «Release») может решить выделить myInteger исключительно в регистре (не уверен, что он действительно может сделать это, когда вы берете его адрес, но ради аргумента ...), в этом случае память не будет выделена для 99, и вы получите вывод мусора.

В качестве более наглядного (но полностью непроверенного) примера - если вы вставите немного malloc и воспользуетесь некоторым использованием памятидо printf вы можете найти искомое значение мусора: P

Ответ на «отредактированную» часть

«Реальный» ответ, который вы хотите получить, должен быть получен при разборке.Хорошее место для начала - gcc -S и gcc -O3 -S.Я оставлю углубленный анализ для волшебников, которые придут.Но я сделал краткий просмотр с помощью GCC, и оказалось, что printf("%s\n") переводится в puts, поэтому соглашение о вызовах отличается.Поскольку локальные переменные размещаются в стеке, вызов функции может «уничтожить» ранее выделенные локальные переменные.

1 голос
/ 10 декабря 2011
  1. Уничтожить это неправильное слово imho. Локальные данные находятся в стеке, если функция возвращает пространство стека, может быть снова использовано. До этого он не перезаписывается и по-прежнему доступен указателями, которые вам могут не понадобиться (потому что это никогда не может указывать на что-то действительное)

  2. Указатели используются для адресации пространства в памяти, для локальных указателей действует то же, что я описал в 1. Однако указатель, похоже, передается основной программе.

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

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

0 голосов
/ 10 декабря 2011

Основная идея заключается в том, что переменная представляет имя и тип для значения, хранящегося где-то в памяти.Когда оно «уничтожено», это означает, что a) к этому значению больше нельзя получить доступ, используя это имя, и b) область памяти свободна для перезаписи.

Поведение не определено, потому что реализацияКомпилятор может сам выбирать, через какое время после «уничтожения» местоположение будет перезаписано.

0 голосов
/ 10 декабря 2011

То есть undefined behavior. Это означает, что может случиться что угодно, даже то, что вы ожидаете.

Сложная часть UB - это когда вы получаете ожидаемый результат, и вы думаете, что делаете все правильно. Затем любое изменение в не связанной части программы изменяет это ...

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

Если вы позвоните, например, printf дважды, во второй раз, скорее всего, будет напечатано другое значение.

0 голосов
/ 10 декабря 2011

В большинстве программ на C / C ++ их локальные переменные живут в стеке, а destroyed означает overwritten с чем-то другим. В этом случае это конкретное местоположение еще не было перезаписано, когда оно было передано в качестве параметра printf().

Конечно, наличие такого кода вызывает проблемы, поскольку в соответствии со стандартами C и C ++ он демонстрирует неопределенное поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...