Почему объект выходит из области видимости, когда передается как указатель, но не когда он возвращается

#c #scope #pass-by-reference #pass-by-value

#c #область видимости #передача по ссылке #передача по значению

Вопрос:

Создавая очень простой связанный список, я обнаружил, что запутался в том, что, вероятно, является очень простой концепцией области видимости. Первый работает так, как ожидалось. Казалось бы, во второй функции makeNode узел «n» выходит из области видимости при завершении. Я в замешательстве, почему это происходит.

Я почти уверен, что выделенная память все еще там, и оба метода имеют указатель на эту память. Итак, почему один из них не работает?

программа имеет две функции make node

 #include <iostream>
using namespace std;

struct Node
{
    int value;
    Node* next;
    Node() : next(NULL){}
};

Node* makeNode1(int value)
{
    Node* n = new Node;
    n->value = value;
    return n;
}

void makeNode2(int value, Node* mountPt)
{
    Node* n = new Node;
    n->value = value;
    mountPt = n;
}

void destroyNode(Node* mountPt)
{
    if(mountPt->next != NULL)
    {
        destroyNode(mountPt->next);
        delete mountPt;
    }
}

int main() {
    Node* origin = NULL;

    // This works
    origin = makeNode1(4);

    // causes a runtime error when dereferenced
    makeNode2(4, origin);

    return 0;
}
  

Комментарии:

1. Откройте главу в вашей книге по C , в которой объясняется разница между передачей параметров функции по значению и передачей их по ссылке, и прочитайте ее. Вы передаете его по значению и makeNode2 утечка памяти. Указатель, переданный в is, не инициализируется, и он остается неинициализированным в вызывающем объекте, потому что он был передан по значению. Вот как работает C .

2. Произошла утечка makeNode2 , вам следует добавить строку if(mountPt) delete mountPt; , чтобы сначала убедиться, что предыдущее выделение удалено, а затем его можно назначить n .

3. Vuwox верен.

4. @Amadeus Да, я понимаю, что вышеупомянутая компиляция, но без удаления, эта функция создает утечку памяти. И, добавляя его, компилятор не будет выдавать parameter 'mountPt' set but not used будет использовать полный уровень предупреждений, а также ошибка времени выполнения также не будет добавляться.

Ответ №1:

Поскольку makeNode2 параметр указателя mountPt передается по самому значению, то любая модификация самого указателя внутри функции like mountPt = n; не имеет ничего общего с исходным аргументом origin .

Вы можете изменить его на передачу по ссылке, т.Е.

 void makeNode2(int value, Node*amp; mountPt)
  

Комментарии:

1. Обратите внимание, что это приводит к утечке памяти; исходный origin указатель, выделенный в makeNode1 , заменяется новым, выделенным в makeNode2 без delete редактирования.

2. Я в курсе. Очевидно, что требуется только одна функция. Я просто показывал обе конфигурации.

Ответ №2:

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

Комментарии:

1. Передача указателя по значению все равно позволит вам навсегда изменить значение, хранящееся по адресу, но не позволит вам изменить то, на что указывает указатель.

2. «возвращается назад» — плохой способ описать это. Источник вообще никогда не меняется.

Ответ №3:

Измените функцию для следующего:

 void makeNode2(int value, Node* mountPt)
{
    Node* n = new Node;
    n->value = value;
    if(mountPt) delete mountPt;
    mountPt = n;
}
  

где, если mountPt уже был выделен, он будет освобожден, а затем origin может указывать на выделение n

Комментарии:

1. Я согласен, что это было бы лучшим управлением памятью, но не будет ли это иметь ту же проблему с областью видимости?

2. @mreff555 Да, это было бы.

3. На самом деле это не так. Но если к параметру Node * добавлен оператор ссылки, как упоминалось выше, это работает

Ответ №4:

Решение X-Y: устраните необходимость в makeNode функции, расширив конструктор Node

 struct Node
{
    int value;
    Node* next;
    Node(int val = 0) :value(val), next(NULL){}
};
  

Использование

 Node * origin = new Node(4);
  

Ответ №5:

Хотя все вышеперечисленное, включая принятый ответ, имеет отношение к делу, ни одно из них напрямую не указывает на фактический логический недостаток. Вместо mountPt = n; вам нужно mountPt->next = n; .