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

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

问题描述

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

联合 ARGB{uint32_t 颜色;结构组件标签{uint8_t b;uint8_t g;uint8_t r;uint8_t a;} 成分;} 像素;像素.颜色 = 0xff040201;//ARGB::colour 从现在开始是活跃成员//线下某处,没有对像素进行任何编辑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天全站免登陆