gpt4 book ai didi

c - realloc()、生命周期和 UB

转载 作者:太空狗 更新时间:2023-10-29 15:03:35 24 4
gpt4 key购买 nike

最近有一个 CppCon2016 演讲 My Little Optimizer: Undefined Behavior is Magic ,它显示了以下代码(演讲进行了 26 分钟)。我美化了一下:

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

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

代码有未定义的行为(即使 realloc() 返回相同的指针,p 在 realloc() 之后变得无效)并且在编译时可能不仅打印“2 2”,而且打印“1 2”。

代码的稍微修改版本怎么样?:

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

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

为什么我仍然可以打印“1 2”?整数变量 ap 是否也以某种方式变得无效或“被污染”?如果是这样,这里的逻辑是什么? ap 不应该与 p 变得“解耦”吗?

附言添加了 C++ 标签。此代码可以简单地重写为 C++,同样的问题也适用于 C++。我对 C 和 C++ 都感兴趣。

最佳答案

如前所述,在 C 中,代码具有未定义的行为,因为 realloc 可能返回不同的内存块。在这种情况下,*(int *)ap 将形成一个无效指针。

一个更有趣的问题是,如果我们更改代码以使其仅在 realloc 未更改 block 时才尝试继续:

int* p = malloc(sizeof(int));
uintptr_t ap = (uintptr_t)p;
int* q = realloc(p, sizeof(int));

if ( (uintptr_t)q == ap )
{
*(int*)ap = 1;
// ...
}

对于 C2X,有 a proposal N2090在通过整数类型传递时指定指针出处

在当前的 C 标准中,有一些与指针起源相关的规则,但它没有说明当指针通过整数类型传递并返回时起源会发生什么。

根据这个提议,我的代码仍然是未定义的行为:ap 获得与 p 相同的出处 token ,当 block 被释放时它变成无效 token 。 (int *)ap 然后使用了一个来源无效的指针。

该提案旨在避免指针来源被 uintptr_t 等中间操作“绕过”。在这种情况下,它指定 (int *)ap 具有与 p 完全相同的行为。 (即使 block 没有移动也是未定义的,因为 prealloc 之后的无效指针,无论它是否物理移动了 block )。在 C 抽象机中,意图是无法判断该 block 是否由 realloc 移动。

指针起源的背景

“指针出处”是指指针值与它们指向的内存块之间的关联。如果指针值指向一个对象,则从该值派生的其他指针值(例如通过指针算法)必须位于该对象的范围内。

(当然,指针变量可以重新分配以指向不同的对象 - 从而获得新的出处 - 这不是我们正在谈论的)。

这不是出现在已编译的可执行文件中的东西,而是编译器可能在编译期间跟踪的东西,以便执行优化。具有不同来源的两个指针可能具有相同的内存表示(例如,在实现使用相同物理内存块的情况下,pq)。

一个简单的例子说明了为什么指针出处提供了有用的优化机会是下面的代码片段:

char p[8];
int q = 5;

*(p+10) = 123;
printf("%d\n", q);

provenance 的想法允许优化器在代码 p + 10 上注册未定义的行为,因此它可以将此代码段转换为 puts("5"),例如,即使 q 在内存中恰好紧跟在 p 之后。 (另外 - 我想知道 DJ Bernstein 的 boringcc 编译器是否真的无法执行此优化)。

关于指针边界检查的现有规则 (C11 6.5.6/8) 确实已经涵盖了这种情况,但在更复杂的情况下它们还不清楚,因此提出了 N2090 提案。例如,if ( p + 8 == (void *)&q ) *(char *)((uintptr_t)p + 10) = 123; 在 N2090 下仍然是未定义的行为。

关于c - realloc()、生命周期和 UB,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40065322/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com