“寫一個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; }
祝好。