繁体   English   中英

带有结构的链表的奇怪行为

[英]Strange behaviour of linked list with struct

基本上,我创建了一个带有 struct 的链表和一个将新节点添加到开头的函数。 我在链表中​​添加了 4 个节点并使用 for 循环读取它。 但输出很奇怪

#include <iostream>
using namespace std;

struct Lin{
    int val;
    Lin* nex;
};

Lin ext (Lin orig, int nod) {
    Lin fresh = {nod, &orig};
    return fresh;
};

int main(){
    Lin x;
    x.val=15;
    x=ext(x,25);
    x=ext(x,35);
    x=ext(x,45);

    for (int i = 0; i < 4; i=i+1) {
        cout <<x.val<< endl;
        x = *x.nex;
    }    
}

输出是:

45
35
-72537468
892483373
Segmentation fault (core dumped)

前 2 个数字是正确的,但第 3 个数字是错误的。 有人能解释一下到底发生了什么吗?

  1. x存储在堆栈中,并且未初始化,因此 x.nex 是垃圾(甚至不是 NULL)。
  2. fresh也存储在堆栈中。 它被初始化,但不幸的是, orig也存储在堆栈中,并且都只在调用期间存在。 ext返回新鲜'副本

代码看起来你更熟悉一切都是参考的语言(如 JS、C# 等)。 在 C/C++ 中,情况并非如此,您必须显式使用指针。 更像:

Lin *ext (Lin *orig, int nod) {
    return new Lin{nod, orig}; // Lin(nod, orig) on pre-C++11 systems
}

int main() {
    Lin *x = nullptr; // NULL on pre-C++11 systems
    x = ext(x, 15);
...
    for (Lin *y = x; y; y = y->nex)
        cout << y->val << endl

更新。

  1. 我忘了提到列表结构。 它是一个链,其中最后一个节点的nex为 NULL(这是 C/C++ 中特殊的空指针;在现代 C++ 中也可以拼写为nullptr )。
  2. 正如 JHBonarius 注意到的,我忘记描述如何释放该列表(否则它将保留在内存中直到程序结束)。 它是在类似的循环中完成的; 这里的技巧是删除节点之前保存nxt
for (Lin *y = x; y; ) {
    Lin *next = y->nxt;
    delete y; // deletes the object `y` points to,
    // so accessing `y->nxt` is not allowed anymore,
    // but accessing `next` is
    y = next;
}

请注意,当第一个节点被删除时, x成为一个悬空(即指向任意内存位置)指针,因此最好在之后将其设置为 NULL(因为可以检查指针是否为 NULL 但不能检查是否为悬空;取消引用 NULL 也是在取消引用悬空指针时几乎可以肯定崩溃会导致出现在不相关程序部分中的模糊问题 [有时也会崩溃])。

你的问题在这里:

Lin fresh = {nod, &orig};

您正在获取orig的地址,声明为:

Lin ext (Lin orig, int nod) {

所以orig其实是Lin副本! 当函数完成时它会超出范围,所以你会留下一个无效的指针和未定义的行为。

您可以通过引用而不是复制来避免这种情况:

Lin ext (Lin &orig, int nod) {

但是你每次都传递相同的Lin对象 ( x ),所以&orig每次都会是相同的地址。 您将需要多个具有不同地址的不同Lin对象。 如果您想继续避免动态分配的内存,则可能如下所示:

int main(){
    Lin x;
    x.val = 15;
    x.nex = nullptr; // Don't forget to null your tail's next!

    Lin x2 = ext(x,25);
    Lin x3 = ext(x2,35);
    Lin x4 = ext(x3,45);

    for (Lin i = x4; i.nex; i = *i.nex) {
        cout << i.val << endl;
    } 
}
    Lin ext (Lin orig, int nod) {
        Lin fresh = {nod, &orig};
        return fresh;
    };

This - Lin orig 作为原始数据的 COPY 传递,引用 this 将在某些时候弄乱内存,因为您正在引用临时变量的内存地址。

我会自己传入一个指针

使得:

#include <iostream>
#include <string>

struct Lin 
{
   int val;
   Lin* nex;

   Lin(int value = 0, Lin *next = NULL) 
    : val(value)
    , nex(next) 
   {}
};

Lin* ext(Lin *orig, int nod) 
{
    return new Lin(nod, orig);
};

int main()
{
   Lin *pi_x = new Lin(15);
   pi_x = ext( pi_x, 25 );
   pi_x = ext( pi_x, 35 );
   pi_x = ext( pi_x, 45 );

   std::cout << "List:" << std::endl;
   for(Lin *lin = pi_x; lin != NULL; lin = lin->nex )
   {
      std::cout << lin->val << std::endl;
   }
   std::cout << "------" << std::endl;

  return 0;
}

最后但并非最不重要的 - 您正在循环指针 - 内存地址。 链表通过将空指针作为终止元素(next 为空)来建模

您正在创建的结构应该至少从某种内存分配开始,这样您就不会在 Lin 的指针中设置随机地址,并且您还应该保证您不会取消引用空指针(即 *x.nex)

-- 我重做了我的实现 - 因为注释正确地说明变量是堆栈分配的,并且相同的地址用于“节点工厂”方法。

现在,通过调用“new”,内存是动态分配的(堆),并且在使用赋值运算符时,使用指针只会更新内存地址,而不是像以前一样使用堆栈分配的内存复制结构的内容。

使用 NULL 检查终止 for 循环可能是一件有风险的事情 - 正如评论所暗示的,如果数据不符合预期的规则,则存在无限循环的风险。

运行修改后的代码的结果

    List:
    45
    35
    25
    15
    ------

关于内存泄漏,可以更改实现以使用可以在“<memory>”中找到的智能指针

然后将任何原始 Lin 指针与

#include <memory>
std::shared_ptr<Lin> ptr;
ptr = std::make_shared<Lin>(0, NULL);

// Getting raw pointer from smart_pointer
Lin *raw = ptr.get();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM