C 和 C++ 中联合的用途 [英] Purpose of Unions in C and C++

查看:25
本文介绍了C 和 C++ 中联合的用途的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我之前很舒服地使用了工会;今天,当我阅读 这篇文章并得知这段代码时,我感到很震惊

联合 ARGB{uint32_t 颜色;结构组件标签{uint8_t b;uint8_t g;uint8_t r;uint8_t一个;} 组件;} 像素;像素.color = 0xff040201;//ARGB::color 从现在开始是活跃的成员//在某处,没有对像素进行任何编辑if(pixel.components.a)//访问非活动成员 ARGB::components

实际上是未定义的行为,即从最近写信的工会成员以外的工会成员那里阅读会导致未定义的行为.如果这不是联合的预期用途,那是什么?谁能详细解释一下?

更新:

我想在事后澄清一些事情.

  • 对于 C 和 C++,问题的答案并不相同;我无知的年轻自我将其标记为 C 和 C++.
  • 在浏览完 C++11 的标准后,我无法确定地说,它要求访问/检查非活动联合成员是未定义/未指定/实现定义的.我只能找到§9.5/1:<块引用>

    如果一个标准布局联合包含多个共享一个公共初始序列的标准布局结构,并且如果此标准布局联合类型的对象包含标准布局结构之一,则允许检查公共初始任何标准布局结构成员的序列.§9.2/19:两个标准布局结构共享一个共同的初始序列,如果相应的成员具有布局兼容的类型,并且两个成员都不是位域或两者都是一个或多个初始序列的具有相同宽度的位域成员.

  • 在 C 语言中,(C99 TC3 - DR 283 起)这样做是合法的(感谢 Pascal Cuoq 提出这个问题).但是,如果读取的值恰好对于它所读取的类型无效(所谓的陷阱表示"),那么尝试这样做它仍然可能导致未定义的行为.否则,读取的值是实现定义的.
  • C89/90 在未指明的行为(附件 J)下指出了这一点,并且 K&R 的书说它是定义的实现.引用 K&R:

    <块引用>

    这就是联合的​​目的——一个可以合法地持有多种类型中的任何一种的单一变量.[...] 只要用法一致:检索到的类型必须是最近存储的类型.程序员有责任跟踪当前存储在联合中的类型;如果将某些内容存储为一种类型并提取为另一种类型,则结果取决于实现.

  • 从 Stroustrup 的 TC++PL 中摘录(重点是我的)

    <块引用>

    联合的使用对于数据的兼容性可能是必不可少的[...] 有时会被误用于类型转换".

最重要的是,提出这个问题(自我提出问题以来,其标题保持不变)的目的是了解工会的目的,而不是标准允许的内容当然,C++ 标准允许使用继承来重用代码,但是 这不是将继承作为 C++ 语言特性引入的目的或初衷.这就是安德烈的答案继续被接受的原因.

解决方案

联合的目的是相当明显的,但由于某些原因,人们经常错过它.

联合的目的是节省内存,通过使用相同的内存区域在不同的时间存储不同的对象.就是这样.

这就像酒店的房间.不同的人在其中生活的时间不重叠.这些人从不见面,而且通常对彼此一无所知.通过适当管理房间的分时(即通过确保不同的人不会同时被分配到一个房间),一个相对较小的酒店可以为相对大量的人提供住宿,这就是酒店是为了.

这正是 union 所做的.如果您知道程序中的多个对象保存具有非重叠值生命周期的值,那么您可以将这些对象合并"到一个联合中,从而节省内存.就像酒店房间在每个时刻最多有一个活跃"租户一样,工会在计划时间的每个时刻最多有一个活跃"成员.只能读取活动"成员.通过写入其他成员,您可以将活动"状态切换到其他成员.

由于某种原因,联合的最初目的被完全不同的东西覆盖":编写联合的一个成员,然后通过另一个成员检查它.这种内存重新解释(又名类型双关语")是 不是对联合的有效使用.它通常会导致未定义的行为在 C89/90 中被描述为产生实现定义的行为.

在 C99 标准的一项技术勘误中给出了更详细的定义(参见 DR#257 和 DR#283).但是,请记住,正式地这并不能保护您免于尝试读取陷阱表示而陷入未定义的行为.

I have used unions earlier comfortably; today I was alarmed when I read this post and came to know that this code

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

is actually undefined behaviour I.e. reading from a member of the union other than the one recently written to leads to undefined behaviour. If this isn't the intended usage of unions, what is? Can some one please explain it elaborately?

Update:

I wanted to clarify a few things in hindsight.

  • The answer to the question isn't the same for C and C++; my ignorant younger self tagged it as both C and C++.
  • After scouring through C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined/unspecified/implementation-defined. All I could find was §9.5/1:

    If a standard-layout union contains several standard-layout structs that share a common initial sequence, and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members. §9.2/19: Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

  • While in C, (C99 TC3 - DR 283 onwards) it's legal to do so (thanks to Pascal Cuoq for bringing this up). However, attempting to do it can still lead to undefined behavior, if the value read happens to be invalid (so called "trap representation") for the type it is read through. Otherwise, the value read is implementation defined.
  • C89/90 called this out under unspecified behavior (Annex J) and K&R's book says it's implementation defined. Quote from K&R:

    This is the purpose of a union - a single variable that can legitimately hold any of one of several types. [...] so long as the usage is consistent: the type retrieved must be the type most recently stored. It is the programmer's responsibility to keep track of which type is currently stored in a union; the results are implementation-dependent if something is stored as one type and extracted as another.

  • Extract from Stroustrup's TC++PL (emphasis mine)

    Use of unions can be essential for compatness of data [...] sometimes misused for "type conversion".

Above all, this question (whose title remains unchanged since my ask) was posed with an intention of understanding the purpose of unions AND not on what the standard allows E.g. Using inheritance for code reuse is, of course, allowed by the C++ standard, but it wasn't the purpose or the original intention of introducing inheritance as a C++ language feature. This is the reason Andrey's answer continues to remain as the accepted one.

解决方案

The purpose of unions is rather obvious, but for some reason people miss it quite often.

The purpose of union is to save memory by using the same memory region for storing different objects at different times. That's it.

It is like a room in a hotel. Different people live in it for non-overlapping periods of time. These people never meet, and generally don't know anything about each other. By properly managing the time-sharing of the rooms (i.e. by making sure different people don't get assigned to one room at the same time), a relatively small hotel can provide accommodations to a relatively large number of people, which is what hotels are for.

That's exactly what union does. If you know that several objects in your program hold values with non-overlapping value-lifetimes, then you can "merge" these objects into a union and thus save memory. Just like a hotel room has at most one "active" tenant at each moment of time, a union has at most one "active" member at each moment of program time. Only the "active" member can be read. By writing into other member you switch the "active" status to that other member.

For some reason, this original purpose of the union got "overridden" with something completely different: writing one member of a union and then inspecting it through another member. This kind of memory reinterpretation (aka "type punning") is not a valid use of unions. It generally leads to undefined behavior is described as producing implementation-defined behavior in C89/90.

EDIT: Using unions for the purposes of type punning (i.e. writing one member and then reading another) was given a more detailed definition in one of the Technical Corrigenda to the C99 standard (see DR#257 and DR#283). However, keep in mind that formally this does not protect you from running into undefined behavior by attempting to read a trap representation.

这篇关于C 和 C++ 中联合的用途的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆