【问题标题】:Strange behaviour using pass-by-reference / dereferencing使用传递引用/取消引用的奇怪行为
【发布时间】:2019-03-25 14:37:24
【问题描述】:

这是我的演示代码:

#include <stdio.h>

#define WORK 0

typedef struct FooStruct {
    int x;
} FooStruct;

void setX(FooStruct *foo_ptr) {
    FooStruct foo = *foo_ptr;

    if (WORK) {
        foo_ptr->x = 10;
    } else {
        foo.x = 10;
    }
}

FooStruct makeFoo() {
    FooStruct foo = { 0 };

    setX(&foo);

    return foo;
}

int main(void) {
    FooStruct foo = makeFoo();

    printf("X = %d\n", foo.x);

    return 0;
}

如果 WORK 定义为 1,则代码按预期执行并打印“X = 10”。

但是,如果 WORK 设置为 0,它会打印“X = 0”,或者如果 FooStruct 没有默认初始化(即将 FooStruct foo = {}; 更改为 FooStruct foo;),valgrind 将抛出一个值printf 行出现未初始化错误。

我确定这是由于我的理解存在差距,但对我来说,两个不同的 WORK 分支在操作上应该基本相同,所以我不确定误解来自哪里。

这是使用 gcc 8.2.0 编译的,带有/不带有 valgrind,结果相同。

========================

编辑:

简化示例:

#include <stdio.h>

#define WORK 0

void setX(int *x_ptr) {
    if (WORK) {
        *x_ptr = 5;
    } else {
        int x = *x_ptr;
        x = 5;
    }
}

int main(void) {
    int x = 0;
    setX(&x);

    printf("X = %d\n", x);

    return 0;
}

【问题讨论】:

  • 为什么你认为两个版本应该相同?这正是“按值传递”和“按引用/指针传递”之间的区别
  • 您可以将其简化为void set_x (int* ptr) { if(foo) { *ptr = 5; } }。当 foo 为零时,ptr 指向的任何东西都不会受到影响并保持为 0,这很奇怪吗?

标签: c pass-by-reference dereference


【解决方案1】:

当您#define WORK 0 时,代码似乎也按预期工作。如果您在结构的副本中设置成员,则不应期望修改原始成员。

你有:

void setX(FooStruct *foo_ptr) {
    FooStruct foo = *foo_ptr;

    if (WORK) {
        foo_ptr->x = 10;    // Modify the structure pointed to by foo_ptr
    } else {
        foo.x = 10;         // Modify the local copy of the structure
    }
}

因为您已经创建了一个 MCVE(Minimal, Complete, Verifiable Example — 谢谢!),所以没有对 setX() 中结构的修改副本进行任何处理。

【讨论】:

  • 啊,我明白了!因此,当您在函数中取消引用指针时,它实际上并没有取消引用指针所指向的堆上同一段内存,而是在堆栈上分配新内存并将值复制到那里?我认为我的大部分困惑来自于我了解到ptr-&gt;x(*ptr).x 的别名,所以Foo f = *ptr; f.x 与我所知道的相同,但它显然不是基于示例和什么你说过。
  • 是的; setX 函数中的 foo 是一个局部变量,您可以通过将 *foo_ptr 的内容复制到局部变量中来对其进行初始化。使用ptr-&gt;x(*ptr).x '相同',只是它更短且更具可读性,尤其是在嵌套上下文中:ptr1-&gt;struct2-&gt;member3(*(*ptr1).struct2).member3 更易于阅读(并且更易于键入!),所以使用箭头@ 987654335@ 带有指针且不使用星点 ((*p).x)。在您的 Foo f = *ptr; 示例中,您正在初始化而不是分配。有区别。初始化后f.x指的是本地的f,而不是*ptr
  • 我已经用一个更简化的示例更新了操作,该示例演示了相同的概念,并删除了我对结构的引用,因为它显然与此无关。感谢您的解释,它真的很有帮助!我唯一的问题是:有没有办法让 Foo* (例如,在当前函数中通过引用传递)并将其转换为 Foo 类型,它 确实 指向同一部​​分内存为原始Foo*?或者这在C语言中是不可能的?再次感谢。
  • 你必须有FooStruct *foop = foo_ptr;,然后在函数中使用foop-&gt;x = 10;,这与在函数中使用foo_ptr-&gt;x = 10;具有相同的效果。如果要引用同一内存,请使用指针表示法。如果您制作副本 (FooStruct foo = *foo_ptr;),则根据定义,局部变量中的内存与指针远端的内存不同。
【解决方案2】:

在这个函数中:

void setX(FooStruct *foo_ptr) {
    FooStruct foo = *foo_ptr;

    if (WORK) {
        foo_ptr->x = 10;
    } else {
        foo.x = 10;
    }
}

, foo 是一个FooStruct 类型的局部变量。因此,函数对foo 执行的修改不会产生任何在函数外部可见的效果。

foo 是用 FooStruct 的内容的副本初始化的,与 foo_ptr 指向的参数无关。特别是,它不会使函数的本地 foo 成为 foo_ptr 指向的任何内容的别名。

另一方面,如果函数修改了foo_ptr 指向的对象,那么任何其他可以直接或间接看到该对象的代码当然都可以看到。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    相关资源
    最近更新 更多