簡單分析針對ARM平臺的C語言程序的編譯問題
我們知道在C語言編譯時,有那么幾個常用的優(yōu)化編譯選項(xiàng),分別是-O0,-O1,-O2,-O3以及-Os。之前一直覺得既然是優(yōu)化選項(xiàng),頂多是優(yōu)化一下邏輯,提高一些效率或者減少一下程序大小而已。很少會覺得它們會影響程序的最終結(jié)果。直到最近在ARM平臺上發(fā)現(xiàn)一個程序里的一個bug,才覺得這些優(yōu)化選項(xiàng)有時候也沒那么智能。或者說針對ARM平臺,還沒有那么智能。
首先看這么一段程序,此程序是我將問題簡單化的程序:
#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;
}
乍看之下,覺得這個程序沒啥問題。然后我們將此程序文件名稱叫point.c。然后分別用交叉編譯鏈進(jìn)行如下編譯:
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
最終再分別執(zhí)行三個程序,結(jié)果卻有點(diǎn)出人意料:
./point0 6 34 ./point1 34 0 ./point2 6 0
只有在-O0,也就是沒有優(yōu)化的情況下,結(jié)果才和假想的一致。但是同樣的問題在x86平臺上卻沒有問題。
于是我通過用以下命令,分別來生成不同優(yōu)化選項(xiàng)下的匯編代碼,來確定在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
然后對比三個匯編的代碼,發(fā)現(xiàn)問題出在memcpy這句話上。
在point0.s中,程序是老老實(shí)實(shí)的調(diào)用的memcpy,然后就將0x12345678老老實(shí)實(shí)按照字節(jié)一個個的放到了buffer+7的位置。
而在point1.s中程序則是沒有調(diào)用memcpy,而是用的語句:
str r3, [sp, #7]
而此時r3中存儲的就是0x12345678;而由于我采用的ARM平臺是32位的,此語句執(zhí)行時,地址線應(yīng)該不會發(fā)生變化,所以最終的結(jié)果是buffer+4到buffer+7的數(shù)據(jù)被覆蓋了,而不是buffer+7到buffer+10的數(shù)據(jù)被修改。
而在point2.s中,貌似又針對流水線進(jìn)行了優(yōu)化,程序執(zhí)行順序會有所變化,在對buffer部分位置賦初值的順序是在str r3, [sp, #7]之后,所以buffer+6處的數(shù)據(jù)反而是正確的6。
分析到這兒,也許有人會說寫個簡單的程序,都會因?yàn)榫幾g的優(yōu)化選項(xiàng)不同導(dǎo)致結(jié)果不同,那這memcpy是不是就不敢用了?
其實(shí)一般只要有較好的編程習(xí)慣的話,都不會遇到此類問題,比如下面的程序:
#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;
}
這段程序其實(shí)只是簡單的改變了p的類型,就能保證在各種優(yōu)化下,結(jié)果都一樣??梢姾玫木幊塘?xí)慣是有多么的重要。
相關(guān)文章
Qt串口通信開發(fā)之Qt串口通信模塊QSerialPort開發(fā)完整實(shí)例(串口助手開發(fā))
這篇文章主要介紹了Qt串口通信開發(fā)之Qt串口通信模塊QSerialPort開發(fā)完整實(shí)例(串口助手開發(fā)),需要的朋友可以參考下2020-03-03
C++繼承中的對象構(gòu)造與析構(gòu)和賦值重載詳解
這篇文章主要為大家詳細(xì)介紹了C++繼承中的對象構(gòu)造與析構(gòu)和賦值重載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
C++實(shí)現(xiàn)LeetCode(64.最小路徑和)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(64.最小路徑和),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
VC實(shí)現(xiàn)給窗體的一個按鈕添加事件的方法
這篇文章主要介紹了VC實(shí)現(xiàn)給窗體的一個按鈕添加事件的方法,通過三個簡單步驟實(shí)現(xiàn)窗體按鈕添加事件,需要的朋友可以參考下2015-05-05
C語言與C++項(xiàng)目實(shí)現(xiàn)相互調(diào)用
extern?“c”的作用可以實(shí)現(xiàn)c語言和c++相互調(diào)用,本文就詳細(xì)的介紹一下C語言與C++項(xiàng)目實(shí)現(xiàn)相互調(diào)用,感興趣的可以了解一下2022-01-01
win32 api實(shí)現(xiàn)簡單的消息窗口示例
這篇文章主要介紹了使用win32 api實(shí)現(xiàn)簡單的消息窗口示例,需要的朋友可以參考下2014-03-03

