7

我正在阅读 O'Reilly 的《实用 C 编程》一书,并阅读了有关 C 编程语言的 K&R 书籍,但我真的很难理解联合背后的概念。

它们占用了构成它们的最大数据类型的大小......并且最近分配的一个覆盖了其余的......但是为什么不根据需要使用/释放内存呢?

书中提到它用于通信,您需要设置相同大小的标志;在一个谷歌网站上,它可以消除奇数大小的内存块......但它在现代的非嵌入式内存空间中有什么用处?

你可以用它和 CPU 寄存器做一些狡猾的事情吗?它只是早期编程时代的延续吗?还是像臭名昭著的 goto 一样,仍然有一些强大的用途(可能在狭窄的内存空间中)值得保留?

4

5 回答 5

5

好吧,你几乎回答了你的问题:记忆。过去内存相当低,甚至节省几 KB 也很有用。

但即使在今天,也有工会有用的场景。例如,如果您想实现某种variant数据类型。最好的方法是使用联合。

这听起来并不多,但让我们假设您想使用一个变量来存储一个 4 字符串(如 ID)或 4 字节数字(可能是一些散列或实际上只是一个数字)。

如果您使用 classic struct,这将是 8 个字节长(至少,如果您不走运,也会有填充字节)。使用union它只有 4 个字节。所以你节省了 50% 的内存,这对于一个实例来说并不是很多,但想象一下拥有一百万个这样的内存。

虽然您可以通过强制转换或子类化联合来实现类似的事情,但仍然是最简单的方法。

于 2013-03-28T00:01:34.913 回答
1

联合的一种用途是让两个变量占用相同的空间,结构中的第二个变量决定您要将其读取为什么数据类型。

例如,您可以有一个布尔值“isDouble”和一个联合体“doubleOrLong”,它同时具有双精度和长精度。如果 isDouble == true 将联合解释为双精度,否则将其解释为长整数。

联合的另一个用途是访问不同表示形式的数据类型。例如,如果您知道双精度在内存中的布局方式,您可以将双精度放在联合中,将其作为不同的数据类型(如 long)访问,直接访问其位、尾数、符号、指数等,并对其进行一些直接操作。

现在你真的不需要这个了,因为内存很便宜,但是在嵌入式系统中它有它的用途。

于 2013-03-27T23:57:53.933 回答
0

Windows API 大量使用联合。LARGE_INTEGER就是这种用法的一个示例。基本上,如果编译器支持 64 位整数,则使用QuadPart成员;否则,手动设置低 DWORD 和高 DWORD。

于 2013-03-28T00:02:39.000 回答
0

这并不是真正的拖延,因为 C 语言是在 1972 年创建的,当时内存是一个真正的问题。

您可以提出这样的论点,即在现代非嵌入式空间中,您可能不想一开始就使用 C 作为编程语言。如果您选择 C ​​作为实现语言的选择,那么您正在寻求利用 C 的优势:它高效、接近金属,从而产生紧凑、快速的二进制文件。

As such, when choosing to use C, you'd still want to take advantage of it's benefits, which includes memory-space efficiency. 对此,联盟运作良好;允许您具有一定程度的类型安全性,同时强制使用最小的内存占用空间。

于 2013-03-28T00:03:40.613 回答
0

我见过它使用的一个地方是 Doom 3/idTech 4 Fast Inverse Square Root实现。

对于不熟悉该算法的人来说,它本质上需要将浮点数视为整数。旧的 Quake(和更早)版本的代码通过以下方式执行此操作:

float y = 2.0f;

// treat the bits of y as an integer
long i  = * ( long * ) &y;

// do some stuff with i

// treat the bits of i as a float
y = * ( float * ) &i;

GitHub上的原始资源

此代码获取浮点数的地址y,将其转换为指向 long 的指针(即,在 Quake 日中为 32 位整数),并将其取消引用为i. 然后它做了一些令人难以置信的奇怪的小玩意儿,反之亦然。

这样做有两个缺点。一个是复杂的地址,强制转换,取消引用过程强制y从内存中读取值,而不是从寄存器1中读取,并且在返回的路上也是如此。然而,在 Quake 时代的计算机上,浮点寄存器和整数寄存器是完全分开的,因此您几乎必须将内存推送到内存并返回以处理此限制。

第二个是,至少在 C++ 中,进行这种强制转换是非常不受欢迎的,即使在执行诸如此函数之类的相当于巫术的事情时也是如此。我确信有更多令人信服的论点,但我不确定它们是什么:)

因此,在 Doom 3 中,id 在他们的新实现中包含了以下位(它使用了一组不同的位旋转,但类似的想法):

union _flint {
        dword                   i;
        float                   f;
};

...
union _flint seed;
seed.i = /* look up some tables to get this */;
double r = seed.f; // <- access the bits of seed.i as a floating point number

GitHub上的原始资源

理论上,在 SSE2 机器上,这可以通过单个寄存器访问;我不确定在实践中是否有任何编译器会这样做。在我看来,它仍然比早期 Quake 版本中的铸造游戏更干净一些。


1 - 忽略“足够高级的编译器”参数

于 2013-03-28T01:26:52.603 回答