C++联合体定义与成员访问规则

35次阅读

ckquote>联合体是一种共享内存的数据结构,其大小等于最大成员的大小,所有成员共用同一块内存空间;写入一个成员后,其他成员变为无效,访问非活跃成员会导致未定义行为;为避免此类问题,应使用判别器(如枚举)标识当前活跃成员,或采用C++17的std::variant以获得类型安全和自动管理功能。ckquote>

c="https://img.php.cn/upload/article/000/969/633/175806727020232.png" alt="C++联合体定义与成员访问规则">

C++联合体,在我看来,它就是一种特殊的“变色龙”式的数据结构,它允许你在同一块内存空间里,根据需要“变身”成不同的数据类型。但请记住,它一次只能“变身”成一种形态,如果你试图去访问它当前没有“变身”成的那个成员,那结果往往是出乎意料的,甚至可以说是未定义行为。简单来说,联合体让你用最紧凑的方式存储互斥的数据,但代价是你必须自己管理当前哪个成员是有效的。

解决方案

要理解和正确使用C++联合体,我们得从它的定义和核心特性说起。

定义与内存分配: 联合体使用

<code class='language-de<code class='language-default'>fcode>ault'>unioncode>

关键字来定义,它看起来和结构体(

<code class='language-de<code class='language-default'>fcode>ault'>structcode>

)非常相似,只是关键字不同。

<code class='language-de<code class='language-default'>fcode>ault'>unioncode> <code class='language-de<code class='language-default'>fcode>ault'>Datacode> {     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> i;     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode> <code class='language-default'>fcode>;     <code class='language-de<code class='language-default'>fcode>ault'>char c[4]code>; // 假设char占1字节,这里是为了演示不同大小的成员 };

当我们定义一个

<code class='language-de<code class='language-default'>fcode>ault'>Datacode>

类型的联合体变量时,例如

<code class='language-de<code class='language-default'>fcode>ault'>Datacode> my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>;

,编译器会为它分配一块内存。这块内存的大小,不是所有成员大小之和,而是所有成员中占用内存最大的那个成员的大小。在上面的例子中,如果

<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>

<code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode>

都占4字节

<code class='language-de<code class='language-default'>fcode>ault'>char c[4]code>

也占4字节,那么

my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>

就会占用4字节的内存。所有成员都从这块内存的起始地址开始共享。

成员访问规则: 这是联合体的核心,也是最容易出错的地方。

  1. 写入成员: 当你向联合体的一个成员写入数据时,比如
    my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i = 10;

    ,这块共享内存就会被

    <code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>

    类型的数据占据。此时,其他成员(

    <code class='language-default'>fcode>

    c

    )的值就变得不确定了,它们不再是“有效”的状态。

  2. 读取成员: 你只能安全地读取你最近一次写入的那个成员。例如,如果你刚刚写入了
    my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i = 10;

    ,那么读取

    my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i

    (

    std::cout << my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i;

    ) 是完全正确的。

  3. 访问非活跃成员: 如果你写入了
    my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i = 10;

    ,然后尝试去读取

    my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.<code class='language-default'>fcode>

    (

    std::cout << my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.<code class='language-default'>fcode>;

    ),这就是所谓的未定义行为(Unde<code class='language-default'>fcode>ined Behavior, UB)。你可能会得到一个随机的浮点数,或者

    0.0

    ,甚至程序崩溃,这完全取决于编译器、操作系统和当时内存的状态。它不会报错,但结果不可预测,这是联合体使用中最需要警惕的地方。

总结一下: 联合体就像一个多功能插槽,你插入了U盘,就不能同时插入SD卡。如果你强行去读SD卡的数据,那读到的可能就是U盘的二进制乱码。

立即学习code class='language-default'>fcode>="https://pan.quark.cn/s/6e7abc4abb9<code class='language-default'>fcode>" style="text-decoration: underline !important; color: blue; <code class='language-default'>fcode>ont-weight: bolder;" rel="no<code class='language-default'>fcode>ollow" target="_blank">C++免费学习笔记(深入)”;

为什么C++联合体能节省内存?它与结构体有何本质区别

联合体之所以能节省内存,核心就在于它那独特的内存分配策略。与结构体(

<code class='language-de<code class='language-default'>fcode>ault'>structcode>

)不同,结构体是为它的每个成员都分配独立的、不重叠的内存空间,所以结构体的总大小通常是其所有成员大小之和(或者更大,考虑到字节对齐)。而联合体,就像前面提到的,它只为所有成员中最大的那个成员分配内存,然后让所有成员共享这同一块起始地址的内存。

举个例子可能更直观:

<code class='language-de<code class='language-default'>fcode>ault'>structcode> S {     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> a;    // 4 bytes     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode> b;  // 4 bytes     char c;   // 1 byte }; // sizeo<code class='language-default'>fcode>(S) 可能是 12 bytes (取决于对齐)  <code class='language-de<code class='language-default'>fcode>ault'>unioncode> U {     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> a;    // 4 bytes     <code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode> b;  // 4 bytes     char c;   // 1 byte }; // sizeo<code class='language-default'>fcode>(U) 必然是 4 bytes (取最大成员<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>/<code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode>的大小)

你看,

U

的大小明显小于

S

。这就是联合体节省内存的秘诀。它牺牲了同时存储所有成员的能力,换取了极致的内存紧凑性。我个人觉得,这就像是同一个柜子,结构体是为每个物品都单独开辟了一个抽屉,而联合体则是所有物品共用一个最大的抽屉,但你一次只能放一件物品进去。这种设计理念,在内存资源非常有限的嵌入式系统或者需要处理大量异构数据的场景下,显得尤为有价值。

在C++联合体中,访问非活跃成员会发生什么?如何避免这种未定义行为?

访问联合体的非活跃成员,简单来说,就是踩到了C++标准中的“未定义行为”地雷。它不会像语法错误那样直接阻止你编译,但运行时可能会导致各种难以预料的后果。

到底会发生什么? 当你写入

my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.i = 10;

后,这4字节的内存被解释为

<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>

类型的

10

。如果此时你尝试读取

my<code class='language-de<code class='language-default'>fcode>ault'>Datacode>.<code class='language-default'>fcode>

,编译器会尝试将这4字节的二进制数据按照

<code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode>

的IEEE 754标准来解释。结果呢?大概率是一个毫无意义的浮点数值,因为它根本就不是按照浮点数格式存储的。更糟糕的是,如果你的联合体成员类型有构造函数、析构函数或更复杂的行为,访问非活跃成员可能导致内存损坏、程序崩溃,或者其他难以追踪的bug。这就像你把一张图片文件用文本编辑器打开,看到的是一堆乱码,只不过在程序里,这种乱码可能会引发更严重的连锁反应。

如何避免这种未定义行为? 避免这种问题的核心在于追踪当前哪个成员是活跃的。C++本身不会自动为你做这件事,所以你通常需要自己动手:

  1. 使用判别器(Discriminator): 这是最常见也是最推荐的做法。通常,我们会把联合体嵌套在一个结构体中,并在结构体中添加一个枚举类型(或者其他简单的类型)作为判别器,用来指示当前联合体中哪个成员是有效的。

    enum class <code class='language-de<code class='language-default'>fcode>ault'>Datacode>Type {     INT,     FLOAT,     CHAR_ARRAY };  <code class='language-de<code class='language-default'>fcode>ault'>structcode> MyVariant {     <code class='language-de<code class='language-default'>fcode>ault'>Datacode>Type type;     <code class='language-de<code class='language-default'>fcode>ault'>unioncode> {         <code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> i;         <code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode> <code class='language-default'>fcode>;         char c_arr[4];     } data; };  // 使用示例 MyVariant mv; mv.type = <code class='language-de<code class='language-default'>fcode>ault'>Datacode>Type::INT; mv.data.i = 42;  i<code class='language-default'>fcode> (mv.type == <code class='language-de<code class='language-default'>fcode>ault'>Datacode>Type::INT) {     std::cout << "Int value: " << mv.data.i << std::endl; } else i<code class='language-default'>fcode> (mv.type == <code class='language-de<code class='language-default'>fcode>ault'>Datacode>Type::FLOAT) {     // ... }

    这样,每次访问前先检查

    type

    字段,就能确保你总是访问正确的成员。

  2. C++17

    std::variant

    如果你的项目允许使用C++17或更高版本,那么

    std::variant

    是一个更安全、更现代的替代品。它在底层可能也使用了联合体的思想,但它提供了类型安全、自动管理活跃成员、值语义以及访问机制(如

    std::get

    std::visit

    ),极大地降低了出错的风险。

     #include <variant> #include <iostream> #include <array> // For std::array  std::variant<<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>, <code class='language-de<code class='language-default'>fcode>ault'><code class='language-default'>fcode>loatcode>, std::array<char, 4>> v; v = 42; // 此时<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>是活跃成员  try {     std::cout << "Int value: " << std::get<<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>>(v) << std::endl
相关标签:

click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16016.html" target="_blank">操作系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16298.html" target="_blank">字节 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16673.html" target="_blank">u盘 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17603.html" target="_blank">c++ click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17719.html" target="_blank">ios click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/27988.html" target="_blank">区别 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/92702.html" target="_blank">为什么 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据类型" target="_blank">数据类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Float" target="_blank">Float click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=构造函数" target="_blank">构造函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=析构函数" target="_blank">析构函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=枚举类型" target="_blank">枚举类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=结构体" target="_blank">结构体 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'>unioncode>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'>unioncode> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=char" target="_blank">char click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据结构" target="_blank">数据结构 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=堆" target="_blank">堆 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Struct" target="_blank">Struct click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=unde<code class='language-default'>fcode>ined" target="_blank">unde<code class='language-default'>fcode>ined click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=嵌入式系统" target="_blank">嵌入式系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=bug" target="_blank">bug

大家都在看:

code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1525915.html" title="C++联合体定义与成员访问规则">C++联合体定义与成员访问规则 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524717.html" title="C++中深拷贝和浅拷贝在内存管理上的区别是什么">C++中深拷贝和浅拷贝在内存管理上的区别是什么 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524710.html" title="C++智能指针引用计数变化观察方法">C++智能指针引用计数变化观察方法 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524698.html" title="C++如何开发学生信息管理系统">C++如何开发学生信息管理系统 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524691.html" title="C++异常处理与标准库算法结合">C++异常处理与标准库算法结合

click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16016.html" target="_blank">操作系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16298.html" target="_blank">字节 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16673.html" target="_blank">u盘 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17603.html" target="_blank">c++ click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17719.html" target="_blank">ios click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/27988.html" target="_blank">区别 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/92702.html" target="_blank">为什么 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据类型" target="_blank">数据类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Float" target="_blank">Float click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=构造函数" target="_blank">构造函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=析构函数" target="_blank">析构函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=枚举类型" target="_blank">枚举类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=结构体" target="_blank">结构体 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'>unioncode>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'>unioncode> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=char" target="_blank">char click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据结构" target="_blank">数据结构 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=堆" target="_blank">堆 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Struct" target="_blank">Struct click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=unde<code class='language-default'>fcode>ined" target="_blank">unde<code class='language-default'>fcode>ined click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=嵌入式系统" target="_blank">嵌入式系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=bug" target="_blank">bug

text=ZqhQzanResources