【问题标题】:Printing Dangling Pointers in C在 C 中打印悬空指针
【发布时间】:2021-09-14 20:22:23
【问题描述】:
#include <stdio.h>

int main()
{
    int *ptr;
    {
        int x = 2;
        ptr = &x;
    }

    printf("%x %d", ptr, *ptr);
    return 0;
}

输出:x的地址,x的值。

这里,ptr 应该是一个悬空指针,对吧?但是,它仍然存储x 的地址。 即使删除了那个块,它怎么仍然指向x 的值?

#include <stdio.h>

int * func (int n)
{
    int temp;
    int *ptr = &temp;
    temp = n * n;
    return ptr;
}

int main()
{
    int n = 4;
    int *p = func(4);
    printf("%x, %d", p, *p);
    return 0;
}

输出:临时地址,16

在这个程序中,数据变量temp和它的指针变量ptr是在单独的函数中创建的。 为什么会产生正确的结果?

#include <stdio.h>

int * func (int n)
{
    int temp;
    int *ptr = &temp;
    temp = n * n;

    for (int i = 0; i < 10; i++)
        printf("%d ", *ptr);
    return ptr;
}
    
int main()
{
    int n = 4;
    int *p = func(4);
    printf("\n%x, %d", p, *p);
    for (int i = 0; i < 10; i++)
        printf("%d ", *ptr);
    *p = 12;
    printf("%d\n", *p);
    printf("%d\n", *p);
    return 0;
}

输出:16 16 16 16 16 16 16 16 16 16
临时地址,1
16 16 16 16 16 16 16 16 16 16
12
12

除了for循环之外,上面的程序与第二个程序类似。在 main() 函数,它每次都给出正确的输出。即使我尝试将其更改为*p = 10,无论我打印多少次,它仍然会给出正确的输出。

但是在第二个程序中,由于未定义的行为,它只给出了一次正确的输出。它在第一个 printf 之后给出垃圾值。

但是在第三个程序中,它如何仍然每次都给出正确的输出?

我的问题是:

  1. 指针变量指向一个超出范围的局部变量,但仍会打印正确的输出,并且可以通过更改指针变量的值来访问它。为什么会这样?
  2. 与在 increment() 中创建的 temp 一样,ptr 也是在本地创建的。为什么它始终正确打印值而没有任何警告或错误?如果没有for循环,打印一次后也会报错。为什么会这样?
    当我通过 temp 时,我收到警告和分段错误错误。但是为什么 ptr 是一个局部变量,可以正确打印值呢?
  3. 在第一个程序中,多次打印 *ptr 后,它给出了正确的输出,我能够更改 *ptr = 1;在第一个 printf 之后。为什么即使变量超出范围,我也可以访问 ptr?

谢谢大家的回答。我现在从你所有的答案中都明白了。非常感谢。

【问题讨论】:

  • 您正在引入未定义的行为
  • 澄清@INDHUJA G 是这里的新海报,对C 来说可能相当新:未定义的行为意味着您的代码可能 可以工作,但可能不行。如果我们确实知道,那将是定义的行为。最好完全避免这种情况。
  • @Chris 和 Daniel。谢谢您的回答。它应该只给出未定义的行为。但是在我现在添加的第一个和第三个程序中,看起来像是定义的行为。这怎么可能?

标签: c dangling-pointer


【解决方案1】:

你的两个程序行为都是未定义的。

在第一个代码中,您的程序正在通过其地址访问x,该地址位于声明它的块之外。 x 是一个本地(自动)非静态变量,它的生命周期仅限于其范围1),即声明它的块。任何在其生命周期之外访问它的尝试都将导致未定义的行为2)。第二个代码中的temp 变量也是如此。

undefined behaviour 包含它可能执行不正确(崩溃或静默生成不正确的结果),或者它可能偶然地完全按照程序员的意图执行。

另外,打印指针的正确格式说明符是%p


1)。来自 C11 Standard#6.2.1p4 [强调我的]

每个其他标识符的范围由其声明的位置决定(在声明符或类型说明符中)。如果声明标识符的声明符或类型说明符出现在任何块或参数列表之外,则标识符具有文件范围,该范围在翻译单元的末尾终止。 如果声明标识符的声明符或类型说明符出现在块内或函数定义中的参数声明列表内,则标识符具有块范围,终止于关联块的末尾。 ......

2)。来自 C11 Standard#6.2.4p2 [强调我的]

2 对象的生命周期是程序执行过程中保证为其保留存储空间的部分。一个对象存在,具有一个常量地址,33) 并在其生命周期内保留其最后存储的值。34) 如果对象在其生命周期之外被引用,则行为未定义。当指针指向(或刚刚过去)的对象到达其生命周期结束时,指针变得不确定。

【讨论】:

  • 感谢您的回答。我明白了。我编辑了我的问题。请告诉我?
【解决方案2】:

我已经用 IDA 反汇编了你的第三个程序。
func() 函数编译为 main() 函数的一部分,而不是编译为独立函数。

因此,保留了正确的值。
我猜这是编译时的优化结果。

但是,当我在 func() 中添加一行时,程序的结果是不同的。

在这种情况下,编译器将“func()”识别为函数。
出现了预期的结果,程序在 '*p = 12' 处崩溃。

【讨论】:

  • 非常感谢您抽出宝贵的时间回答我的问题
【解决方案3】:

第一个代码中的'x'和第二个代码中的'temp'是一个局部变量,因此当变量超出定义的块时,它会从堆栈中释放。

'ptr'和'p'是指向这些局部变量地址的指针,但是这些指针中存储的值在局部变量出栈后是无效的。

局部变量释放后,值是否留在内存中,是开发工具和环境的问题。也就是释放栈,然后清空占用局部变量的指针的内存,是在OS或者编译器内部处理的,关键是你不能再使用那个地址的值valid了。

当我查看VC++ 2008时,局部变量释放后,指针没有更多的有效值。它具有随机值。

【讨论】:

  • 感谢您的回答。是的,这是未定义的行为。但是在第一个和第二个程序中,它就像定义的行为一样。我有一个编辑问题。请回答它?
猜你喜欢
  • 2023-03-12
  • 2011-07-14
  • 2020-01-15
  • 2018-12-22
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
相关资源
最近更新 更多