“写一个swap函数,要求不使用中间变量,交换a、b两个变量的值”,应该算是老生常谈了。但今天却碰到一点新问题。
今天阅读《程序员的自我修养--链接、装载与库》,P98有一小段示例代码:
void swap(int *a, int *b){ *a ^= *b ^= *a ^= *b; }
我觉得用一行代码来实现交换a与b的值,还是很简洁的。于是顺手发给了一个朋友。结果朋友说,这个函数的执行出现了问题。
// File: swap.c
#includ <stdio.h> void swap(int *a, int *b){ *a ^= *b ^= *a ^= *b; } int main(void){ int a = 5, b = 10; swap(&a, &b); printf("a: %d, b: %d\n", a, b); }
预期的输出应为 a: 10, b:5,使用VC 2010编译,输出符合预期。但使用gcc 4.2.4 (Ubuntu 8.04, 32位),默认参数编译gcc swap.c,其执行结果确是:
a: 0, b: 5
a的输出是错误的。那就gcc -S swap.c,查看一下它的汇编代码吧,swap函数的代码如下:
.globl swap .type swap, @function swap: " 记*a的值为va,*b的值为vb pushl %ebp movl %esp, %ebp pushl %ebx movl 8(%ebp), %eax movl (%eax), %ebx " ebx 存放 va movl 12(%ebp), %eax movl (%eax), %ecx " ecx 存放 vb movl 8(%ebp), %eax movl (%eax), %edx " edx 存放 va movl 12(%ebp), %eax movl (%eax), %eax " eax 存放 vb xorl %eax, %edx " edx = (va ^= vb) movl 8(%ebp), %eax movl %edx, (%eax) " *a = (va ^= vb) movl 8(%ebp), %eax movl (%eax), %eax " eax = (va ^= vb) movl %ecx, %edx " edx = vb xorl %eax, %edx " edx = va movl 12(%ebp), %eax movl %edx, (%eax) " *b = va movl 12(%ebp), %eax movl (%eax), %eax " eax = va movl %ebx, %edx " edx = va xorl %eax, %edx " edx = 0 movl 8(%ebp), %eax " movl %edx, (%eax) popl %ebx popl %ebp ret
纯红色的三行代码,导致a的输出是0,估计是gcc对*a ^= *b ^= *a ^= *b的处理有问题。但奇怪的是,如果开启优化选项进行编译,得出的结果反而是对的,使用gcc -O2 swap.c,执行的结果就是对的,经过查看,其O2情况下汇编代码是正确的。
有人知道这是不是gcc的一个bug?还是我哪里弄错了?请不吝指教。
这不是gcc的bug,详见@garbageMan提供的链接。
下面附赠两个不使用中间变量的swap函数吧:
// 将*a ^= *b ^= *a ^= *b拆开写 // 利用 a ^ a ^ b == b void swap(int *a, int *b){ *a ^= *b; *b ^= *a; *a ^= *b; } // 利用 a + b - b = a void swap(int *a, int *b){ *a = *a + *b; *b = *a - *b; *a = *a - *b; }
下面是K&R C语言中的swap函数:
// interchange *px and *py void swap(int *px, int *py){ int temp; temp = *px; *px = *py; *py = temp; }
祝好。