首页 > 解决方案 > 关于数组外指针算术的 C 标准

问题描述

我阅读了很多关于指针算法和未定义行为(链接链接链接链接链接)的内容。它总是得出相同的结论:指针算法仅在数组类型上以及在数组[0] 和数组[array_size+1] 之间定义良好(对于 C 标准来说,末尾的一个元素是有效的)。

我的问题是:这是否意味着当编译器看到一个与任何数组无关的指针算术(未定义的行为)时,它可以发出它想要的东西(甚至什么都没有)?还是更高级的“未定义行为”意味着您可以访问未映射的内存、垃圾数据等,并且不能保证地址的有效性?

在这个例子中:

char test[10];
char * ptr = &test[0];
printf("test[-1] : %d", *(ptr-1))

通过“未定义的行为”,只是该值根本不能保证(可能是垃圾、未映射的内存等),但我们仍然可以肯定地说,我们正在访问与数组连续 8 个字节的内存地址开始前? 还是编译器根本无法发出此代码的“未定义行为”?

另一个简单的用例:您想计算一个函数的内存大小。一个幼稚的实现可能是下面的代码,假设函数以相同的顺序在二进制文件中输出,是连续的并且中间没有任何填充。

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

void func1()
{}

void func2()
{}

int main()
{
  uint8_t * ptr1 = (uint8_t*) &func1;
  uint8_t * ptr2 = (uint8_t*) &func2;

  printf("Func 1 size : %ld", ptr2-ptr1);

  return 0;
}

由于ptr1andptr2不是数组的一部分,它被认为是未定义的行为。同样,这是否意味着编译器无法发出这些代码?或者“未定义的行为”是否意味着减法根据系统(内存中不连续的函数、填充等)没有意义,但仍按预期发生?有没有明确定义的方法来计算两个不相关的指针之间的减法?

标签: cpointersmathstandardsundefined-behavior

解决方案


C 标准没有定义未定义行为的未定义程度。如果它是未定义的,它总是所有的赌注都是关闭的。

此外,现代编译器会混淆这个指针出处,编译器甚至会观察一个可能有效的指针是否正确派生,如果不是,它可以调整程序行为。

如果您想要没有 UB 可能性的数学指针算术,您可以尝试uintptr_t在进行数学运算之前将指针转换为。


例如:

#include <stdio.h>
int main()
{
    char a,b;
    printf("&a=%p\n", &a);
    printf("&b=%p\n", &b);
    printf("&a+1=%p\n", &a+1);
    printf("&b+1=%p\n", &b+1);
    printf("%d\n", &a+1==&b || &b+1==&a);
}

在我的机器上,用 编译gcc -O2,结果:

&a=0x7ffee4e36cae
&b=0x7ffee4e36caf
&a+1=0x7ffee4e36caf
&b+1=0x7ffee4e36cb0
0

即,&a+1具有相同的数字地址,&b但被视为不等于,&b因为地址来自不同的对象。

(这个 gcc 优化有点争议。它不跨越函数调用/翻译单元边界,clang 不这样做,而且没有必要,因为6.5.9p6确实允许意外的指针相等。请参阅dbush对这个Keith汤普森的 回答了解更多细节。)


推荐阅读