问题描述
是的,我很清楚你不应该那样做。如果我们有这个代码:
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
将被允许打印不同的值)。 (无论哪种方式,它都不是未定义的行为,除了陷阱表示的可能性。)