7

我可以打印整数的地址和值,但不能打印联合的字符。为什么会这样

#include <iostream>

using namespace std;

union Endian
{
    int i;
    char c[sizeof(int)];
    int j;
};

int main(int argc, char *argv[]) {
    Endian e;
    e.i = 20;
    cout << &e.j;
    cout << &e.i;
    cout << &e.c[0]; //Why can't I print this address
    cout << e.c[1]; // Why can't I print this value

}

O/P:0x7fff5451ab68 0x7fff5451ab68

4

3 回答 3

22

免责声明:OP的标签很模糊,所以这个答案使用代码作为参考框架,即C++(使用iostream,拉入std命名空间,cout)。

您正在union以不适当的方式使用。但我们稍后再谈。

e.i = 20;

您的代码首先使用 union as i,一个整数。没关系。但是你后来的所作所为真的不是一个好主意。首先,您做了两件可以接受的事情:

cout << &e.j;
cout << &e.i;

您在 union 中查询了两个ints 的地址,这很好,因为它们都共享存储,因此第一个字节的地址是共享的。

cout << &e.c[0]; //Why can't I print this address
cout << e.c[1]; // Why can't I print this value

现在,这就是你越界的地方。您现在正在执行隐式指针算术和取消引用,以索引到char[]数组,即使您尝试获取第一个元素的地址,也可能评估一个不是联合中最后一个集合的元素。所以,这是一个很大的禁忌。

此外,&e.c[0]基本上是char*哪个将被“拦截”并被cout视为 C 风格的字符串。它不会将其视为简单地址。

cout << e.c[1]; // Why can't I print this value

未定义的行为。“可是,可是!” ,我能听到你们中的一些人说。是的,它是 C++ 中的 UB。在 C99 (6.5/7) 中有效,仅通过脚注和一些胶带。这是一件简单的事情,LightnessRacesInSpace 和 Mysticial 在这个答案和其他人的评论中已经解释过。

是的,您可以将任何类型的变量转换为 char 数组,并出于您的任何目的而将其弄乱。但是在 C++ 中通过联合进行类型双关是非法的,没有任何理由和借口。是的,它可能会起作用。是的,如果您不介意,您可以继续使用它。但根据 C++ 标准,这显然是非法的。

除非该成员是您为其分配值的联合的最后一个成员,否则您不得检索其值。就这么简单。

C++ 中的联合有一个目的,如下所述。它们还可以具有成员函数和访问说明符。它们不能有虚函数或静态成员。它们也不能用作基类或从某些东西继承。而且它们不能用于类型双关语。这在 C++ 中是非法的。

进一步阅读!

了解工会

工会是:

  • 一种允许内存重用的方法。
  • 而已。

工会不是:

  • 一种在工会元素之间进行牛仔表演的方法
  • 一种欺骗严格别名的方法。

甚至 MSDN 都做对了

联合是一种用户定义的数据或类类型,在任何给定时间,它只包含其成员列表中的一个对象(尽管该对象可以是数组或类类型)。

这是什么意思?这意味着您可以按照以下方式定义一些东西:

union stuff {

    int i;
    double d;
    float f;    

} m;

这个想法是所有这些都坐在内存中的同一个空间中。联合的存储是从给定实现中的最大数据类型推断出来的。平台在这里有很大的自由度。规范无法涵盖的自由。不是 C。不是 C++。

不能将其作为 an 写给 union int,然后将其作为 a float(或其他任何东西)作为某种奇怪的牛仔 reinterpret_cast 的一种方式来阅读。

的使用std::cout是出于示例目的和简单性。

这是非法的:

m.i = 5;
std::cout << m.f; // NO. NO. NO. Please, no.

这是合法的:

m.i = 5;
std::cout << m.i;

// Now I'm done with i, I have no intention of using it
// If I do, I'll make sure I properly set it.

m.f = 3.0f;
std::cout << m.f; // No "cowboy-interpreting", defined.

// I've got an idea, but I need it to be an int.

m.i = 3; // m.f and m.d are here-by invalidated.
int lol = 5;
m.i += lol;

注意没有“交叉火力”。这是预期的用途。用于三个不同时间使用的三个变量的超薄内存存储,无需争吵。

误解是如何产生的?一些非常糟糕的人有一天醒来,我敢打赌其中一个是 3D 程序员并考虑这样做:

// This is wrong on so many different levels.
union {

    float arr[4];
    struct {
        float x,y,z,w;
    };

};

毫无疑问,他有一个“高尚的想法”,即以浮点数组和单个 xyzw 成员的形式访问 4 元组。现在,您知道为什么在联合方面这是错误的,但这里还有一个失败:

C++ 没有匿名结构。它确实具有匿名联合,出于上述目的,使其更接近预期用途(删除m.“前缀”),因为您肯定可以看到这对联合背后的一般理念有何好处。

不要这样做。请。

于 2013-04-11T16:07:48.033 回答
4

严格来说,您的代码的行为是 undefined与我之前所说的相反,代码的行为不是未定义的(我认为它是实现定义的)。有关说明,请参阅https://stackoverflow.com/a/1812932/367273

发生的是&e.c[0]type char*,因此被打印为 C 字符串,而不是指针。该字符串为空白或由不可打印的字符组成,因此您看不到任何输出。类似的事情发生在e.c[1],除了它是一个单一的char而不是一个字符串。

当我初始化e如下:

e.i = 0x00424344;

最后两行分别打印DBCB(这利用了在我的机器上,int32 位宽并且是小端序的事实)。

于 2013-04-11T15:02:32.960 回答
2

It's Undefined Behaviour to access field of the union with a type other than the last set one, at least in C++.

Whilst taking an address is legal in theory, that's not what the unions are for.

于 2013-04-11T15:06:02.833 回答