有没有一种故障安全方法可以确定 C 中指针的对齐/尾随位?

Is there a failsafe way to determine the alignment/trailing bits of a pointer in C?

提问人:user16217248 提问时间:11/16/2023 更新时间:11/22/2023 访问量:99

问:

在 C 中,如果一定数量的尾随位为零,则指针与某个指针对齐。这需要提取指针的位,尽管只是有限数量的尾随位。显而易见的方法是强制转换指向位掩码的指针并使用位掩码:uintptr_t

void *my_ptr;
bool aligned_to_16 = (uintptr_t)my_ptr & 15;

但是,C 标准不需要,因为这是一种可选类型。这可能是指针太大而无法放入整数中的情况。但是,这应该无关紧要,因为无论如何我们只关心尾随位。那么,如果我只是投射到 or 呢?uintptr_tunsigned charint

好吧,根据这个答案,这仍然是未定义的行为,即使我们不关心会导致值不适合的前导位:

据我所知,指向不同类型指针的指针的所有其他强制转换都是未定义的行为。特别是,如果不强制转换为 char 或足够大的整数类型,则将指针强制转换为其他指针类型可能始终是 UB - 即使没有取消引用它。

我找不到任何符合故障安全标准的方法来提取指针的实际尾部位,例如测试指针对齐方式,不调用任何可选功能或实现定义/未指定/未定义的行为。但是,除了使用格式说明符执行并解析结果之外,实际上还有一种方法吗?snprintf()%p

C 指针类型 转换 跨平台 未定义行为

评论

4赞 Barmar 11/16/2023
我不这么认为。可移植代码不应该关心这些细节。
3赞 Barmar 11/16/2023
请注意,即使提供了,理论上它也可能无法执行您想要的操作。不要求整数值与指针地址相同,只需可以在类型之间来回转换。加 1 是有效的,而减去 1 是有效的。uintptr_tptr->uintptr_t
2赞 tadman 11/16/2023
如果您发现一个系统不起作用,请回复我们,因为我们都可以嘲笑这是多么彻底的糟糕,我们会想知道更多。
2赞 user16217248 11/16/2023
在 2022/2023 年,STDC 委员会认为值得放弃对非 2 的补码表示的支持,但出于某种原因,他们认为不值得放弃对理论机器的支持,因为指针的对齐实际上并不反映在尾部位的可整除性上。
2赞 Eric Postpischil 11/16/2023
这对你有什么好处?即使你有一种方法可以测试C标准完全指定的对齐方式,它能让你做什么,这也是由C标准指定的,而不是依赖于实现的?不能将其转换为基于对齐测试的另一种类型,然后使用该类型访问对象,因为这不符合别名规则,除非是人为的情况。因此,你要用它来做任何事情都是依赖于实现的,所以你不妨使用一种依赖于实现的方式来测试一致性。

答:

1赞 supercat 11/22/2023 #1

不要求指针标识硬件地址。实现可以将每个指针值表示为分配表中的索引和该分配中的偏移量的组合。例如,一些用于 80286 的 C 编译器就是这样工作的。虽然我所知道的执行此类操作的实现通常会以将偏移量的底部位放入整数底部位的方式执行指针到整数的转换,但并不要求它们这样做。

另一方面,该标准没有强制要求所有实现都以 99% 的实现的通用方式运行,这并不意味着编译器编写者通常不应该这样做,而是将偏差限制在那些具有明显或有据可查且令人信服的理由的实现上。它也不意味着程序员应该跳过重重障碍来适应相反的行为,除非他们需要针对这种行为是合理的实现,或者其作者会认为标准对这种偏差的允许本身就是合理的。

评论

0赞 Eric Postpischil 11/22/2023
回复“......未能...并不意味着......不该。。。副歌。。。偏离。。。在没有...否则......“:双重否定是轻罪,四重否定是重罪,七重否定是死罪。不要指望任何人能理解这篇文章。
0赞 supercat 11/22/2023
@EricPostpischil:更好?
1赞 chqrlie 11/22/2023 #2

显而易见的方法确实是将指针转换为并使用位掩码屏蔽结果值。然而,在您的示例中是未初始化的,因此您确实有一个未定义的行为,编译器可能会利用该行为进行一些不可思议的优化:)此外,如果指针在 16 个字节上对齐,则这样计算的布尔值将为 true。(uintptr_t)my_ptraligned_to_16

假设目标上存在该类型,并且指针有效,则行为是实现定义的,这与尝试实现非可移植实现相关技巧一样好。uintptr_t

所以答案是:不,没有符合故障安全标准的方法来提取指针的实际尾部位,即使与转换一起使用,但如果你真的需要这些信息,实现定义不是问题。snprintf%p

需要一个反常的系统(想想 DS9K)来阻止你从这种方法中获得有意义的结果。

摆弄指针的低位通常用于手动优化的实现:

  • memcpy、 、 和其他字符串函数memmovememset
  • 对动态类型对象使用标记指针的解释器
  • 内存分配器(使用链接指针的低位作为标志)

以上都不是完全可移植的,但适用于大多数当前的架构。在实际需要移植软件之前,您不必担心异国情调的硬件。

最后,你应该问问自己:我真的需要求助于低级的不可移植的黑客,还是我只是沉迷于过早的优化?

评论

0赞 Andrew Henle 11/22/2023
摆弄指针的低位......这可能是非常不便携的。我之前的评论隐藏在关于问题本身的 20 条左右的评论中,但仅仅创建一个指向不满足该类型对齐要求的类型的指针会调用未定义的行为:“如果生成的指针未正确对齐引用类型,则行为未定义。调用 UB 无需取消引用。任何与对齐相关的代码都将非常依赖于实现。
0赞 chqrlie 11/22/2023
@AndrewHenle:没错,但可能性很小。你有例子吗?
0赞 Andrew Henle 11/22/2023
ARM 和 SPARC CPU 具有严格的对齐要求,如果违反,则会导致对齐,但在指针被取消引用之前,IME 不会出现 barf,但我尽量避免这样做,因此我的经验并不完全排除仅创建指针导致问题。重要的是,根据 6.3.2.3p7,优化编译器可以假设不会发生未对齐的指针,这为各种问题打开了大门。ISTR是一篇关于原始x86 CPU上的旧分段内存模型的博客文章,内容是关于使用这些指针玩游戏如何导致各种恶作剧。SIGBUS
0赞 chqrlie 11/22/2023
@AndrewHenle:是的,无效的选择器(分段指针的高字)只需将指针值加载到一对寄存器中即可导致分段错误()。但在这里,我们只看低位,当然,更改这些位必须小心,以避免为其类型创建未对齐的指针。LES BX,DWORD PTR myptr
0赞 supercat 11/26/2023
@chqrlie: If ARM clang is given something like and that function tries to access and , clang may replace the two 16-bit accesses with a single 32-bit accesses, on the presumption that every pointer to a union will be aligned to satisfy the coarsest union member that exists, rather than the coarsest member that is dereferenced.union u { unsigned short hh[4]; unsigned ll[2]; }; void test(union u *p)p->hh[0]p->hh[1]