Висячий указатель в C

Я написал программу на C с оборванным указателем.

#include<stdio.h>

int *func(void)
{
    int num;
    num = 100;
    return &num;
}

int func1(void)
{
    int x,y,z;
    scanf("%d %d",&y,&z);
    x=y+z;
    return x;
}

int main(void)
{
    int *a = func();
    int b;
    b = func1();
    printf("%d\n",*a);
    return 0;
}

Я получаю выход как 100, хотя указатель болтается.

Я сделал единственное изменение в приведенной выше функции func1(). Вместо того, чтобы брать значения y и z из стандартного ввода, как в приведенной выше программе, теперь я присваиваю значение во время компиляции.

Я переопределил func1() следующим образом:

int func1(void)
{
    int x,y,z;
    y=100;
    z=100;
    x=y+z;
    return x;
}

Теперь выход равен 200.

Может кто-нибудь объяснить мне причину двух вышеупомянутых выходов?


person pradeepchhetri    schedule 13.03.2011    source источник
comment
Есть кнопка кода, которую вы можете использовать для создания кода всего раздела вместо использования фрагментов кода для каждой строки.   -  person Marlon    schedule 13.03.2011
comment
Какие входные данные для первой версии дают результат 100?   -  person Marcelo Cantos    schedule 13.03.2011


Ответы (7)


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

void func3() {
  int a=0, b=1, c=2;
}

Если вы включите вызов func3() между func1 и printf, вы получите другой результат.

РЕДАКТИРОВАТЬ: Что на самом деле происходит на некоторых платформах.

int *func(void)
{  
    int num;  
    num = 100;  
    return &num;  
}

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

Когда вы вызываете функцию, адрес возврата помещается в стек (в позиции 10), а указатель стека увеличивается до 14 (да, очень упрощенно). Затем переменная num создается в стеке в позиции 14, а указатель стека увеличивается до 18.

Когда вы возвращаетесь, вы возвращаете указатель на адрес 14 — адрес возврата извлекается из стека, а указатель стека возвращается к 10.

void func2() {
    int y = 1;
}

Здесь происходит то же самое. Адрес возврата помещается в позицию, y создается в позиции 14, вы назначаете 1 для y (записываете в адрес 14), вы возвращаете и возвращаете указатель обратно в позицию 10.

Теперь ваш старый int * вернулся из func точек на адрес 14, и последней модификацией, внесенной в этот адрес, было назначение локальной переменной func2. Итак, у вас есть висячий указатель (ничего выше позиции 10 в стеке не является допустимым), который указывает на значение, оставшееся после вызова func2.

person Erik    schedule 13.03.2011
comment
Когда я использую scanf(), почему ответом является значение num. Не перезаписывается ли переменная стека num на y или z. Можете ли вы объяснить мне, какие переменные идут в стек? - person pradeepchhetri; 13.03.2011
comment
Компилятор может свободно проверить ваше приложение и решить, что вам нужно всего X байтов стека для этих вызовов, выделить все это за один раз, и, следовательно, вы можете получить разные адреса для локальных переменных в этих вызовах функций. Это неопределенное поведение — вам нужно разобрать исполняемый файл, чтобы точно увидеть, что происходит. - person Erik; 13.03.2011
comment
Большое спасибо, Эрик, за подробное объяснение. - person pradeepchhetri; 13.03.2011

Это из-за того, как выделяется память.

После вызова func и возврата висячего указателя часть стека, где хранилось num, по-прежнему имеет значение 100 (это то, что вы видите впоследствии). Мы можем прийти к такому выводу на основе наблюдаемого поведения.

После изменения похоже, что происходит то, что вызов func1 перезаписывает ячейку памяти, на которую указывает a, с результатом добавления внутри func1 (пространство стека, ранее использовавшееся для func, теперь повторно используется func1), поэтому вы видите 200.

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

person Jon    schedule 13.03.2011

Это неопределенное поведение. Он может корректно работать на вашем компьютере прямо сейчас, через 20 минут, может выйти из строя через час и т. д. Как только другой объект займет то же место в стеке, что и num, вы будете обречены!

person Marlon    schedule 13.03.2011

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

В частности, области памяти повторно используются случайно* в func1. Результат зависит от схемы стека, оптимизации компилятора, архитектуры, соглашений о вызовах и механизмов безопасности стека.

person phihag    schedule 13.03.2011

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

person Codo    schedule 13.03.2011

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

person Marcelo Cantos    schedule 13.03.2011

Пожалуйста, изучите функции из основ C. Ваша концепция ошибочна... main должна быть

int main(void)  
{  
    int *a = func();  
    int b;

    b = func1();  
    printf("%d\n%d",*a,func1());  
    return 0;  
}

Это выведет 100 200

person Avradeep    schedule 21.06.2012