【问题标题】:Strange behaviour of linked list with struct带有结构的链表的奇怪行为
【发布时间】:2020-11-02 20:58:44
【问题描述】:

基本上我创建了一个带有结构的链表和一个将新节点添加到开头的函数。 我在链表中​​添加了 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 个数字是错误的。 有人能解释一下到底发生了什么吗?

【问题讨论】:

  • Lin fresh = {nod, &amp;orig}; -- 您正在存储临时变量的地址。问问自己当函数返回时会发生什么。
  • 只有一个变量 x。您希望如何在一个变量 x 中存储 4 个值?
  • 请不要使用using namespace std;。它提高了标识符与 std 命名空间中的符号冲突的机会。常见问题与sortminmax 有关。

标签: c++ pointers struct linked-list


【解决方案1】:
  1. x 存储在堆栈中,未初始化,因此 x.nex 是垃圾(甚至不是 NULL)。
  2. fresh 也存储在堆栈中。它已初始化,但不幸的是,orig 也存储在堆栈中,并且两者都仅在调用期间存在。 ext 返回一份 `fresh.

代码看起来你更熟悉一切都是参考的语言(如 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 几乎肯定会崩溃,而取消引用悬空指针会导致在不相关的程序部分中出现模糊问题 [有时也会崩溃]。

【讨论】:

  • 没有清理,所以泄漏了很多内存
【解决方案2】:

你的问题在这里:

Lin fresh = {nod, &orig};

你正在抓取orig的地址,声明为:

Lin ext (Lin orig, int nod) {

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

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

Lin ext (Lin &orig, int nod) {

但是你每次都传递相同的Lin 对象(x),所以&amp;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;
    } 
}

【讨论】:

    【解决方案3】:
        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;
    }
    

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

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

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

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

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

    修改后的代码运行结果

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

    关于内存泄漏,可以将实现更改为使用可以在 ´´

    中找到的智能指针

    然后将任何原始 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();
    

    【讨论】:

    • 发帖前测试
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-28
    • 2019-08-06
    • 1970-01-01
    • 2017-08-08
    • 2015-11-21
    • 1970-01-01
    相关资源
    最近更新 更多