当变量与union相关联时,编译器会通过考虑最大内存的大小来分配内存。因此,union的大小等于最大成员的大小。所以这意味着改变任何成员的值将改变其他成员的价值。 但是当我执行以下代码时,
output: 4 5 7.000000
union job
{
int a;
struct data
{
double b;
int x
}q;
} w;
w.q.b=7;
w.a=4;
w.q.x=5;
printf("%d %d %f",w.a,w.q.x,w.q.b);
return 0;
}
问题是,首先我分配a的值,然后修改q.x的值,然后q的值将被q.x覆盖。但是在输出中它仍然显示了a和q.x的原始值。我无法理解为什么会这样?
答案 0 :(得分:5)
您的理解是正确的 - 数字应该改变。我拿了你的代码,并添加了一些,以向你展示究竟发生了什么。
真正的问题非常有趣,并且与浮点数在内存中的表示方式有关。
首先,让我们创建一个结构中使用的字节映射:
aaaa
bbbbbbbbxxxx
如您所见,b
的前四个字节与a
重叠。这将变得很重要。
现在我们要看看double
通常存储的方式(我是从Mac的角度来看这个,使用64位Intel架构。事实上,内存中的格式确实是IEEE754格式):
这里要注意的重要一点是,英特尔机器是“小端” - 也就是说,首先存储的数字是“右边的东西”,即“分数”的最低有效位。 / p>
现在让我们看看一个与你的代码做同样事情的程序 - 但打印出结构的内容,以便我们看到发生了什么:
#include <stdio.h>
#include <string.h>
void dumpBytes(void *p, int n) {
int ii;
char hex[9];
for(ii = 0; ii < n; ii++) {
sprintf(hex, "%02x", (char)*((char*)p + ii));
printf("%s ", hex + strlen(hex)-2);
}
printf("\n");
}
int main(void) {
static union job
{
int a;
struct data
{
double b;
int x;
}q;
} w;
printf("intial value:\n");
dumpBytes(&w, sizeof(w));
w.q.b=7;
printf("setting w.q.b = 7:\n");
dumpBytes(&w, sizeof(w));
w.a=4;
printf("setting w.a = 4:\n");
dumpBytes(&w, sizeof(w));
w.q.x=5;
printf("setting w.q.x = 5:\n");
dumpBytes(&w, sizeof(w));
printf("values are now %d %d %.15lf\n",w.a,w.q.x,w.q.b);
w.q.b=7;
printf("setting w.q.b = 7:\n");
dumpBytes(&w, sizeof(w));
printf("values are now %d %d %.15lf\n",w.a,w.q.x,w.q.b);
return 0;
}
输出:
intial value:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
全部为零(我声明了变量static
- 确保所有内容都被初始化)。请注意,该函数打印出16个字节,即使您可能认为最大元素为double + int
的结构应该只有12个字节长。这与字节对齐有关 - 当最大元素长度为8个字节时,结构将在8位边界上对齐。
setting w.q.b = 7:
00 00 00 00 00 00 1c 40 00 00 00 00 00 00 00 00
让我们按照正确的顺序查看代表double
的字节:
40 1c 00 00 00 00 00 00
Sign bit = 0
exponent = 1 0000 0000 0111b (binary representation)
mantissa = 0
setting w.a = 4:
04 00 00 00 00 00 1c 40 00 00 00 00 00 00 00 00
当我们现在写a
时,我们修改了第一个字节。这对应于尾数的最低有效位,现在是(十六进制):
00 00 00 00 00 00 04
现在,尾数的格式意味着这个数字左边有一个1
;因此,将最后一位从0
更改为4
会将数字的大小改变一小部分 - 您需要查看第15个小数才能看到它。
setting w.q.x = 5:
04 00 00 00 00 00 1c 40 05 00 00 00 00 00 00 00
值5
写在自己的小空间
values are now 4 5 7.000000000000004
注意 - 当我使用大量数字时,您可以看到b
的最不重要部分不完全是7
- 即使double完全能够准确地表示整数。
setting w.q.b = 7:
00 00 00 00 00 00 1c 40 05 00 00 00 00 00 00 00
values are now 0 5 7.000000000000000
再次将7
写入double后,您可以看到第一个字节再次00
,现在printf
语句的结果确实是7.0。
所以 - 你的理解是正确的。问题在于你的诊断 - 数字不同但你看不到它。
通常,查找这些内容的好方法是将数字存储在临时变量中,然后查看差异。那么你会很容易找到它。
答案 1 :(得分:1)
如果您运行以下代码,则可以看到更改的值: -
#include <stdio.h>
union job
{
struct data
{
int x;
double b;
}q;
int a;
} w;
int main() {
w.q.b=7;
w.a=4;
w.q.x=5;
printf("%d %d %f",w.a,w.q.x,w.q.b);
return 0;
}
输出:5 5 7.000000
我稍微修改了联合内部的结构,但这解释了你的担忧。
答案 2 :(得分:1)
Actualy指令w.a = 4
会覆盖w.q.b
的数据。这是你的记忆的样子:
After w.q.b=7; After w.a=4; After w.q.x=5;
|0|1|0|0|0|0|0|0| |0|1|0|0|0|0|0|0| |0|1|0|0|0|0|0|0| \ \
|0|0|0|1|1|1|0|0| |0|0|1|1|1|0|0|0| |0|0|1|1|1|0|0|0| | w.a |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| | |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|0| |0|0|0|0|0|1|0|0| / | w.q.b
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| /
----------------- ----------------- -----------------
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| \
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| | w.q.x
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |
|0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|1| /
正如您所看到的,w.q.b
的第30位由0
更改为1
,因为4
已分配给前4个字节,但此更改是太低,只有尾数部分受到影响,打印精度w.q.b
不会显示此变化。