【问题标题】:Pointer to a local variable survives指向局部变量的指针仍然存在
【发布时间】:2017-11-23 11:11:49
【问题描述】:

我想问一个简单的问题。请考虑随附的代码。在 main 函数中,指向结构的指针通过 ctor1 或 ctor 2 以两种不同的方式构建。在这两种情况下,无论我使用哪个构造函数,程序都可以正常工作。

ctor1 工作的原因是结构实例的内存分配在函数框架之外(即在堆中)。因此,在ctor1终止后,它将在main函数中可用。

我的问题归结为 ctor2 函数。据我所知,局部变量“myPtr foo”预计会在函数结束时被销毁。因此,从现在开始,“那个”指针应该不指向任何东西。然而,执行完程序后,我发现两个构造函数都完美无缺。

显然,有一个微妙的细节让我无法理解。你能解释一下为什么函数 ctor2 有效吗? 提前谢谢!

#include <stdio.h>
#include <malloc.h>

int _method(void) {

    return 0;
}//_foo

typedef struct vTable {
    int (*method)(void);
} myPtr;

myPtr *ctor1(void) {
myPtr *foo;

    foo = (myPtr*)malloc(1 * sizeof(myPtr));
    foo->method = &_method;

    return foo;
}//ctor1

void ctor2(myPtr *that) {
myPtr foo = { &_method };

    that = &foo;

    return;
// having reached the function end "foo" is destroyed
// and "that" should point to nothing, supposedly
}//ctor2

int dtor(myPtr *foo) {

    free(foo);
    foo->method = NULL;
    foo = NULL;

    return 0;
}//dtor

int main(void) {
myPtr *vPtr;

    // it works as expected
    vPtr = ctor1();
    printf("%p\n\n", vPtr); // 003E0F68
    dtor(vPtr);

    // it works surprisingly enough
    ctor2(vPtr);
    printf("%p\n", vPtr); // 003E0F68
    printf("%p\n", vPtr); // 003E0F68
    // it keeps on working
    printf("%p\n", vPtr); // 003E0F68
    dtor(vPtr);

    return 0;
}//main

Screenshot

【问题讨论】:

  • 使用指向已销毁对象的指针是未定义的行为。如果它看起来有效,那只是偶然,可能是因为内存还没有被重用。
  • ctor2 函数不起作用,原因之一不是指向局部变量:您将指针传递给函数按值我>。您只需修改函数内部的 局部变量 thatmain函数中的vPtr变量根本没有被修改。
  • free(foo); foo-&gt;method = NULL; 导致未定义的行为。释放对象后不能尝试修改它
  • 感谢您的回复。 Quantum Leaps 在本文第 8 页中使用了类似的技术。state-machine.com/doc/AN_OOP_in_C.pdf 我想知道它是否合法。
  • @user3633207:该文章中的代码传递了一个指向包含vptr 的结构的指针,然后对其进行修改。在您指向的对象中为vptr 赋值会更改对象的vptr 成员。您的代码在参数中传递了vptr。更改参数只会更改参数,它是传递值的副本

标签: c pointers


【解决方案1】:

代码void ctor2(myPtr *that)that 声明为指向myPtr 类型对象的参数。参数是按值传递的,因此参数that 只是传递的任何内容的副本。更改 that 不会更改已通过的内容。

如果你想改变指向myPtr的指针的值,你必须传递一个指向myPtr的指针:

void ctor2(myPtr **that)

然后你可以改变它:

*that = malloc(…);

【讨论】:

    【解决方案2】:

    这里有几个问题,让我们一一解决。

    首先,在您的ctor2 函数中:

    void ctor2(myPtr *that) {
        myPtr foo = { &_method };
        that = &foo;
        return;
    }//ctor2
    

    这个函数实际上是通过值获取指向 myPtr 的指针,并在本地修改它以指向函数中堆栈上分配的内容。这对传入的指针有影响。如果你想修改传入的指针,你会传入一个双指针并取消引用它:

    void ctor2(myPtr **that) {
        //malloc foo
        *that = foo;
        return;
    }
    

    其次,因为您从未通过调用 ctor2 修改 vPtr,所以对 dtor 的第二次调用正在释放已释放的内存,这是未定义的行为,通常会导致崩溃。我很惊讶它没有在你的系统上崩溃,但这就是 UB 的问题,你永远不知道。

    第三,你想要模仿的行为是:

    /* constructor */
    void Shape_ctor(Shape * const me, int16_t x, int16_t y) {
      static struct ShapeVtbl const vtbl = { /* vtbl of the Shape class */
        &Shape_area_,
        &Shape_draw_
      };
      me->vptr = &vtbl; /* "hook" the vptr to the vtbl */
      me->x = x;
      me->y = y;
    }
    

    不同之处在于,在这种情况下,ShapeVtbl 结构是静态分配的。这没关系,因为它只指向函数,不会从对象实例更改为对象实例。但是静态分配它允许它在类似的函数中分配并分配给对象。

    【讨论】:

    • 谢谢!这个纲要确实很有帮助。
    【解决方案3】:

    为了放大 Eric Postpischil 的出色 answer,考虑一下,

    void foo( int i ) { i++; }
    

    这个函数改变了它被传递的值,但不是那个值是它被调用时的原始值的副本,

    int j=8; 
    foo(j);
    

    你不会指望j 会改变,对吧?

    void ctor2(myPtr *that) { // my version
        that = NULL;
    }
    

    that 是调用时传递的参数的副本,

    ctor2(vPtr);
    

    因为vPtr 没有改变,所以你的程序会打印出……它没有改变的值。

    ctor2 可以更改that 所指向的任何内容的值,但对参数本身的任何更改都只会产生局部影响。

    正如其他答案所指出的,您的程序中还有其他错误。但是为什么ctor2“有效”的答案基本上是它什么都不做。

    【讨论】:

      猜你喜欢
      • 2014-05-18
      • 2016-10-25
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 2020-10-13
      • 1970-01-01
      相关资源
      最近更新 更多