【问题标题】:adding integer to null pointer address returning unexpected result将整数添加到空指针地址返回意外结果
【发布时间】:2021-02-17 00:50:19
【问题描述】:
#include <stdio.h>

int main() {
  int *numPtrA = NULL;         // pointer to an integer pointer to null.. ascii value of zero
 
  printf("%p\n", numPtrA + 2); // prints 0x8 which i expect
  printf("%p\n", numPtrA + 5); // prints 0x14 and i dont know why
}

enter image description here

我正在尝试通过在空指针的 ASCII 码中添加一些东西来打印地址 0x14,我认为空指针是 0 地址。但是,当我将 5 添加到 NULL 时,我不明白为什么我得到 0x14 而不是 0x20。我想既然 ptrNumA 是一个指向整数的指针,它应该添加大约 5 个整数块(每个 4 个字节)并且新地址应该是 0x20

感谢您的帮助。非常感谢。

【问题讨论】:

  • 十六进制的 14 是十进制的 20
  • 5 * 4 = 20 = 0x14
  • 除非必要,请不要发布图片
  • 0x 表明该值是以 16 为基数的。所以您期望的 0x20 是十进制的 320x 不代表“指针值”。
  • 不要同时使用 C 和 C++ 标记问题,除非它们涉及两种语言之间的交互,或者有特定原因询问两种语言之间的差异。 C 和 C++ 对空指针和地址算术有不同的规范,并且同时询问两者会混淆问题。如果您想询问两种语言,可以在单独的问题中进行。

标签: c++ c pointers null-pointer


【解决方案1】:

从技术上讲,numPtrA 不指向数组,甚至不指向单个对象,因此向其中添加除 0 以外的任何内容都会导致 C++ 中的未定义行为,因为指针运算仅针对数组和对象范围内的指针定义.

以下是最新 C++ 标准草案的引述:

[expr.add]

当一个整数类型的表达式 J 被添加到一个指针类型的表达式 P 中或从一个表达式 P 中减去时,结果具有 P 的类型。

  • 如果 P 的计算结果为空指针值,而 J 的计算结果为 0,则结​​果为空指针值。 [不适用]
  • 否则,如果 P 指向具有 n 个元素的数组对象 x 的数组元素 i ... [不适用] (注意:指向对象的指针可以视为数组出于本规则的目的,包含 1 个元素)
  • 否则,行为未定义

除了技术性之外,0x 前缀表示十六进制基数。 5 * 4 = 20 = 0x140x20 = 32 != 20

也就是说,空指针的值不一定是 0(即使字面量 0 始终是空指针字面量),所以期望不能移植到一些并非如此的系统。这在需要地址 0 用于实际存储的嵌入式系统上很典型。

此外,printf 说明符 %p 要求参数为 void* 类型,而您将 int* 作为参数传递。由于违反了这个约束,程序的行为是不确定的。

【讨论】:

  • @pqans 无论是否取消引用结果,简单地越界执行指针算术是未定义的行为。
  • @pqans 指向 null 的指针不是(至少保证是)指向对象的指针。即使是这样,添加一个大于 1 的值也会溢出一个元素的虚拟数组,导致未定义的行为。
  • @pqans The output of the sample program indeed does not indicate undefined behaviour. 这无关紧要。缺少指示 UB 的输出无论如何都不能证明缺少 UB。无论您尝试了多少编译器。大多数编译器都按照您的预期执行这一事实,这就是我将这种违规行为视为技术性问题的原因。
  • @pqans:在 C 中(问题标记为 C 和 C++),如图所示初始化的 numPtrA 并不指向地址 0 处的 int。当然它的 type 是“指向int 的指针”,但它的 是一个空指针。首先,请注意空指针不一定指向“地址 0”。由于 C 标准定义空指针常量的方式,常量 0 在用作指针时由编译器转换为实现用来表示空指针的任何位组合。这可能是 0、0xFFFFFFFF、0xDEADBEEF 或任何其他值……
  • … 其次,在计算的C模型中,没有对象存在。 C 2018 6.3.2.3 3 指定空指针“保证不等于指向任何对象或函数的指针”。因此,如果numPtrA 确实指向位于“地址 0”或任何其他地址的int,那么numPtrA == numPtrA 必须为真(根据 6.5.9 6 中的规则),但我们将有一个空指针(numPtrA) 比较等于指向对象的指针 (也称为 numPtrA),违反 6.3.2.3 3. 所以 C 标准中使用的模型是空指针 not指向一个对象。
【解决方案2】:

答案就在 cmets 中。 在您的情况下,sizeof(int) == 4 因此numPtr + 2 指向地址8 = 2*4numPtr + 5 指向地址20 = 5*4,这确实是十六进制表示的0x14

【讨论】:

  • 见这个:godbolt.org/z/8n5acv。助记符ud2 给出了一个无效的操作码,它只是生成了一个硬件指令陷阱。现在请解释为什么在(void*)0而不是(void*)1的情况下会发生这种情况。
【解决方案3】:

不允许对不指向有效对象的指针进行指针运算 - 这是未定义的行为(根据 6.5.6,详细信息和引号 here)。所以你可以从中得到任何结果。

此外,不保证空指针在 C 中的二进制表示为零。我建议学习What's the difference between null pointers and NULL?

最后,%p 给出了十六进制的输出。指针运算的基础是typeptr + 5 等于添加sizeof(*typeptr) * 5,以字节表示。因为sizeof(int) 可能是0x4,所以你得到0x4 * 0x5 = 0x14

【讨论】:

  • 本例中的指针指向地址 0 处的有效整数。
  • @pqans 不,它没有(必然)。请阅读提供的关于空指针如何工作的链接。
  • @pqans 为了说明这一点,您可能想思考为什么bananas_t* (*bananas) (bananas_t*) = (void*)0; 编译干净,但bananas_t* (*bananas) (bananas_t*) = (void*)1; 给出编译器错误“ISO C 禁止在函数指针和void * 之间进行初始化”
  • Re“你不被允许”:我做了很多次,一次也没被拦住。
  • Re“%p 以十六进制形式给出输出”:在 OP 的实现中,通常不是标准要求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-30
  • 1970-01-01
  • 2015-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多