寻求关于C语言联合体的澄清

5
typedef union {
    float flts[4];
    struct {
        GLfloat r;
        GLfloat theta;
        GLfloat phi;
        GLfloat w;
    };
    struct {
        GLfloat x;
        GLfloat y;
        GLfloat z;
        GLfloat w;
    };
} FltVector;

好的,我认为我知道如何使用这个功能(或者说,我已经看到它是如何被使用的),即:

FltVector fltVec1 = {{1.0f, 1.0f, 1.0f, 1.0f}};
float aaa = fltVec1.x;
etc.

但我并不真正理解联合声明了多少存储空间(4个浮点数?8个浮点数?12个浮点数?),为什么?以及为什么使用FltVector {{}}时需要两组花括号?

为什么要使用联合?为什么不这样做...

   struct FltVector {
        GLfloat x;
        GLfloat y;
        GLfloat z;
        GLfloat w;
   }

任何指针都非常感谢(对双关语感到抱歉)
6个回答

5
一个联合体允许您为不同类型的变量“回收”同一内存区域。通常,联合体占用的存储空间与其最大成员相同,在本例中可能是4个浮点数。您可以使用sizeof检查。
在这种情况下,联合体可能用于提供以下两种功能:1)结构体中相同浮点数的备用名称(例如,xr共享相同的内存),2)将相同的四个浮点数作为数组访问(例如,xflts [0]共享相同的内存)。有时,联合体被用于各种“技巧”,通常是不可移植的,以访问某些数据类型的内部,例如机器顺序中整数的各个字节。

而在这种情况下,就是4个浮点数(如果GLfloat等于float)。 - tur1ng

5
如果sizeof(GLfloat) == sizeof(float),那么将分配4个浮点数。
在这里,flts[0]rx都将指向同一块内存。
在联合中,每个声明的不同变量都指向相同的内存。
在这里,我们有3个变量,2个结构体和一个数组,它们每个都从同一内存点开始。

2

有几个问题 :)

@Arkku关于大小的说法是正确的。对齐也可能起到一定作用,但在这里可能不重要。

之所以如此,是因为任何时候联合体只保存可能值中的一个。因此,通常将联合体放在结构体中,并将其与标识哪个值有效的内容(有时称为判别式联合scrim)一起使用。

一对大括号用于联合体,另一对用于数组初始化器。


1
在您的示例中,如果我们考虑变量的名称,联合体最肯定不是用于通过x和r(半径和x坐标不适合)访问同一内存单元,而是为了让用户为两者提供相同的参数。当您使用笛卡尔坐标时,设置x、y、z、w要简单得多,并且使用这些相同的名称来表示径向坐标会很尴尬。这两种方法都比仅使用数组索引更简单。您可能还有另一个参数,指定所提供坐标的类型(笛卡尔或径向)。因此,您将拥有像pdbartlett所称的带判别式的联合体。
在这种情况下,双层大括号是无用的,因为数组可以通过数组(双层大括号)或通过其中一个内部结构进行初始化。 更正:双层大括号避免了将输入强制转换为GLFloats。
最后一个细节:未命名的内部结构不是标准C,做事情的标准方式是给内部结构命名,就像这样。
typedef union {
    float flts[4];
    struct {
        float r;
        float theta;
        float phi;
        float w;
    } cartesian;
    struct {
        float x;
        float y;
        float z;
        float w;
    } radial;
} FltVector;

FltVector f = {1.0, 2.0, 3.0, 4.0 };

int main(int argc, char * argv[]){
    printf("flts[0]=%f f.radial.r=%f f.cartesian.x=%f\n",
        f.flts[0], f.radial.r, f.cartesian.x);
}

这不是“更常见”,实际上这是语言所要求的。由于非标准编译器扩展,它可以在没有这些名称的情况下编译。 - AnT stands with Russia
@AndreyT:我会编辑答案,我没有检查标准(不过我并不是标准的真正信徒,我认为编译器才是真正的东西。在我看来,标准只是为了避免太多的熵并引导语言进化的工具)。 - kriss

1
为什么在使用FltVector {{}}时需要两组花括号?
看整行代码:FltVector fltVec1 = {{1.0f, 1.0f, 1.0f, 1.0f}}; 你正在初始化第一个结构体中的四个浮点数,而这个结构体又位于联合体中。正如你从加粗的"in"中可以看到的那样,有两层嵌套。如果嵌套的层数更深,你甚至可以有更多的花括号。

是的,但是我们可以毫不费力地删除一组大括号,因为它将初始化第一个内部结构体。 - kriss

0

正如所述,该代码使用不同的名称和数据类型分配相同的内存。有时候,允许在其他情况下将向量视为数组,同时仍然可以处理命名的向量分量(xyzw)可能会更加舒适。

虽然如此,笛卡尔和径向结构的名称似乎被交换了。"r"、"theta"和"phi"是径向坐标的常用名称,而笛卡尔坐标通常用"x"、"y"和"z"表示。

我认为值得注意的是,使用不同的表示方式并不严格符合标准(但可能在所有现有的C实现中都能正常工作),原因有两个:

  1. 读取非最近写入的联合成员将产生未定义的结果。任何理智的实现都会返回存储在该内存中的值。
  2. 编译器可能会在结构成员之间添加填充(出于性能考虑),而数组永远不会添加填充。然而,在任何现代CPU上,这种情况不太可能发生。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接