26
int *p;
{
    int x = 0;
    p = &x;
}
// p is no longer valid
{
    int x = 0;
    if (&x == p) {
        *p = 2;  // Is this valid?
    }
}

在指针指向的东西被释放后访问指针是未定义的行为,但是如果稍后在同一区域中发生一些分配,并且您明确地将旧指针与指向新事物的指针进行比较,会发生什么?如果我在比较它们之前先转换&xp转换有关系吗?uintptr_t

(我知道不能保证这两个x变量占据同一个位置。我没有理由这样做,但我可以想象一个算法,你可以将一组指针与一组绝对有效的指针相交指针,删除进程中的无效指针。如果先前无效的指针等于已知的好指针,我很好奇会发生什么。)

4

7 回答 7

12

根据我对标准的理解 (6.2.4. (2))

当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定。

比较时您有未定义的行为

if (&x == p) {

因为这符合附件 J.2 中列出的这些要点:

— 使用指向生命周期已结束的对象的指针的值 (6.2.4)。
— 具有自动存储持续时间的对象的值在不确定时使用(6.2.4、6.7.9、6.8)。

于 2013-08-22T15:14:21.743 回答
5

好的,这似乎被某些人解释为一个二分三部分的问题。

首先,有人担心是否完全定义了使用指针进行比较。

正如评论中所指出的,仅使用指针就是 UB,因为$J.2:表示使用指向生命周期已结束的对象的指针是 UB。

但是,如果克服了这个障碍(这在 UB 的范围内,它毕竟可以工作并且可以在许多平台上工作),这是我发现的其他问题:

鉴于指针确实比较相等,代码是有效的:

C 标准,§6.5.3.2,4

[...] 如果已为指针分配了无效值,则一元 * 运算符的行为未定义。

尽管该位置的脚注明确指出。对象在其生命周期结束后的地址是无效的指针值,这在这里不适用,因为if确保指针的值是地址,x因此是有效的。

C++ 标准,§3.9.2,3:

如果一个类型 T 的对象位于地址 A,则称其值为地址 A 的类型为 cv T* 的指针指向该对象,而不管该值是如何获得的。[注意:例如,数组末尾的地址(5.7)将被视为指向可能位于该地址的数组元素类型的不相关对象。

重点是我的。

于 2013-08-22T15:03:31.500 回答
1

它可能适用于大多数编译器,但它仍然是未定义的行为。对于 C 语言,这x是两个不同的对象,一个已经结束了它的生命周期,所以你有 UB。

更严重的是,一些编译器可能会决定以与您预期不同的方式欺骗​​您。

C标准说

两个指针比较相等当且仅当两者都是空指针,两者都是指向同一个对象(包括指向对象的指针和在其开头的子对象)或函数,两者都是指向同一数组最后一个元素之后的指针对象,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧随地址空间中的第一个数组对象。

特别注意短语“两者都是指向同一对象的指针”。在标准意义上,两个“x”不是同一个对象。它们可能碰巧在相同的内存位置实现,但这由编译器决定。由于它们显然是两个不同的对象,在不同的范围内声明,因此比较实际上不应该是真的。因此,优化器可能会完全切断该分支。

尚未讨论的另一个方面是它的有效性取决于对象的“生命周期”而不是范围。如果您要在该范围内添加可能的跳转

{
    int x = 0;
    p = &x;
  BLURB: ;
}
...
if (...)
...
if (something) goto BLURB;

只要第一个的范围x是可达的,生命周期就会延长。那么一切都是有效的行为,但你的测试仍然总是错误的,并由一个体面的编译器优化。

从你所看到的一切来看,你最好把它留在 UB 的争论中,并且不要在真实代码中玩这样的游戏。

于 2013-08-22T15:15:38.597 回答
0

行为未定义。但是,您的问题让我想起了另一个使用类似概念的案例。在提到的情况下,这些线程会因为它们的优先级而获得不同数量的 cpu 时间。因此,线程 1 将获得更多时间,因为线程 2 正在等待 I/O 或其他东西。完成工作后,线程 1 会将值写入内存以供线程 2 使用。这不是以受控方式“共享”内存。它会写入调用堆栈本身。线程 2 中的变量将被分配内存。现在,当线程 2 最终开始执行时,它所有声明的变量将永远不必被赋值,因为它们占用的位置具有有效值。我不

于 2013-08-22T15:10:37.250 回答
0

它会起作用,如果你通过工作使用一个非常自由的定义,大致相当于它不会崩溃。

然而,这是一个坏主意。我无法想象为什么更容易交叉手指并希望两个局部变量存储在同一个内存地址中而不是p=&x再次写入的单一原因。如果这只是一个学术问题,那么是的,它是有效的 C - 但是不能保证 if 语句是否正确在平台甚至不同程序之间是一致的。

编辑:要清楚,未定义的行为是是否&x == p在第二个块中。的值p不会改变,它仍然是指向那个地址的指针,那个地址不再属于你了。现在编译器可能(可能会)将第二个x放在同一个地址(假设没有任何其他中间代码)。如果这恰好是真的,那么p像你一样取消引用是完全合法的&x,只要它的类型是指向 int 或更小的指针。就像说是合法的一样p = 0x00000042; if (p == &x) {*p = whatever;}

于 2013-08-22T14:55:23.627 回答
0

如果它有效(我现在确信它不是,请参阅 Arne Mertz 的回答),撇开事实不谈,我仍然认为它是学术性的。

您正在考虑的算法不会产生非常有用的结果,因为您只能比较两个指针,但您没有机会确定这些指针是指向同一种对象还是完全不同的对象。例如,指向 a 的指针struct现在可以是单个的地址char

于 2013-08-22T15:14:25.863 回答
0

此未定义行为竞赛中的获胜者 #2与您的代码非常相似:

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *p = (int*)malloc(sizeof(int));
  int *q = (int*)realloc(p, sizeof(int));
  *p = 1;
  *q = 2;
  if (p == q)
    printf("%d %d\n", *p, *q);
}

根据帖子:

使用最新版本的 Clang(Linux 上 x86-64 的 r160635):

$ clang -O realloc.c ; ./a.out

1 2

只有当 Clang 开发人员认为这个示例和您的示例表现出未定义的行为时,才能解释这一点。

于 2013-08-22T15:15:13.833 回答