最近遇到了編譯優化選項竟然會影響到代碼邏輯的事,然後上網看到一篇還不錯的文章,以防備用,粘貼如下:
原文鏈接地址:點擊打開鏈接
正文一
首先看這麼一段程序,此程序是我將問題簡單化的程序:
#include<stdio.h> #include<string.h> int main() { char buffer[1024] = {0,1,2,3,4,5,6,7}; int iTest = 0x12345678; int *p = (int *)(buffer + 7); memcpy(p, &iTest, sizeof(iTest)); printf("%x\n", buffer[6]); printf("%x\n", buffer[9]); return 0; }
</pre><pre>
乍看之下,覺得這個程序沒啥問題。然後我們將此程序文件名稱叫point.c。然後分別用交叉編譯鏈進行如下編譯:arm-xxx-linux-gcc point.c -o point0 -O0
arm-xxx-linux-gcc point.c -o point1 -O1
arm-xxx-linux-gcc point.c -o point2 -O2
最終再分別執行三個程序,結果卻有點出人意料:
./point0
6
34
./point1
34
0
./point2
6
0
只有在-O0,也就是沒有優化的情況下,結果才和假想的一致。但是同樣的問題在x86平臺上卻沒有問題。
於是我通過用以下命令,分別來生成不同優化選項下的彙編代碼,來確定在ARM平臺上編譯到底出了什麼問題。
arm-xxx-linux-gcc point.c -o point0.s -O0 -S
arm-xxx-linux-gcc point.c -o point1.s -O1 -S
arm-xxx-linux-gcc point.c -o point2.s -O2 -S
然後對比三個彙編的代碼,發現問題出在memcpy這句話上。
在point0.s中,程序是老老實實的調用的memcpy,然後就將0x12345678老老實實按照字節一個個的放到了buffer+7的位置。
而在point1.s中程序則是沒有調用memcpy,而是用的語句:
str r3, [sp, #7]
而此時r3中存儲的就是0x12345678;而由於我採用的ARM平臺是32位的,此語句執行時,地址線應該不會發生變化,所以最終的結果是buffer+4到buffer+7的數據被覆蓋了,而不是buffer+7到buffer+10的數據被修改。
而在point2.s中,貌似又針對流水線進行了優化,程序執行順序會有所變化,在對buffer部分位置賦初值的順序是在str r3, [sp, #7]之後,所以buffer+6處的數據反而是正確的6。
分析到這兒,也許有人會說寫個簡單的程序,都會因爲編譯的優化選項不同導致結果不同,那這memcpy是不是就不敢用了?
其實一般只要有較好的編程習慣的話,都不會遇到此類問題,比如下面的程序:
#include<stdio.h> #include<string.h> int main() { char buffer[1024] = {0,1,2,3,4,5,6,7}; int iTest = 0x12345678; char *p = buffer + 7; memcpy(p, &iTest, sizeof(iTest)); printf("%x\n", buffer[6]); printf("%x\n", buffer[9]); return 0; }
這段程序其實只是簡單的改變了p的類型,就能保證在各種優化下,結果都一樣。可見好的編程習慣是有多麼的重要。