返回指向局部变量的指针是UB吗?

问题描述

是的,我很清楚你不应该那样做。如果我们有这个代码

int *foo() {
    int a = 42;
    return &a;
}

正如大多数 C 程序员所知,这是未定义的行为:Using pointer after free()

int *p = foo();
printf("%d\n",*p);

只是为了让未来的读者不要把这当成事实。下面的所有内容,直到问题都是基于我的错误假设。这不是UB。简直糟透了。

正如一些 C 编码人员所知道的,但比上面的要少,这也是 UB,即使没有取消引用:(试图找到一个关于这个的好问题,但没有找到任何人)

int *p = foo();
printf("%p\n",p); // Should be printf("%p\n",(void*) p);
                   // Eric P clarified this in his answer,but this missing cast
                   // is not a part of the primary question

出于同样的原因,这也是 UB,因为发生的情况是指针变得不确定,就像未初始化的变量一样。所谓的悬空指针。

int *p = foo();
int *q = p;

有些人有些惊讶,即使这样也不行:

free(ptr);
if(ptr == NULL) // Just as bad as if ptr is not initialized

问题

我确实想知道,这一行是否也调用了 UB:

int *p = foo();

或者甚至这个?

foo();

换句话说,p 会成为一个悬空指针,还是被赋值一个悬空指针?

我不知道这是否有任何实际用途,除了对 C 语言有更深入的了解。一个很好的用例是找出哪些重构是紧急的,哪些可以等待。

解决方法

C 2018 6.2.4 说“……当指针指向(或刚刚过去)的对象到达其生命周期结束时,指针的值变得不确定。” 3.19.2 告诉我们不确定的值是“未指定的值或陷阱表示”。 3.19.3 告诉我们一个未指定的值是“相关类型的有效值,本文档对在任何情况下选择哪个值没有强加要求”(意味着每次使用时该值可能看起来不同,即使没有对其进行了明显的更改)。

因此:

int *p = foo();
printf("%p\n",(void *) p); // "(void *)" added to pass correct type for %p.

我们不知道为 p 打印什么值。如果 C 实现没有指针的陷阱表示,则其不确定值不能是陷阱表示,因此不存在未定义的行为。但是,根据 3.19.3,它是一些有效值。

此回答不涉及帖子中的其他问题,例如:

int *p = foo();
int *q = p;

分配给 q 一些一旦分配完成就固定的值(因此重复打印 q 总是打印相同的值)或分配给 q 名义上的“不确定值”(因此重复打印 q 将被允许打印不同的值)。 (无论哪种方式,它都不是未定义的行为,除了陷阱表示的可能性。)