我已按如下方式定义了一个联盟:
union {
uintptr_t refcount;
struct slab_header *page;
} u;
page
指针保证在页面边界上对齐(最可能是4096),永远不会是NULL
。这意味着可能的最低地址为4096
。
refcount
将在0 .. 4095
内。
创建封闭结构后,我可以u.refcount = 0
或u.page = mmap(...)
。
这个联盟的代码将是这样的:
if (u.refcount < 4096) {
/* work with refcount, possibly increment it */
} else {
/* work with page, possibly dereference it */
}
这始终可以保证完全符合POSIX标准吗? uintptr_t
和struct slab_header *
是否有可能有不同的表示形式,例如,当u.page == 8192
,u.refcount < 4096
产生真的时?
答案 0 :(得分:3)
我不认为它“总能保证工作”,因为:
uintptr_t
是可选的(7.18.1.4)。void *
可以转换为uintptr_t
并返回(7.18.1.4)。 struct slab_header*
不能保证这种情况。 void *
具有与指向字符类型的指针相同的表示和对齐要求。指向结构的指针不需要具有相同的表示或对齐(6.2.5 27)。即使不是这种情况,也没有任何保证sizeof(uintptr_t) == sizeof(void *)
,它显然可以更大,并且仍然满足在典型的均匀指针情况下可转换为void *
的要求。因此,我得出结论,最好的方法是有一个共同的初始元素告诉国家。
答案 1 :(得分:1)
我将回答一个不同的问题 - “这是一个好主意吗?”我从代码中看到的更重要的担心是别名问题。如果有可能编写一段具有
效果的代码,我会毫不惊讶(事实上,我会温和地期待它)u.refcount
u.page
u.refcount
你可能会嗤之以鼻,但我已经看到了这种情况 - 如果你不了解危险,那么非常很长时间来调试。
你对这个特殊的联盟可能是安全的;我将把它留给那些对此类事物有更多经验的人(和/或C标准的副本)以做出判断。但我想强调,这是重要要担心的事情!!!
还有另一项费用。将union与“魔术”测试结合使用以发现存储在其中的内容 - 尤其是使用系统特定事实的内容 - 将使您的代码更难以阅读,调试和维护。您可以采取措施来缓解这一事实,但这始终是一个问题。
当然,当有人试图在奇怪的机器上使用它时,代码的成本根本不起作用。
正确的解决方案可能是以某种方式构建代码,以便关注数据布局的唯一代码是几个很小的inline
d访问例程,这样您就可以轻松地交换如何存储内容。组织您的代码以使用编译时标志来选择哪一个!
现在我已经说了所有这些,我真正想问的问题(并希望让你养成考虑的习惯):“它值得吗?”您正在牺牲自己的时间,代码可读性,编程的简易性和可移植性。你有什么收获?
许多人忘记提出这个问题了,他们编写复杂的错误代码时,简单易用,易于编写和阅读代码同样出色。
如果您发现这种变化会使时间密集型例程的性能翻倍,那么可能值得处理这个问题。但是,如果你只是因为这是一个节省8个字节的聪明技巧来调查这个问题,那么你应该把它视为一个智力练习。
答案 2 :(得分:0)
我认为<stdint.h>
和C99标准保证uintptr_t
和void*
具有相同的尺寸,并且可以毫无损失地投放。
为指针进行页面对齐是一个实现细节。
答案 3 :(得分:0)
该代码应该有任何问题。但是对于任何情况,您都可以在程序的init中进行检查:
if (sizeof(uintptr_t) != sizeof (struct slab_header*)
{
print error
}
但似乎有些事情(或不清楚)。当你有一个页面时,你希望“refcount”为0。页面为NULL时不为零?
答案 4 :(得分:0)
这总能保证有效吗?
没有。你不能读一个不同于最后写的联盟的成员。优化者确实考虑到了这一点,因此这不是一个理论问题。
uintptr_t和struct slab_header *是否有可能具有不同的表示形式, 因此,例如,当u.page == 8192时,u.refcount&lt; 4096收益是真的吗?
理论上也是可能的。我想不出它发生的当前实现。