离开函数后对象仍然可达

问题描述

#include <stdio.h>

typedef struct node {
  int val;
} node;

node *copy(node *x) {
  node *tmp = &((node){.val = 10});                                             
  return tmp;
}

int main() {
  // node *a = &((node){.val = 9});
  node *a = 0;
  a = copy(a);
  printf("%d\n",a->val);
  return 0;
}

据我所知,函数堆栈中的每个对象在离开函数后都将无法访问。 然而,它没有。

我使用 GDB 来检查对象在哪里。

(gdb) b 9
(gdb) b 16
(gdb) r
(gdb) p $rsp
$8 = (void *) 0x7fffffffdcc0
(gdb) p $rbp
$9 = (void *) 0x7fffffffdcf0
(gdb) p tmp
$7 = (node *) 0x7fffffffdcdc
(gdb) c
(gdb) p $rsp
$10 = (void *) 0x7fffffffdd00
(gdb) p $rbp
$11 = (void *) 0x7fffffffdd10
(gdb) p a
$12 = (node *) 0x7fffffffdcdc

您可以看到变量“a”指向不在 $rsp 和 $rbp 之间的 0x7fffffffdcdc。 最后,程序打印“10”而没有任何错误信息。 我不明白为什么它可以工作。

解决方法

当一个对象超出范围时,您将无法再通过其符号名称访问它。

如果您获取指向对象的指针(内存地址)并按原样返回,则该指针指向对象曾经存在过的内存。当对象超出范围时,该内存内容不会发生任何具体变化 - 它只是可用于其他目的。所以如果那个内存还没有被重用,那么对象占用的内存没有改变也就不足为奇了。

您正在观察一个物体的幽灵。它不会存活很长时间。例如,如果您这样做:

  printf("%d\n",a->val);
  printf("%d\n",a->val);

很可能会发现在第二次 printf() 调用时它已被修改,因为 printf() 调用使用了先前由 {{1} 占用的堆栈空间} 复制。

严格来说,这是未定义行为,任何事情都可能发生。在实践中,我所描述的是“典型”——如果你发生了不同的事情,那么耸耸肩,其他事情也是可能的。