超出有效内存范围的指针算术会产生啥危害?

Posted

技术标签:

【中文标题】超出有效内存范围的指针算术会产生啥危害?【英文标题】:What harm would arise by pointer arithmetic beyond a valid memory range?超出有效内存范围的指针算术会产生什么危害? 【发布时间】:2014-04-23 07:24:47 【问题描述】:

我关注了One-byte-off pointer still valid in C?的讨论。

据我所知,该讨论的要点是,如果您有:

char *p = malloc(4);

那么就可以通过使用指针算法来获得指向p+4 的指针。如果使用p+5 获取指针,则行为未定义。

我明白为什么取消引用 p+5 会导致未定义的行为。但是只使用指针算法的未定义行为?

为什么算术运算符+- 不是有效运算?我认为从指针中添加或减去数字没有任何害处。毕竟,指针是用一个数字来表示的,它捕获了一个对象的地址。

当然,我不是标准化委员会的成员 :) 我不参与他们在编纂标准之前的讨论。我只是好奇。任何见解都会很有用。

【问题讨论】:

在您的示例中取消引用 p+4 也可能导致未定义的行为。 如果这是正确的(我不确定它是否正确),我的猜测是他们试图允许可能想要检测指针数学错误的架构和环境它发生在哪里,而不是在它被使用的地方。同样,未定义,这意味着没有承诺会失败……只是没有承诺会成功。 @user3277173 您仍然可以与 p+4 进行比较。 @pat,该标准保证计算 p+4 是一个有效的操作。取消引用p+4 显然不是。在p:p+4 范围内执行算术运算也保证成功。 @user3277173:您通常与过去的指针进行比较以终止循环。 (例如iter != foo.end())。 one-past-the-end 指针的合法性是专门允许这种习惯用法的。 【参考方案1】:

最简单的答案是机器捕获整数溢出是可以想象的。如果是这种情况,那么任何不限于单个存储区域的指针运算都可能导致溢出,从而导致陷阱,从而中断程序的执行。 C 不应该在尝试指针运算之前检查可能的溢出,因此标准允许在这种机器上的 C 实现只允许陷阱发生,即使随后发生混乱。

另一种情况是内存被分段的架构,因此指针由段地址(带有隐式尾随 0)和偏移量组成。任何给定的对象都必须适合单个段,这意味着有效的指针算法只能在偏移量上工作。同样,在指针运算过程中溢出偏移量可能会产生随机结果,C 实现没有义务对此进行检查。

最后,编译器很可能会在所有指针算法都有效的假设下产生优化。作为一个简单的激励案例:

if (iter - 1 < object.end()) ...

这里可以省略测试,因为对于任何指针iter,其值是object 中(或之后)的有效位置,它必须为真。无效指针算法的 UB 意味着编译器没有任何义务试图证明 iter 是有效的(尽管它可能需要确保它基于指向 object 的指针),所以它可以直接删除比较并继续生成无条件代码。一些编译器可能会做这种事情,所以要小心:)

顺便说一下,这里是unspecified 行为和undefined 行为之间的重要区别。将两个指针(相同类型)与== 进行比较是已定义,无论它们是否是指向同一对象的指针。特别是,如果ab 是相同类型的两个不同对象,则end_a 是指向a 的过去的指针,begin_b 是指向b 的指针,那么

end_a == begin_b

未指定;它将是 1 当且仅当 b 恰好在内存中的 a 之后,否则为 0。由于您通常不能依靠知道这一点(除非ab 是同一数组的数组元素),因此比较通常是没有意义的; 但这不是未定义的行为,编译器需要安排生成 01(此外,为了使相同的比较始终具有相同的值,因为您可以依赖关于不在内存中移动的对象。)

【讨论】:

【参考方案2】:

对指针执行基本的 +/- 算术不会导致问题。指针值的顺序是连续的:&amp;p[0] &lt; &amp;p[1] &lt; ... &amp;p[n] 用于类型 n 对象 long。但未定义超出此范围的指针算术。 &amp;p[-1] 可能小于或大于 &amp;p[0]

int *p = malloc(80 * sizeof *p);
int *q = p + 1000;
printf("p:%p q:%p\n", p, q);

取消引用超出其范围甚至内存范围内的指针,但未对齐是一个问题。

printf("*p:%d\n", *p);  // OK
printf("*p:%d\n", p[79]);  // OK
printf("*p:%d\n", p[80]);  // Bad, but &p[80] will be greater than &p[79]
printf("*p:%d\n", p[-1]);  // Bad, order of p, p[-1] is not defined 
printf("*p:%d\n", p[81]);  // Bad, order of p[80], p[81] is not defined
char *r = (char*) p;
printf("*p:%d\n", *((int*) (r + 1)) );  // Bad
printf("*p:%d\n", *q);  // Bad

问:为什么p[81] 的行为未定义? 答:示例:内存运行0N-1char *p 的值为 N-81p[0]p[79] 定义明确。 p[80] 也定义明确。 p[81] 需要与 N 的值保持一致,但这会溢出,因此 p[81] 的值可能为 0、N 或谁知道。

【讨论】:

OP 是正确的,越界执行指针算术会调用未定义的行为。它在大多数(如果不是全部)平台上都按预期运行这一事实并没有改变这一点。【参考方案3】:

我能想到+- 的结果可能会产生意外结果的一种情况是上溢或下溢。

您提到的问题指出,对于p = malloc(4),您可以使用p+4 进行比较。这需要保证的一件事是p+4 不会溢出。它不能保证p+5 不会溢出。

也就是说+- 本身不会引起任何问题,但有可能,无论多么小,它们都会返回一个不适合比较的值。

【讨论】:

【参考方案4】:

这里有几件事,p+4 在这种情况下有效的原因是因为迭代到最后一个位置之后是有效的。

p+5 理论上不会有问题,但在我看来,问题在于您何时尝试取消引用 (p+5) 或者您可能会尝试覆盖该地址。

【讨论】:

我了解取消引用 p+5 的部分。问题是为什么计算 p+5 会导致未定义的行为。 你是说不能 printf, p+5 吗? p+5, p+10, p+12;都是有效的操作。您可以继续打印任意数量的地址。唯一的问题是指那里的价值观。或者甚至在某些情况下该地址不存在。 不,我不是。由于根据标准未定义行为,因此编译器可以选择正常行为或其他方式。 由于这个原因,它是未定义的,您可能会取消引用它或覆盖它。您刚刚为四个字符分配了内存,然后他们希望您只使用该内存。如果您想要 5 个字符的内存,请为 5 个字符分配内存。

以上是关于超出有效内存范围的指针算术会产生啥危害?的主要内容,如果未能解决你的问题,请参考以下文章

当函数返回时,指向超出范围的对象的 C++ 指针 - 为啥会这样?

当变量超出范围时会发生啥?

当 Model 超出使用范围时,实际会发生啥?

为啥共享指针在 main 末尾没有超出范围?

如何使智能指针在exit()时超出范围

C如何检查内存地址是不是仍在范围内[关闭]