百科问答小站 logo
百科问答小站 font logo



C++中 union 储存的 char 成员能否通过 int 成员读取? 第1页

  

user avatar   zorrolang 网友的相关建议: 
      

(邀请的名单有点长,我就默认选择谢邀第一个了)。这个问题是比较典型的大小端问题。我们从两个方面来说:

(补充:有小伙伴指出C++对union有特殊的生存周期的处理,我看了一下C++还确实有member lifetime这个说法,既然如此,那下面的程序我就只能说适用于C语言了。因为C++从编译器层面对union成员的行为有了特别的“规定”,这种处理几乎可以说从语法上限制了问题题目的这种使用方式,将这种行为直接视为未定义【具体的参考评论区和invalid s的讨论】。C语言则粗糙或者说开放很多,只要你程序员处理的好大小端的问题,这事我就不管~):

首先说一下大小端对

       union {   char c;   int n; } un;     

的影响。

我觉得这个很多人基本都应该是清楚的,比如下面的程序:

       #include <stdio.h> union mytest {   char c;   int n; };  int main() {         union mytest un = {0};          un.n = 0xFF0055AA;         printf("un.n = 0x%x
", un.n);         printf("un.c = 0x%hhx
", un.c);         return 0; }     

你觉得打印出来的结果会是什么?特别是un.c的结果,你觉得是FF还是AA?

……

实际上在抛开大小端不谈的情况下是没有明确答案的,在大端机器上(我使用的s390x)结果是:

       un.n = 0xff0055aa un.c = 0xff     

而在小端机器上(我使用的x86_64)结果是:‘

       un.n = 0xff0055aa un.c = 0xaa     

如果你不能理解,那就要回答什么是大小端这个最原始的问题上来了。可能很多人都听过大小端的定义,但是实际使用时总是会有一些模糊的地方。关于大小端的问题我写过下面的一个回答,做了比较详细的解释:

如果你懒得翻,我就用一小段说一下:

关于Endianness有这样一段描述(来源wikipedia,非决定性定义)

In its most common usage, endianness indicates the ordering of bytes within a multi-byte number. A big-endian ordering places the most significant byte first and the least significant byte last, while a little-endian ordering does the opposite.

这段描述可以清楚的说明我们“通常”所说的大小端的意思。首先我们所使用的现代计算机系统都是以字节为一个最小存储单元,你向内存中读写数据一次最少是一个字节,换句话说一个字节内是不能拆分的(取bit是先取字节再得到的bit)。而多个字节就涉及了如何拆分/排布的问题,比如一个两个字节的数据类型,我们定义为:

       u16 data = 0x1122;     

那么

       高地址 +------+             +------+ | 0x11 |             | 0x22 | +------+     或      +------+ | 0x22 |             | 0x11 | +------+             +------+ 低地址     

的存储方式就成了人们争论的问题。很遗憾生产厂商没有就此达成共识,于是就有了针对一个多字节的数据类型有“从高地址往低地址存”和“从低地址往高地址”两种方案。关于大小端问题不只存在于内存存储数据的方式上,比如硬盘存储、网络数据传输都方面都涉及大小端问题,这里先不引申了。所以大小端是针对一个多字节数据类型产生的,比如int, long等。而对于单字节,如char本身,是没有大小端的问题(除非在按位分大小端的系统上,不过我们目前没有这样系统的机器)。

所以上面程序在大端机器上的表现形式就是:

       高地址 +------+ | 0xaa | +------+ | 0x55 | +------+ | 0x00 | +------+ | 0xff | +------+     <--- un.n / un.c 低地址     

而在小端机器上的表现形式就是:

       高地址 +------+ | 0xff | +------+ | 0x00 | +------+ | 0x55 | +------+ | 0xaa | +------+     <--- un.n / un.c 低地址     

然后我们再说一下:

       union {   char c;   int n; } un;     

这种用法合不合适,我不能说绝对不合适,因为这里没有写明它的应用场景。但是就一般的使用习惯上来说,除非你明确知道自己在操作的内存块在不同平台的表现都符合你的预期,否则就别这么玩。

现实中常见的是保持大小一致的用法,比如用两个不同的名字:

       union {         unsigned long   sllp;   /* SLB L||LP (exact mask to use in slbmte) */         unsigned long ap;       /* Ap encoding used by PowerISA 3.0 */ };     

比如用数组:

       union {         __u8 cdata[4];         __u32 idata; } result, last_result;     

比如用位域:

       union ia64_isr {         __u64  val;         struct {                 __u64 code : 16;                 __u64 vector : 8;                 __u64 reserved1 : 8;                 __u64 x : 1;                 __u64 w : 1;                 __u64 r : 1;                 __u64 na : 1;                 __u64 sp : 1;                 __u64 rs : 1;                 __u64 ir : 1;                 __u64 ni : 1;                 __u64 so : 1;                 __u64 ei : 2;                 __u64 ed : 1;                 __u64 reserved2 : 20;         }; }     

等等……

那有没有问题中那种用法呢,肯定是有的,我一开始就没有否定这种用法,我否定的是在“不知道自己在干什么”的胡乱使用。比如下面这个例子(摘自Linux内核):

       void kvm_mmio_write_buf(void *buf, unsigned int len, unsigned long data) {         void *datap = NULL;         union {                 u8      byte;                 u16     hword;                 u32     word;                 u64     dword;         } tmp;          switch (len) {         case 1:                 tmp.byte        = data;                 datap           = &tmp.byte;                 break;         case 2:                 tmp.hword       = data;                 datap           = &tmp.hword;                 break;         case 4:                 tmp.word        = data;                 datap           = &tmp.word;                 break;         case 8:                 tmp.dword       = data;                 datap           = &tmp.dword;                 break;         }          memcpy(buf, datap, len); }     

到此,如果你觉得你已经清楚了,那么留几个思考题(设int是四个字节,char是一个字节):

       union mytest {         char c;         struct {                 char a[4];         }sta;         struct {                 char w;                 char x;                 char y;                 char z;         }stw;         int n; }un;     

这种情况下如果赋值:

       un.sta.a[0] = 0xAA; un.sta.a[1] = 0x55; un.sta.a[2] = 0x00; un.sta.a[3] = 0xFF;     

那么c, w, x, y, z和n(在32或64位大小端机器上,系统、编译器和库也是32位以上的)分别是多少?

如果赋值:

       un.stw.w = 0xAA; un.stw.x = 0x55; un.stw.y = 0x00; un.stw.z = 0xFF;     

那么c, a[0~3]和n分别是多少?




  

相关话题

  C#调用C++DLL函数,一般怎么封装这个DLL? 
  Python如何调用一个py文件并输出部分行内容? 
  清华大学计算机专业本科这位在「自己写的 CPU 上运行自己写的操作系统」的同学是什么水平? 
  在c++代码中使用const关键字是不是自找麻烦? 
  C/C++中按值传递比按地址传递更快吗, 引用呢? 
  想学计算机系却报错志愿进了数学系,该怎么办? 
  国内研究生不小心跟了一个水货老师是什么样的体验? 
  C/C++有什么库可以完成命令行参数解析? 
  有哪些c++的书推荐? 
  有一台不会坏掉的电脑,这台电脑上只有vc++6.0,给一个人一亿年的时间,能创造出现在的各种软件吗? 

前一个讨论
市场监管总局对腾讯、杭州阿里创业投资等进行行政处罚,腾讯、哔哩哔哩被罚款 50 万元,释放了哪些信号?
下一个讨论
古代皇帝赐百亩良田是赐哪里的田给奖励者的?





© 2024-11-08 - tinynew.org. All Rights Reserved.
© 2024-11-08 - tinynew.org. 保留所有权利