当函数中的值发生变化时,指针不反映变化

Pointers Not Reflecting Change when value changed in a function

我正在使用 array.It 实现一个链表,它有一个函数 Reverse 定义如下

void Reverse(List *l)
{
    List *m=CreateList(ListSize(l));
    for(int i=0;i<l->count;i++)
    {
        m->array[i]=l->array[l->count-i-1];
        m->count++;
    }
    free(l->array);
    free(l);
    l=m;
    //Traverse(l); Here it prints the reversed List

}

它以一个List结构作为参数。我像这样从 main 调用它

int main()
{
    size=5;
    List *l=CreateList(size);
    Reverse(l);
    Traverse(l); //Here the list is not printing the reversed list !

}

为什么我对 l 所做的反向更改没有显示在 main() 中? 谢谢!

你必须传递一个双指针才能改变指针指向的位置。

void Reverse(List **l);
Reverse(&l);

在 C 中不可能通过引用传递,来自 www-cs-students.stanford.edu:

In C, Pass-by-reference is simulated by passing the address of a variable (a pointer) and dereferencing that address within the function to read or write the actual variable. This will be referred to as "C style pass-by-reference."

当您执行 l = m 时,您只是在 Reverse 内设置 llReverse 的参数列表的一部分。当您更改该值时,它只会在调用 Reverse 期间更改它。它永远不会传播给调用者。

您可以使用 Filip 的方法传播值。

或者,您可以:

  1. 重新定义 Reverse 为:List *Reverse(List *l)
  2. 添加return m作为其中的最后一个语句
  3. 并通过以下方式调用:l = Reverse(l).

但是,这有点浪费。您还可以进行就地反转:

void Reverse(List *l)
{
    int left = 0;
    int right = l->count - 1;

    // NOTE: this should be whatever the type of array[0] is:
    int tmp;

    for (;  left < right;  ++left, --right) {
        tmp = l->array[left];
        l->array[left] = l->array[right];
        l->array[right] = tmp;
    }
}

更新:

When I do *l=*m in the last line the code works but I dont know why !

甚至有一个错误。

那是因为您要替换 l 指向的全部 内容 。本质上,您创建了一个新的[反向]列表m,填充它,然后将其复制回l

free(l)之前*l = *m是一个错误。您正在取消引用指向已释放内存的指针。

想做free(l->array),但是free(l).

此外,在完成 *l = *m 之后,您还需要完成 free(m)。否则,m 指向 [但 而不是 m->array 指向的 struct 内存泄漏,因为它保存在 l->array].

这是不必要的复杂性并且容易出错。

并且您完成了所需工作量的两倍。当你做 *l = *m 时,你实际上是在做:

l->count = m->count;
for (int i = 0;  i < m->count;  ++i)
    l->array[i] = m->array[i];

换句话说,不要执行 *l = *m,即使它看起来有效。使用三种 easier/correct 方法中的一种。


更新#2:

It seems to work fine though(*l=*m) even if the memory that l points to doesn't exist . Please tell me what does the function free() actually does in this case

是的,它似乎工作正常,但工作正常[在一般情况下]。

执行free(l)后,l指向的内容不可用。内存分配器假定您没有进一步使用此内存[因为当您free(l)时,您是在告诉它] .分配器可以自由地 随心所欲地 使用该内存:

  1. 在多线程环境中,在你调用 free(l) 和它 return 控制你之间,另一个线程可能已经完成了 malloc 现在你有两个线程指向到相同的内存并将其用于不同的目的。执行后续 *l = *m 会破坏 other 线程存储在那里的数据。这引入了一个微妙的、间歇性的、难以发现的错误。
  2. 在单线程环境中,内存分配器可能会使用内存来存储元数据以用于其自身的[内部]目的。同样, 之前 return 来自 free(l)。因此,当您执行 *l = *m 时,您可能会破坏分配器的内部数据结构(反之亦然)。下次您发出 malloc 时,分配器可能会出现段错误。
  3. 下次您发出 malloc 时,它可能 return 指向 相同 内存的指针 l is/was 指向(例如,您通过调用 l2 = CreateList(10) 创建第二个列表 [in main])。现在,ll2 具有相同的值(即它们指向相同的 struct)。因此,ll2 不是单独的列表,而是 碰撞 。或者,l2 可能不同,但 l2->array 可能与 l
  4. 重叠

下面是一般资源 allocation/release 问题的一个例子。因为您不知道分配或释放函数在内部执行的操作,所以在 released/freed 之后您无法安全地访问资源中的 任何内容。下面的 release 函数添加了一行来说明为什么你用 *l = *m

做的事情是不安全的
#include <malloc.h>

struct resource {
    int count;
    int *array;
};

struct resource *
allocate_resource(int count)
{
    struct resource *ptr;

    ptr = malloc(sizeof(struct resource));
    ptr->count = count;
    ptr->array = malloc(sizeof(int) * count);

    return ptr;
}

void
free_resource(struct resource *ptr)
{

    free(ptr->array);

    // prevents "*l = *m" from "seeming to work"
    ptr->array = NULL;

    free(ptr);
}

int
main(void)
{

    while (1) {
        struct resource *x = allocate_resource(20);

        // x may be utilized here ...

        free_resource(x);

        // x may _not_ be utilized here ...
        // this will segfault
        x->array[0] = 23;
    }

    return 0;
}

在 C 语言中,函数参数总是按值传递。这意味着在函数 ReverseList()l 是在函数调用中传递的指向 List 的指针的 copy。因此,当创建反向 List 并将地址分配给函数内的 l 时,这对 main() 中指向 List 的原始指针没有影响(因为Reverse() 中的 l 只是 main()l 的副本)。给函数中的变量取一个不同于它们在调用者中的相应名称的名称可能有助于保持这种事情的正确性。

一个解决方案是将指向 List 的指针传递给 Reverse() 函数:

void Reverse(List **lptr)
{
    List *m=CreateList(ListSize(*lptr));
    for(int i=0;i<(*lptr)->count;i++)
    {
        m->array[i]=(*lptr)->array[(*lptr)->count-i-1];
        m->count++;
    }
    free((*lptr)->array);
    free(*lptr);
    *lptr=m;
    //Traverse(l); Here it prints the reversed List
}

使用 Reverse(&l) 调用此函数。这里,由于在Reverse()中使用了指向List的指针的副本,而在main()lptr指向了指针llptr 可以被取消引用,并且 m 的值可以分配给在 main().

中找到的指向 List (l) 的实际指针

另一种解决方案是将 Reverse() 函数更改为 return 指向调用者 List 的指针。然后将 returned 值赋值给 l:

List * Reverse(List *l)
{
    List *m=CreateList(ListSize(l));
    for(int i=0;i<l->count;i++)
    {
        m->array[i]=l->array[l->count-i-1];
        m->count++;
    }
    free(l->array);
    free(l);
    return m;
    //Traverse(l); Here it prints the reversed List
}

int main(void)
{
    size=5;
    List *l=CreateList(size);
    l = Reverse(l);
    Traverse(l); //Here the list is not printing the reversed list !

    return 0;
}

此外,如果 CreateList() 尚未检查分配错误,代码应该在 free 之前的分配之前检查这些错误。