使用sprintf時溢出怎麼會影響到變數的值?

最近在MSP430G2553(一款微控制器)上寫程序,用到了stdio.h里的sprintf函數,其實老發現有錯誤,後來單步調試發現當我運行sprintf函數第三次的,有幾個全局變數的值被改變了,第一感覺就是sprintf使什麼東西溢出了,覆蓋了原先的數據。我想問的問題是:

1、就是標題。

2、遇到這種問題時想要不上知乎獨立解決這個問題(計算機工作原理?編譯原理?)需要哪些知識,往大家推薦點書或者名詞我索引索引。

還有如果單純問問題1,那我就不會在知乎問了。

給出詳細信息:

我在另一個文件a.h中定義了

//模式枚舉體


typedef enum


{


ConstVolMode = 0,


ConstCurMode


}ModeDef;


//模式枚舉體


extern ModeDef ConstMode;


在a.c里我初始化為

ModeDef ConstMode = ConstVolMode;


然後在另一個文件b.c中include了a.h


可是我運行的時候發現執行到一句的時候ConstMode 的值被改變成一個莫名其妙的量。。


如圖:

當我執行到

sprintf(Strline3, "輸出電流:%.3fA", ADC_FiltedValue[1]);時


我跳到了彙編內運行。單步跳到00D9DE處,此時ConstMode的值還是ConstVolMode,截了個圖,按下一步得到下圖。

明顯ConstMode的值被改成了一個奇怪的值。


關於第一個問題,這其實只是 C 語言的基礎問題,查看相關文檔可解決。sprintf 本身不是安全的,它會無條件往第一個參數的地址寫數據,無論這塊數據空間是誰的。當然,在格式化串的時候也可能發生問題只是概率較低,目前想來可能有問題的就這兩處,首先當然你應當去找 Strline3 的責任。

關於第二個問題,其實用 Linux 的話,標準庫函數直接 man 就好了。不行去問問 stackoverflow 也是好的,順帶提高英語讀寫技能。

順便說一下:也許會有很多人說你這程序寫得不好,全局變數亂飛什麼的。這其實也是事實,只不過很多時候程序能用,沒太多人去糾結這些細節。但你現在程序有問題了,再來看的時候,會發現你這個函數裡面冒出來的變數好像全是全局量,這對你查問題其實非常不利。


如果你的程序是運行在x86的windows的話,那麼sprintf用的是cdecl這個calling convention。sprintf要求第一個參數是緩衝區的指針,而且他在輸入的時候是不檢查緩衝區的大小的。在這裡推薦使用VC++的sprintf_s函數。如果你的StrlineX是一個數組,不管是全局變數也好,函數的局部變數也好(這個更危險),那StrlineX的「後面」肯定是別的變數了。於是如果你提供給sprintf函數的緩衝區的尺寸不夠,那他就會把你的東西慢慢覆蓋到你接下來的所有變數裡面。

也就是說,如果你這麼聲明的話:

char a[10];

char b;

char c;

某些編譯選項下面,a[10]==b, a[11]==c這種情況是會出現的。

VC++在debug下面會在變數中間放(通常是12個位元組的)某些值,退出函數的時候他會檢查一下這些值是不是被改寫了,如果是證明你搞錯了什麼事情,就掛了。這是release沒事但是debug有事的其中一種常見原因——因為release通常沒有檢查,而且函數的本地變數用完即棄,剛好你也沒有寫得太遠。


1. 關於第一個問題:

ConstMode是個全局變數,它在進程的地址空間中屬於數據區。

對於sprintf函數,第一個參數是個指針,指向一個緩衝區,存入輸出的內容。

結合你的程序邏輯和執行情況,排除ConstMode並發地從其它程序流程修改,

那麼唯一的可能就是Strline3在緊臨數據區中ConstMode的地方,導致輸出到

Strline3這個緩衝區後,超出限制,繼續覆蓋後續的本來屬於ConstMode的內存

區域。(我猜想很有可能ConstMode和Strline3這兩個變數都是定義為全局變數,

並且,在程序鏈接後,Strline3分配在ConstMode地址之前,導致了這種可能性,

如果你能把程序定義這兩個變數的部分給出,更有助於分析)

2. 關於第二個問題:

1.了解程序進程地址空間概念,知道全局變數,臨時變數的區別是如何體現在進程空間上的。

這對於了解緩衝區溢出,堆溢出的原理很重要。你這個程序的原因就是,緩衝區長度分配有限

 ,這個緩衝區目前看很有可能是定義為全局變數(即在數據段),它的長度在程序開始時就固定  了,這就百分百可以觸發緩衝區溢出問題。這顯然就不是合理的做法,更合理的做法是根據要輸

 出數據的長度,動態地在堆區域分配(malloc或new).

 如果對進程地址空間有了概念,很多這種問題能避免,或者幫助分析。

  • 某一操作系統書籍中關於進程內存空間章節的描述,

  • 編譯,鏈接的一些知識,《程序員的自我修養—鏈接、裝載與庫》是不錯的入門書

2. 學習必要的彙編知識,了解函數的棧幀的結構,這對於分析緩衝區溢出問題也是必備的。(

過,你的例子並不是在棧區的溢出,而是在數據區的溢出)

  •  任一本彙編書籍中的關於函數部分的論述章節


《深入理解計算機系統》絕對是你想要的那一本:

深入理解計算機系統(原書第2版) (豆瓣)


sprintf不會檢查緩衝區長度,在一些編譯選項下會造成超過長度bug,也就是你提出的問題,如果是windows下vc++的話,盡量用安全函數,sprintf_s


推薦閱讀:

小弟做c++伺服器差不多一年了,用ace框架的,還沒什麼信心,還很菜,請教各位大神如何提升進階啊?
計算機專業C++應該怎麼教?
計算機中缺失MSVCP120D.dll和MSVCR120D.dll怎麼解決?
多幀圖片的數據存儲的問題?
從項目管理上來說,C++ 是否適合做大的項目?

TAG:程序員 | C編程語言 | C | 編譯原理 | 嵌入式開發 |