12

我目前正在做一个项目来构建一个小型编译器,只是为了它。

我决定采用构建一个非常简单的虚拟机作为目标的方法,这样我就不必担心学习 elf、intel 汇编等的来龙去脉了。

我的问题是关于在 C 中使用联合的类型双关语。我决定在 vm 的内存中只支持 32 位整数和 32 位浮点值。为方便起见,vm的“主存”设置如下:

typedef union
{    
    int i;
    float f;
}word;


memory = (word *)malloc(mem_size * sizeof(word));

因此,我可以根据指令将内存部分视为 int 或 float。

这是技术上的双关语吗?如果我将整数用作记忆词,然后使用 float* 将它们视为浮点数,那肯定会是这样。我目前的方法虽然在语法上有所不同,但我不认为在语义上有所不同。最后,我仍然将内存中的 32 位视为 int 或 float。

我能在网上找到的唯一信息表明这取决于实现。有没有更便携的方法来实现这一点而不会浪费大量空间?

我可以执行以下操作,但是我将占用 2 倍以上的内存并在工会方面“重新发明轮子”。

typedef struct
{
    int i;
    float f;
    char is_int;
}

编辑

我可能没有把我的确切问题说清楚。我知道我可以使用联合中的浮点数或整数,而不会出现未定义的行为。我所追求的是一种拥有 32 位内存位置的方法,我可以安全地将其用作 int 或 float,而无需知道最后设置的值是什么。我想说明使用其他类型的情况。

4

2 回答 2

14

是的,存储一个 union 成员并读取另一个成员是类型双关语(假设类型足够不同)。此外,这是 C 语言官方支持的唯一一种通用(任何类型到任何类型)类型双关语。在某种意义上支持该语言承诺在这种情况下类型双关语将实际发生,即将发生将一种类型的对象读取为另一种类型的对象的物理尝试。除其他外,这意味着写入联合的一个成员并读取另一个成员意味着写入和读取之间的数据依赖关系。然而,这仍然给您带来了确保类型双关语不会产生陷阱表示的负担。

当您将类型转换指针用于类型双关语(通常被理解为“经典”类型双关语)时,该语言明确指出在一般情况下行为是未定义的(除了将对象的值重新解释为chars 数组和其他受限情况)。像 GCC 这样的编译器实现了所谓的“严格别名语义”,这基本上意味着基于指针的类型双关语可能无法像您期望的那样工作。例如,编译器可能(并且将)忽略类型双关读取和写入之间的数据依赖性并任意重新排列它们,从而完全破坏您的意图。这

int i;
float f;

i = 5;
f = *(float *) &i;

可以很容易地重新排列成实际的

f = *(float *) &i;
i = 5;

特别是因为严格别名的编译器故意忽略了示例中写入和读取之间数据依赖的可能性。

在现代 C 编译器中,当您确实需要将一个对象值物理重新解释为另一种类型的值时,您被限制为memcpy从一个对象到另一个对象的 -ing 字节或基于联合的类型双关语。没有其他方法。铸造指针不再是一个可行的选择。

于 2012-07-11T23:33:43.367 回答
7

只要您只访问最近存储的成员(int或),就没有问题,也没有真正的实现依赖性。float将值存储在联合成员中然后读取同一个成员是非常安全且定义明确的。

(请注意,不能保证intfloat大小相同,尽管它们在我见过的每个系统上都有。)

如果您将一个值存储在一个成员中,然后读取另一个成员,那就是类型双关语。引用最新 C11 草案中的一个脚注:

如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的适当部分重新解释为新类型中的对象表示在 6.2.6 中描述(有时称为“类型双关语”的过程)。这可能是一个陷阱表示。

于 2012-07-11T22:58:57.947 回答