在C中键入punning和Unions

Dav*_*son 12 c

我正在开发一个项目来构建一个小编译器,只是为了它.

我决定采用构建一个非常简单的虚拟机来实现目标,所以我不必担心学习精灵,英特尔组装等的来龙去脉.

我的问题是关于使用工会在C中打字.我决定只在vm的内存中支持32位整数和32位浮点值.为方便起见,vm的"主内存"设置如下:

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


memory = (word *)malloc(mem_size * sizeof(word));
Run Code Online (Sandbox Code Playgroud)

所以我可以根据指令将内存部分视为int或float.

这在技术上是打字吗?当然,如果我使用int作为内存的话,然后使用float*来像浮点数一样对待它们.我目前的方法虽然在语法上有所不同,但我认为它在语义上并不相同.最后,我仍然将内存中的32位视为int或float.

我在网上提出的唯一信息表明这是依赖于实现的.是否有更便携的方式来实现这一点而不浪费一大堆空间?

我可以做到以下几点,但接下来我会占用相当于工会的2倍以上的记忆和"重新发明轮子".

typedef struct
{
    int i;
    float f;
    char is_int;
}
Run Code Online (Sandbox Code Playgroud)

编辑

我也许没有说清楚我的确切问题.我知道我可以使用一个没有未定义行为的联合中的float或int.我所追求的是一种具有32位内存位置的方法,我可以安全地将其用作int或float,而不知道最后一个值集是什么.我想说明使用其他类型的情况.

AnT*_*AnT 14

是的,存储一个联合成员并读取另一个成员是类型双关语(假设类型是完全不同的).而且,这是C语言正式支持的唯一一种通用(任何类型到任何类型)类型的双关语.在某种意义上,语言支持在这种情况下承诺将实际发生类型双关语,即,将发生将一种类型的对象作为另一种类型的对象进行读取的物理尝试.除此之外,它意味着编写联合的一个成员并读取另一个成员意味着写入和读取之间的数据依赖性.但是,这仍然会让您承担确保类型惩罚不会产生陷阱表示的负担.

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

int i;
float f;

i = 5;
f = *(float *) &i;
Run Code Online (Sandbox Code Playgroud)

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

f = *(float *) &i;
i = 5;
Run Code Online (Sandbox Code Playgroud)

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

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


Kei*_*son 6

只要您只访问最近存储的成员(intfloat),就没有问题,也没有真正的实现依赖性.将值存储在union成员中然后读取同一个成员是非常安全和定义良好的.

(请注意,虽然它们出现在我见过的每个系统上,但int并不能保证它们float具有相同的大小.)

如果您将值存储在一个成员中,然后读取另一个成员,则表示类型为punning.在最新的C11草案中引用脚注:

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

  • 在我经验丰富的(少数)64位系统上,`int`和`float`都是32位宽.哪些系统有不同的尺寸? (2认同)
  • @bta:A,没有`float32_t`-但是断言`sizeof(int32_t)== sizeof(float)`对于绝大多数系统都是可以的。 (2认同)