无效指针再次变为有效

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_tuintptr_t会有什么关系吗?

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

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

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

比较时你有未定义的行为

 if (&x == p) { 

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

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

好吧,这似乎被解释为一个由两个人组成的问题。

首先,有人担心是否使用poitner进行比较。

正如在注释中指出的那样,仅仅使用指针是UB,因为$ J.2:表示使用指向生命周期结束的对象的指针是UB。

但是,如果这个障碍通过(在UB的范围内很好,它可以在许多平台上运行),这就是我发现的其他问题:

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

C标准,§6.5.3.2,4

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

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

C ++标准,§3.9.2,3:

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

重点是我的。

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

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

C标准说

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

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

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

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

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

从你看到的所有内容中,你最好将其留在UB的争论中,并且不要在实际代码中玩这样的游戏。

它可以工作,如果你按照工作使用非常自由的定义,大致相当于它不会崩溃。

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

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

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

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

 #include  #include  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); } 

根据post:

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

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

1 2

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

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

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