“来自整数的指针/来自没有强制转换的指针的整数”问题

Lun*_*din 12 c pointers casting initialization assignment-operator

这个问题旨在成为整数和指针问题之间所有初始化/分配的常见问题解答条目。


我想编写代码,将指针设置为特定的内存地址,例如0x12345678. 但是当用 gcc 编译器编译这段代码时,我得到“初始化使指针从整数而不进行强制转换”警告/错误:

int* p = 0x12345678;
Run Code Online (Sandbox Code Playgroud)

类似地,这段代码给出了“初始化从指针生成整数而不进行强制转换”:

int* p = ...;
int i =  p;
Run Code Online (Sandbox Code Playgroud)

如果我在变量声明行之外做同样的事情,消息是一样的,但说的是“赋值”而不是“初始化”:

p = 0x12345678; // "assignment makes pointer from integer without a cast"
i = p;          // "assignment makes integer from pointer without a cast"
Run Code Online (Sandbox Code Playgroud)

使用其他流行编译器的测试也会给出错误/警告消息:

  • clang 说“不兼容的整数到指针转换”
  • icc 说“类型的值int不能用于初始化类型的实体int*
  • MSVC (cl) 说“初始化int*在间接级别上与int”不同。

问题:上面的例子是有效的 C 吗?


还有一个后续问题:

这不会给出任何警告/错误:

int* p = 0;
Run Code Online (Sandbox Code Playgroud)

为什么不?

Lun*_*din 13

不,它不是有效的 C 并且从来都不是有效的 C。这些例子是所谓的违反标准的约束

该标准不允许您初始化/分配指向整数的指针,也不允许您初始化/分配指向指针的整数。您需要使用强制转换手动强制类型转换:

int* p = (int*) 0x1234;

int i = (int)p;
Run Code Online (Sandbox Code Playgroud)

如果您不使用强制转换,则代码不是有效的 C,并且您的编译器不允许在不显示消息的情况下让代码通过。具体来说,这是由简单分配规则,C17 6.5.16.1 §1 规定的:

6.5.16.1 简单赋值

约束

符合下列条件之一:

  • 左操作数具有原子、限定或非限定算术类型,右侧具有算术类型;
  • 左操作数具有与右操作数兼容的结构或联合类型的原子、限定或非限定版本;
  • 左操作数具有原子、限定或非限定指针类型,并且(考虑左操作数在左值转换后的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针,并且左操作数指向的类型具有所有右边所指类型的限定符;
  • 左操作数具有原子的、限定的或非限定的指针类型,并且(考虑左操作数在左值转换后将具有的类型)一个操作数是指向对象类型的指针,另一个是指向限定或未限定版本的指针void,左边指向的类型具有右边指向的类型的所有限定符;
  • 左操作数是原子的、限定的或非限定的指针,右操作数是空指针常量;或者
  • 左操作数的类型是原子的、限定的或非限定的 _Bool,右操作数是一个指针。

在 的情况下int* p = 0x12345678;,左操作数是指针,右操作数是算术类型。
在 的情况下int i = p;,左操作数是算术类型,右操作数是指针。
这些都不符合上述任何约束条件。

至于为什么int* p = 0;有效,这是一个特例。左操作数是一个指针,右操作数是一个空指针常量有关空指针、空指针常量和 NULL 宏之间区别的更多信息


一些注意事项:

  • 如果将原始地址分配给指针,则该指针可能需要volatile限定,因为它指向硬件寄存器或 EEPROM/闪存位置之类的东西,可以在运行时更改其内容。

  • 即使使用强制转换,也不能保证将指针转换为整数。标准(C17 6.3.2.3 §5 和 §6 说):

整数可以转换为任何指针类型。除了前面指定的,结果是实现定义的,可能没有正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。68)

任何指针类型都可以转换为整数类型。除了前面指定的,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

信息性脚注:

68)将指针转换为整数或将整数转换为指针的映射函数旨在与执行环境的寻址结构保持一致。

此外,来自指针的地址可能大于 可以容纳在 中的地址int,大多数 64 位系统就是这种情况。因此最好使用uintptr_tfrom<stdint.h>