【问题标题】:Object is still reachable after leaving the function离开函数后对象仍然可以访问
【发布时间】:2021-04-29 18:17:59
【问题描述】:
#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”指向0x7fffffffdcdc,它不在$rsp 和$rbp 之间。 最后程序打印“10”,没有任何错误信息。 我不明白为什么它可以工作。

【问题讨论】:

  • 程序有未定义的行为。您可以创建多种具有未定义行为的程序,它们将按预期工作。还要注意函数参数x没有使用。
  • 参考:C11 6.5.2.5p5:“[...] 复合文字 [...] 具有与封闭块关联的自动存储持续时间。”
  • 从语言的角度来看,程序具有未定义的行为,未定义行为的可观察效果可以是任何东西,包括程序按预期工作的外观。从实际目的来看,在这么短的程序中,用于保存现已失效对象的内存在对象被销毁后并没有被重用,因此您仍然可以查看该内存并找到您期望的数据。尝试在copy 之后但在打印值之前调用另一个不相关的函数。
  • 我使用gcc -O0 -g -std=c99 -Wall -Werror -fsanitize=undefined main.c 来检测未定义的行为,但标准输出为空。
  • 静态分析不会捕获所有未定义行为的实例 - 这需要更深入的分析。碰巧-O3 会执行如此深入的分析,并且如果您设置问题被捕获。如果您想将编译器用作穷人的静态分析工具,请始终使用高优化级别。 node y = {.val = 10} ; return &amp;y; 等更简单的成语在-O0 中被捕获。在-O3 它被没有 sanitize 捕获。优化器是比sanitize 更好的静态分析工具(至少在这方面)。

标签: c pointers struct lifecycle


【解决方案1】:

当一个对象超出范围时,你不能再通过它的符号名来访问它。

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

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

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

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

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

【讨论】:

  • 是的,现在我可以看到不同的输出了。
猜你喜欢
  • 1970-01-01
  • 2014-06-23
  • 1970-01-01
  • 2012-05-07
  • 2013-07-11
  • 2011-05-23
  • 2014-03-05
  • 2021-10-17
  • 2016-09-24
相关资源
最近更新 更多