首页 文章

在C中键入punning和Unions

提问于
浏览
11

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

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

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

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


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

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

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

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

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

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

Edit

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

2 回答

  • 13

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

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

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

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

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

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

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

  • 7

    只要您只访问最近存储的成员( intfloat ),'s no problem and no real implementation dependency. It'就完全安全且定义良好,可以将值存储在union成员中,然后读取该成员 .

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

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

    如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的相应部分将重新解释为新类型中的对象表示形式如6.2.6所述(有时称为“打字”的过程) . 这可能是陷阱表示 .

相关问题