我有这样的代码
#include <stdio.h>
int main()
{
int A[4] = {3, 519, 27, 49};
(A[1]) ^= (A[3]);
(A[3]) ^= (A[1]);
(A[1]) ^= (A[3]);
printf("%d, %d, %d, %d\n", A[0], A[1], A[2], A[3]);
A[1] ^= A[3] ^= A[1] ^= A[3];
printf("%d, %d, %d, %d\n", A[0], A[1], A[2], A[3]);
}
我想交换A [1]和A [3]的值。对于第一个printf,我得到答案 3,49,27,519,这是对的。但是对于第二个,我得到3分,0分,27分,49分。 我认为声明“A [1] ^ = A [3] ^ = A [1] ^ = A [3];”已被翻译为:
A[1] = A[1] ^ A[3];
A[3] = A[3] ^ A[1] ^ A[3];
A[1] = A[1] ^ A[3] ^ A[1] ^ A[3];
计算这些表达式时,和A [1]总是519,A [3]等于49。 当我用gdb调试时,我发现在这个语句中A [1]首先被改变49 - &gt; 566,然后A [3]从519变为49,然后A [1]从566变为0.
我也尝试改变这样的声明:volatile int A [4] = {3,519,27,49}; 但输出仍然是一样的。 并改变这样的陈述:A [1] ^ =(A [3] ^ =(A [1] ^ = A [3])); 答案仍然是错误的。
但如果我使用g ++而不是gcc编译代码,我可以得到正确的答案: 3,49,27,519 3,519,27,49 如果声明是
int a = 49;
int b = 519;
a ^= b ^= a ^= b;
它可以交换价值。 我不知道为什么数组元素出错了。
答案 0 :(得分:6)
第二个语句调用未定义的行为,因为您在sequence points之间修改了相同的值两次。
这是未定义行为的原因是因为如果我们希望编译器能够生成有效的代码,那么这样的松散规则是必要的。在你的例子中,它并不明显,但如果你有这样的函数:
void
foo(int *a, int *b, int *c, int *d)
{
*a ^= *b ^= *c ^= *d;
}
这将在机器代码中翻译为:
load r1 (register 1) with value at d
load r2 with value at c
load r3 with value at b
load r4 with value at a
r2 = r1 xor r2
r3 = r2 xor r3
r4 = r3 xor r4
store r2 at c
store r3 at b
store r4 at a
但编译器不知道指针是否指向同一个内存。因此,如果我们要强制执行此函数的严格排序,我们必须执行以下操作:
load r1 with value at d
load r2 with value at c
r2 = r1 xor r2
store r2 at c
load r1 with value at b
r2 = r1 xor r2
store r2 at b
load r1 with value at a
r2 = r1 xor r2
store r2 at a
现在,这似乎是相同的工作量,不是吗?相同数量的指令,只是以不同的顺序,作为奖励我们使用更少的寄存器。那为什么不呢?原因是记忆很慢。第一个指令序列将执行得更快,因为在执行下一条指令之前,cpu实际上并没有在前一条指令完成之前等待。您可以拥有许多已经开始但尚未完成的说明。当我们在第一个例子中想要r1的值时,我们已经从内存中开始了三次加载。这听起来并不多,但这样的事情加起来。
因此,C标准决定允许编译器在序列点之间做很多他们想要的事情(我在这个答案的第一句中链接了一个wiki页面,解释了序列点是什么)如果需要的话那就是生成有效的代码。这意味着如果您想确保计算的正确结果,您必须遵循某些规则(并且编译器通常不会警告您)。您不能在两个序列点之间两次修改相同的值。如果您读取并修改两个序列点之间的值,则只能读取它以计算修改。等等。关于未定义的内容有很多规则,几乎所有这些规则都未定义,因为我们希望编译器能够在各种不同的CPU架构上生成快速代码。