天天看點

printf 位址_記64位位址截斷引發的挂死問題

作者: 守望,Linux應用開發者,目前在公衆号【程式設計珠玑】 分享Linux/C/C++/資料結構與算法/工具等原創技術文章和學習資源

前言

最近要将整個項目的代碼從原先的隻支援32位變成同時支援32位和64位,這個過程中遇到一個很不容易定位的挂死問題,花了不少時間才定位解決,是以分享給大家。

32位和64位代碼差別

在分享之前,需要了解一下32位和64位程式代碼有何差別,它的主要差別展現在某些資料類型的占用位元組大小的不同:

資料類型 32位 64位
long 4位元組 8位元組
unsigned long 4位元組 8位元組
指針 4位元組 8位元組
size_t 4位元組 8位元組
ssize_t 4位元組 8位元組

這些是主要的差别。

那麼為什麼要切64位呢?原因也很簡單,32位尋址範圍有限,能使用的最大記憶體也是非常有限的,是以需要使其能夠支援64位,這個過程需要修改編譯工程,編譯第三方庫為64位,修改代碼等等。當然這些都不是本文的重點,本文僅介紹遇到的這個典型的問題。

問題描述

由于項目本身涉及的系統比較複雜,是以簡單分享一下定位過程,下一節将通過簡潔的示例程式來說明。

問題現象:向伺服器發送一條操作指令後直接挂死

分析解決過程簡化為如下步驟:

  • 檢視日志以及coredump資訊,初步定位挂死的位置
  • 發現挂死在停止定時器的位置
  • 32位程式正常,而64位異常,是以和32位與64位的差别有關
  • 懷疑傳入定時器資料有問題,編寫小demo,排除傳入資料問題
  • 編譯可調試版本,加入-g參數
  • 跟蹤調試,發現最終挂在了一個動态庫中
  • 設定gdb源碼路徑,以便調試跟蹤動态庫
  • 通過gdb觀察傳入指針,在通路指針時,出現錯誤,提示通路非法記憶體
  • 列印傳入定時器指針位址,發現異常,位址開頭4位元組為全f,不正常,是以懷疑該指針最開始就已經出問題
  • 跟蹤啟動定時器部分,動态庫接口傳回的位址值,就已經異常了。但是跟蹤到動态庫接口内部,發現傳回的結果是正常的8位元組位址值,排除定時器接口的問題
  • 最終可以确定,在調用動态庫接口時,雖然傳回的是8位元組位址,但是賦給外部變量時,就被截斷了
  • 換項目中的另外一個程序調試demo發現,編譯時出現錯誤,提示函數沒有聲明
  • 于是加上聲明之後編譯通過,但并沒有出現挂死的問題
  • 随即繼續跟蹤原項目出問題的程序,發現同樣這些接口都沒有外部聲明,再加上另外一個程序的警告資訊,提示有int往指針強轉,是以懷疑和函數的聲明有關。

最終确實如此。

具體是為什麼呢?

簡化示例

示例代碼分别放在main.c和test.c中,main.c内容如下:

test.c的内容如下:

上面兩段代碼再簡單不過,testFun在函數中申請一段記憶體,并傳回。而main函數通過調用testFun,将位址值傳回給p,并列印p的位址值。

編譯運作:

從運作結果中,我們可以發現以下幾個事實:

  • 64位程式位址為8位元組
  • testFun内部申請到的記憶體位址值是占用8位元組的值
  • main函數中的p的位址值為4位元組
  • 傳回值被截斷了

也就是和我們預期的結果完全不一樣。我們逐漸分析,到底是為什麼。

特别說明:如果指派那一行改成下面這樣

運作結果中如下:

其實看到8位元組的前面4位元組都是f,就可以判斷這個位址是非法的了。為什麼?(提示:程式位址空間分布)。

為什麼coredump?

這個問題很明顯,因為申請記憶體得到的位址值與釋放記憶體的位址不是同一個,是以導緻coredump(coredump的檢視可參考《linux常用指令-開發調試篇》中的gdb部分)。

為什麼位址值被截斷?

在解釋這個之前,我們先看一個簡單的示例程式:

編譯:

我們在編譯的時候出現了一個警告,提示test函數沒有傳回值,會預設傳回值為int。

也就是說,如果函數實際有傳回值,但是函數傳回值類型卻沒有指明,編譯器會将其預設為int。

實際上前面的示例程式在編譯的時候就有警告:

兩個警告的意思分别為:

  • testFun沒有聲明
  • 嘗試從整形轉換成指針

第一個警告很容易了解,雖然定義了testFun函數,但是在main函數中并沒有聲明。是以對mian函數來說,它在編譯階段(關于編譯階段,可參考《hello程式是如何變成可執行檔案的》),“看不到”testFun,是以會預設為其傳回值為int。而正因如此,就有了第二個警告,提示從整型轉換成指針。

到此其實也就真相大白了。既然testFun的傳回值被編譯器預設為int,傳回一個8位元組的指針類型,而傳回值卻是int,自然就會被截斷了。

如何解決

既然知道原因所在,那麼如何解決呢?這裡提供兩種方式。

  • extern聲明
  • 在頭檔案中聲明,調用者包含該頭檔案

按照第一種方式,在main.c中增加一行聲明:

運作結果:

第二種方式,增加test.h,内容為testFun的聲明:

main.c包含test.h頭檔案:

test.c修改如下:

以上兩種方式都可解決前面的問題。

而32位程式為什麼正常?相信你已經有了答案。

總結

由于對出現問題的程式代碼不熟悉,加上其編譯工程充斥着大量的警告而沒有處理,以及涉及動态庫,導緻這個引起挂死問題的罪魁禍首沒有提前暴露處出來。而問題的根本原因我們也清楚了,就是因為調用函數前沒有聲明。本文總結如下:

  • 不要忽略任何一個警告,除非你非常清楚地知道自己在做什麼
  • 在頭檔案中聲明函數,并提供給調用者
  • 函數使用前進行聲明
  • 問題長期定位不出來時,休息一下
  • 盡量編寫通用性代碼
  • 非必要時不強轉
  • 使用void *指針格外小心

思考

  • 為什麼32位的時候運作正常,而64位程式會挂死
  • 32位和64位程式使用者空間位址範圍分别是多少
  • 如何在調試中設定程式源碼路徑
  • 程式完整編譯經曆那幾個階段

●編号532,輸入編号直達本文

●輸入m擷取文章目錄

C語言與C++程式設計

printf 位址_記64位位址截斷引發的挂死問題

分享C/C++技術文章