显式结构布局上的未分配字段
Posted
技术标签:
【中文标题】显式结构布局上的未分配字段【英文标题】:unassigned field on explicit struct layout 【发布时间】:2017-07-12 11:36:05 【问题描述】:我想生成一个 C# sqrt 基准,但某些 sqrt 函数需要一个联合来进行按位计算。
我的工会是这样定义的:
[StructLayout(LayoutKind.Explicit)]
struct U
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public float x;
下一个代码在u.i
上产生未分配字段错误:
U u;
u.x = x;
u.i = (1 << 29) + (u.i >> 1) - (1 << 22);
我知道 u.i 是在 u.x 也被分配的时候被分配的,所以如果没有明确的 u.i
分配,是否可以在编译时忽略未分配的字段错误?
【问题讨论】:
不,你不能忽视。只需添加它。但是...为什么要在 C# 中模拟联合?!?!使用双打可以使用BitConverter
。使用浮点数...作为基准,我会省略分配并只计算sqrt()
,不会测量u.i
的额外分配。如果你真的想那样做,那就慢慢来:***.com/q/21801213/1207195
@AdrianoRepetti 主要 C 函数作为一个联合我只想尽可能地复制为 C(ish)。 BitConverter 似乎比基本的“模拟”慢?工会,但如果它是错误的,我会使用它。顺便说一句,我同意额外的任务。
检查链接的帖子,在接受的答案中有unsafe
内存访问,应该和C一样快union
【参考方案1】:
FieldOffset
主要是一个互操作功能;它告诉运行时在本机上下文中使用时应如何编组结构。在某些情况下(blittable 结构),它也会影响托管内存布局。应该注意的是,任何显式的结构布局都意味着代码不再是可移植的。这对您的代码来说可能是也可能不是问题。
重要的部分是编译器甚至不会尝试验证不安全的代码,只需要一些非常简单的启发式方法;它仍然看到两个字段,并且您从未分配过其中一个字段,因此您违反了struct
合同。这与使用 e.g. 没有太大区别。访问字段的指针算法。您犯了一个错误并忘记分配一个字段的可能性仍然比这明确地是您想要的要大得多。如果您真的想要,您可以在读取字段之前进行赋值(或使用构造函数;C# 不是 C,U u = new U();
通常很好)阅读该字段。
但在您的情况下,无论如何都没有理由使用联合字段。如果您想执行这样的非托管操作,请使用不安全代码。这就是它的用途。不要滥用互操作功能。当然,无论您选择哪种方式,都不要期望它是便携的。
float x = 42.0f;
(*(int*)((void*)&x)) // The value of `x` reinterpreted as an int
您的 sqrt 近似值的完整示例可能如下所示:
unsafe void Main()
sqrt(42).Dump(); // 6.625
sqrt(9).Dump(); // 3.125
unsafe float sqrt(float x)
int* pX = (int*)((void*)&x);
*pX = (1 << 29) + (*pX >> 1) - (1 << 22);
return x;
【讨论】:
我认为说“托管方将i
放置在与x
相同的内存上”是不正确的,一个明确的结构布局用于按位定位进行交互,在您的情况下,这行不通。你的(int*)((void*)&x);
在 IL 代码上被翻译成(int*) &x
,但你说得对,它似乎比使用结构更快,我将两者兼而有之。谢谢@Luaan!
@FlorianJ 我写过你不能保证会发生这种情况,即与你所读的相反:) StructLayout
(和FieldOffset
)是用于互操作的——他们对托管的任何更改内存布局是一个实现细节,而不是你可以依赖的东西。同样,获取一个字段的指针是安全的,但不要向该指针添加偏移量并期望在那里找到不同的字段 - 这根本不是合同的一部分。
FieldOffset 是一种互操作功能,不会以任何方式影响托管代码。 这实际上是不正确的。 LayoutKind.Explicit
... 影响托管和非托管布局,适用于 blittable 和非 blittable 类型。
@Luaan 这是来自docs.microsoft.com/en-us/dotnet/api/…的直接引用
ECMA-335: II.10.7 控制实例布局; II.16 定义和引用字段; II.22.16 字段布局。以上是关于显式结构布局上的未分配字段的主要内容,如果未能解决你的问题,请参考以下文章