首页 > 解决方案 > C++:使用 cout 导致变量值发生变化

问题描述

我正在学习如何使用类并编写了以下代码:

#include<iostream>

using namespace std;

class DQNode
{
    public:
    int data;
    DQNode *next, *last;

    DQNode(int num, DQNode *n, DQNode *l)
    {
        data = num;
        next = n;
        last = l;
    }
};

class Deque
{
    private:
    DQNode *top, *bottom;
    int size;

    public:
    Deque()
    {
        top = NULL;
        bottom = NULL;
        size = 0;
    }

    void addFirst(int inp)
    {
        DQNode nn(inp, NULL, top);
        if(top != NULL)
        {
            (*top).next = &nn;
        }
        top = &nn;
        if(bottom == NULL)
        {
            bottom = &nn;
        }
    }

    void PrintAll()
    {
        cout<<top->data<<endl;
    }
};

int main()
{
    Deque n;
    n.addFirst(5);
    cout<<"yo"<<endl;
    n.PrintAll();
}

上面的代码打印“yo”后跟一个随机整数,有趣的部分是删除cout<<"yo"<<endl;输出完全符合预期,即 5。所以,如果有人明白出了什么问题,请帮助我。

标签: c++

解决方案


你有未定义的行为。

当您违反规则时,就会发生这种情况。如果幸运的话,您的程序会因分段错误而崩溃。其他时候,你运气不好,你的程序行为不端。

你是怎么打破规则的?好吧,你访问了一个死对象。

问题的根源发生在您的addFirst函数中。您将 a 存储DQNode在您的Deque但该节点死亡。

您会看到,所有具有自动存储功能的局部变量都有明确定义的生存和死亡规则。它们的生命周期是基于范围的。它看起来像这样:

                                //      Lifetimes
void addFirst(int inp)          //   inp
{                               //    | 
    DQNode nn(inp, NULL, top);  //    |       nn
    if(top != NULL)             //    |       |
    {                           //    |       |
        (*top).next = &nn;      //    |       +--- <- top->next
    }                           //    |       |
    top = &nn;                  //    |       +--- <- top
    if(bottom == NULL)          //    |       |
    {                           //    |       |
        bottom = &nn;           //    |       +--- <- bottom
    }                           //    |       |
}                               //    |       X -- nn dies
                                //    X -- inp dies

具有自动生命周期的变量首先生存,最后死亡。没有例外。在}函数末尾的字符处,即其作用域结束的地方,所有局部变量都被销毁。nn首先,然后inp

此时, top, bottomortop->next仍指向nn, 已被销毁!

然后,在你的PrintAll函数中读取指针=,它指向一个被破坏的变量。那时,您可以读取程序该点堆栈上的任何内容。一个简单的调用就cout可以在原来的地方分配变量nn,并分配任何需要的值。您的指针仍将指向那里,并打印垃圾。


你能为这个做什么?

好吧,您不想要自动存储。它不会做你想做的事。您希望控制变量的生命周期,并在您决定何时不再需要它们时释放它们。这称为免费商店您使用动态分配在那里创建对象:

void addFirst(int inp)
{
    // new will dynamically allocate the object on the free store.
    DQNode* nn = new DQNode(inp, NULL, top);
    if(top != NULL)
    {
        (*top).next = nn;
    }
    top = nn;
    if(bottom == NULL)
    {
        bottom = nn;
    }
} // the `nn` pointer dies, but not the object it refers to!

但是,您的程序永远不会自行解除分配变量,您必须手动执行此操作,否则会发生内存泄漏。

~Deque() {
    // traverse the tree
    // delete all node in the tree
}

幸运的是,还有一个称为 a 的工具std::unique_ptr,它会在分配的对象死亡时负责删除它。

名称unique_ptr来自唯一所有者。内存的所有权可以转让,但始终只有一个所有者。当所有者死亡时,它会从空闲存储中释放对象:

// create a int equal to 1 on the free store using dynamic allocation
std::unique_ptr<int> int_a = std::make_unique<int>(1);
std::unique_ptr<int> int_b = nullptr;

// transfer ownership from int_a to int_b
int_b = std::move(int_a); 

// int_a is null
// int_b point to a int equal to 1

// This is disallowed, you cannot have two owner, so no copy
// int_a = int_b;

// can have an observing raw pointer:
int* int_c = int_b.get();

// transfer ownership back
int_a = std::move(int_b);

// at that point int_a is equal to int_c
// int_a is the owner, int_c simply point to the same int.
// int_b is null

// Set the last owner to null:
int_a = nullptr;

// the int has been destroyed because there is no owner left.
// int_a is null, int_b is null
// int_c is a dangling pointer, point to the dead object.

推荐阅读