将 C 指针强制转换为其他类型时要记住的规则

Rules to keep in mind when casting a C pointer to a different type

提问人:alessio solari 提问时间:7/25/2023 最后编辑:chqrliealessio solari 更新时间:7/26/2023 访问量:125

问:

我已经使用 C 指针一段时间了,一切都按预期工作。但是,现在我遇到了 ISO 标准,该标准说“如果生成的指针未正确对齐指向类型,则行为未定义”。

这让我不禁要问自己:

  1. 这是什么意思?

  2. 我们如何安全地将指针投射到其他类型?

我发现这个 C 代码示例据说是不正确的,因为它与更严格对齐的指针类型有关:

int main() {
    
    char c = 'x';
    int *ip = (int *)&c;
    char *cp = (char *)ip;

    return 0;
}
  1. 更严格对齐的指针类型是什么意思?
C 指针 转换 内存对齐

评论

0赞 Robert Harvey 7/25/2023
int *ip = (int *)&c;可以访问未由 定义的内存。char c
0赞 alessio solari 7/25/2023
Rovert Harvay,我知道。我说的问题是另一个问题。请仔细阅读问题
1赞 Robert Harvey 7/25/2023
根据平台的不同,可能需要 4 字节对齐,而 a 只需要单字节对齐。这个问题似乎很清楚。intchar
0赞 alessio solari 7/25/2023
罗伯特·哈维(Robert Harvay),那么当我们将指针投射到不同类型的指针时,要记住哪些规则才能100%确定我们的投射是正确的?
1赞 Barmar 7/26/2023
@MooingDuck C++,问题是关于C的。

答:

2赞 Eric Postpischil 7/26/2023 #1
  1. 更严格对齐的指针类型是什么意思?

C 2018 6.2.8 5说道:

对齐方式的顺序是从较弱较强更严格的对齐方式。更严格的对齐方式具有较大的对齐方式值。满足对齐要求的地址也满足任何较弱的有效对齐要求。

如果您没有“对齐”的规范,C 2018 6.2.8 1 说:

完整的对象类型具有对齐要求,这些要求对可以分配该类型对象的地址施加了限制。对齐方式是实现定义的整数值,表示可以分配给定对象的连续地址之间的字节数...

实际上,这意味着具有对齐要求 A 的对象必须从地址 A 的倍数开始。

  1. 这是什么意思?

考虑一些值为 P 的指针。也就是说,P 是指针指向的地址。出于这个答案的目的,我们将 P 视为内存空间中的完全解析地址。(实际指针可以用对各种基址或段的引用来表示,也可以用这些基址或段开头的偏移量来表示。当指针转换为指向其他对象类型的指针时,新对象类型具有一些对齐要求 A。如果 P 是 A 的倍数,则 P 与对象类型正确对齐,并且转换会生成一些新的有效指针。(由于 C 中的其他规则,它不一定是可用于访问新类型对象的指针。如果 P 不是 A 的倍数,则 C 标准不定义程序的行为。转换可能不会生成有效的指针,可能会生成调整为正确对齐的指针,程序可能会中止,或者程序的行为方式可能出乎您的意料。

  1. 我们如何安全地将指针投射到其他类型?

仅当您知道源指针与目标类型正确对齐时,才将指针转换为指向新对象类型的指针。

鉴于 转换为 通常不安全,因为 a 可以被赋予任何起始地址,但在现代 C 实现中通常有四个字节的对齐要求,即使在旧的 C 实现中也至少需要两个字节。因此,存在不合适对齐的危险。char c;&cint *charint&cint

类型的对齐要求因 C 实现而异。如果指针指向对齐类型与新类型一样严格或更严格的对象,则您知道可以安全地转换该对象。否则,C 实现可能会提供测试对齐的方法。通常,转换为 a 会提供地址的表示形式,可以测试该地址以查看它是否是所需对齐方式的倍数。一个类型的对齐要求可以通过计算来获得。uintptr_t_Alignof(type-name)

评论

0赞 Barmar 7/26/2023
如果您解释示例代码如何具体违反对齐规则(对齐方式为 1,通常对齐方式为 4 或 8),因此您可以强制转换为 ,但反之亦然,这将很有帮助。char*int*int*char*
0赞 alessio solari 7/26/2023
Eric Postipischil,我得出的结论是,始终 100% 安全的转换是从指向 T 类型对象的指针到 void*。我们保证,当我们回到T时,我们不会有不好的惊喜。你同意吗?
1赞 Eric Postpischil 7/26/2023
@alessiosolari:是的,将指向对象类型的指针与同一对象类型相互转换是安全的。的对齐要求是最小值,即 1 个字节,并且对于转换回来,已知指针满足目标类型的对齐要求,因为它源自该类型。void *void
1赞 chux - Reinstate Monica 7/26/2023 #2

我们如何安全地将指针投射到其他类型?

除了其他答案中涵盖的方面外......

警惕在对象指针之间进行转换,例如 、 、 ...和函数指针行。void *int *unsigned char *int (*p)()

函数指针可能比所有对象指针更宽(或很少更少)。因此,像 这样的代码可能会丢失重要信息。void *p = main

通常,在明确限制之前,请避免将函数指针转换为对象指针 - 即使如此,也请考虑其他方法。

2赞 John Bollinger 7/26/2023 #3

Erik 已经彻底涵盖了问题 (1) “它是什么意思?”和问题 (3) “更严格对齐的指针类型是什么意思?”。

至于。。。

  1. 我们如何安全地将指针投射到其他类型?

埃里克基本上说,如果,从任何类型转换为任何类型都是安全的,这是绝对正确的,也是最一般和最全面的答案。但是,您似乎正在寻找一些实用的规则,显式检查对齐要求在运行时并没有真正有用,如果在开发过程中完成,则会产生特定于实现的结果。您可能会发现这些规则更实用:T *U *alignof(U) <= alignof(T)

安全

  • 从任何对象指针类型强制转换为void *
  • 从任何对象指针类型强制转换为 ,或者char *signed char *unsigned char *
  • 在指向类型的指针之间强制转换,这些类型仅在符号和/或类型限定符上有所不同。
  • 从指向结构类型的指针转换为指向结构的第一个成员类型的指针
  • 从指向联合类型的指针转换为指向联合的任何成员类型的指针
  • 从指向数组类型的指针转换为指向数组元素类型的指针,反之亦然
  • 如果可以通过一个或多个安全的中间转换安全地从 to 转换为类型,则直接从类型转换为类型。T *U *T *U *
  • 在任意两种函数指针类型之间强制转换
  • 给定通过一系列指针转换获得的指针值,这些指针转换中的任何一个都不调用未定义的行为,无论其安全性是否可事先证明,都要转换回原始类型或任何中间类型。pp

请注意,指针转换的安全性仅与转换本身有关。取消引用由安全(在我们正在讨论的意义上)转换生成的指针,或者对于函数指针,通过转换后的指针调用指向的函数不一定安全。