int *p;
{
int x = 0;
p = &x;
}
// p is no longer valid
{
int x = 0;
if (&x == p) {
*p = 2; // Is this valid?
}
}
在指向的东西被释放后访问指针是未定义的行为,但是如果稍后的分配发生在同一区域会发生什么,并且您明确地将旧指针与指向新事物的指针进行比较?如果我在比较它们之前将&x
和p
投放到uintptr_t
,这会有什么关系吗?
(我知道它并不能保证两个x
变量占据相同的位置。我没有理由这样做,但我可以想象一个算法,你可以用一组算法可能已经使用一组绝对有效的指针释放的指针,删除了进程中的无效指针。如果先前失效的指针等于已知的良好指针,我很好奇会发生什么。)
答案 0 :(得分:13)
根据我对标准的理解(6.2.4。(2))
当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定。
比较时有未定义的行为
if (&x == p) {
因为它符合附件J.2中列出的这些要点:
- 使用指向生命周期结束的对象的指针的值(6.2.4) - 具有自动存储持续时间的对象的值在不确定时使用(6.2.4,6.7.9,6.8)。
答案 1 :(得分:6)
好吧,这似乎被解释为两个让一些人提出三个部分问题。
首先,有人担心是否会定义使用poitner进行比较。
正如评论中指出的那样,仅仅使用指针是UB,因为 $ J.2:表示使用指向生命周期结束的对象的指针是UB。
然而,如果这个障碍通过(在UB的范围内很好,它毕竟可以工作并将在许多平台上运行),这就是我发现的关于其他问题:< / p>
鉴于指针执行比较等于,代码有效:
C标准,§6.5.3.2,4:
[...]如果为指针指定了无效值,则unary *运算符的行为未定义。
虽然该位置的脚注明确说明。一个对象在其生命周期结束后的地址是一个无效的指针值,这在这里不适用,因为if
确保指针的值是x
的地址,因此是有效的。
C ++标准,§3.9.2,3:
如果类型为T的对象位于地址A,那么类型为cv T *的指针(其值为地址A)将被指向该对象,,无论该值是如何获得的。 [注意:例如,超过数组末尾的地址(5.7)将被视为指向可能位于该地址的数组元素类型的无关对象。
重点是我的。
答案 2 :(得分:1)
它可能适用于大多数编译器,但它仍然是未定义的行为。对于C语言,这些x
是两个不同的对象,一个已经结束了它的生命周期,所以你有UB。
更严重的是,有些编制者可能会以不同于您预期的方式欺骗您。
C标准说
当且仅当两者都是空指针时,两个指针才相等 是指向同一对象的指针(包括指向对象的指针) 它开头的一个子对象)或函数,都是一个指针 过去同一个数组对象的最后一个元素,或者一个是指针 一个数组对象的结尾,另一个是指向的数组 碰巧紧接着的另一个数组对象的开始 地址空间中的第一个数组对象。
特别注意短语“都是指向同一对象的指针”。在标准意义上,两个“x”不是同一个对象。它们可能碰巧在同一个内存位置实现,但这是由编译器自行决定的。由于它们显然是两个不同的对象,在不同的范围内声明,因此比较实际上永远不会成立。因此,优化器可能会完全切断该分支。
尚未讨论过的另一个方面是,其有效性取决于对象的“生命周期”而不是范围。如果您要添加可能的跳转到该范围
{
int x = 0;
p = &x;
BLURB: ;
}
...
if (...)
...
if (something) goto BLURB;
只要第一个x
的范围可以到达,生命周期就会延长。然后一切都是有效的行为,但你的测试仍然是假的,并由一个体面的编译器进行优化。
从你看到的所有内容中,你最好将其留在UB的争论中,并且不要在真实的代码中玩这样的游戏。
答案 3 :(得分:0)
如果通过工作你使用一个非常自由的定义,大致相当于它不会崩溃的那样,它会起作用。
然而,这是一个坏主意。我无法想象为什么更容易交叉手指,并希望两个局部变量存储在相同的内存地址中,而不是再次写入p=&x
。如果这只是一个学术问题,那么它是有效的C - 但if语句是否真实并不保证在不同平台甚至不同程序中保持一致。
编辑:要清楚,未定义的行为是第二个块中的&x == p
。 p
的值不会改变,它仍然是指向该地址的指针,该地址不再属于您。现在编译器可能(可能会)将第二个x
放在同一个地址(假设没有任何其他干预代码)。如果这恰好是真的,那么就像p
一样取消引用&x
是完全合法的,只要它的类型是指向int或更小的指针。就像说p = 0x00000042; if (p == &x) {*p = whatever;}
一样合法。
答案 4 :(得分:0)
行为未定义。但是,你的问题让我想起了另一个采用了类似概念的案例。在提到的情况下,由于它们的优先级,存在这些线程将获得不同数量的cpu时间。因此,线程1将获得更多时间,因为线程2正在等待I / O或其他东西。完成其工作后,线程1会将值写入内存以供线程2使用。这不是以受控方式“共享”内存。它会写入调用堆栈本身。线程2中的变量将被分配内存。现在,当线程2最终完成执行时,所有声明的变量都不必分配值,因为它们占用的位置具有有效值。如果在这个过程中出现问题,我不知道他们做了什么,但这是我见过的C代码中最神奇的优化之一。
答案 5 :(得分:0)
如果它有效(并且我现在确信它不是,请参阅Arne Mertz的回答),请抛开这个事实。我仍然认为它是学术性的。
您正在考虑的算法不会产生非常有用的结果,因为您只能比较两个指针,但您没有机会确定这些指针是指向同一类型的对象还是完全不同的对象。指向struct
的指针现在可以是单个char
的地址。
答案 6 :(得分:0)
此undefined behavior contest中的获胜者#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开发人员认为此示例和您的示例表现出未定义的行为时,才能解释这一点。