2013-03-11 35 views
1

我只是花了半天的時間來計算x86-64代碼中的一次微妙崩潰,所以這是對別人的單挑 - 我沒有在別處看到過這種情況。在x86-64中返回指針的未聲明函數導致無效指針擴展

如果您在沒有正確聲明的情況下使用libc函數,那麼gcc 會認爲它返回int。例如。 setlocale()假定爲int setlocale(),在EAX中返回一個32位的int值。

嘗試將此返回值轉換爲指針,無論是通過隱式轉換還是顯式轉換,都將強制通過符號擴展從32位轉換爲64位,即使被調用函數返回了有效的64位指針值RAX!例如。

char *p = setlocale(0, 0);  // bear with me for a second 

被編譯到

1c: b8 00 00 00 00   mov $0x0,%eax 
    21: e8 00 00 00 00   callq 26 <hard_locale+0x26> 
    26: 48 98     cltq ; <--- eax is expanded in rax 

GCC甚至試圖告訴你這一點:

warning: initialization makes pointer from integer without a cast 

如果添加一個明確的轉換,警告變化,這說明這個問題:

warning: cast to pointer from integer of different size 

如果你幸運的話,什麼都不會發生,但如果它發生在內存的指針返回最多一個較大的值,它會被搞砸了,如下:

function returns in RAX: 0x07ffff7b9705e 
    cltq considers EAX with negative sign: 0xf7b9705e 
    now RAX is: 0xfffffffff7b9705e 

,你的指針是無效的。

修復和解決方案:

  • 總是用正確的函數聲明

  • -Wall -Werror應在x86-64的編譯器拖欠。

+0

嗯,是的,如果你沒有聲明你使用的函數,你會有一個糟糕的時間。我認爲這是衆所周知的,因爲需要始終啓用盡可能多的編譯器警告。我不認爲拼出很多程序集的基本類型錯誤是非常有啓發性的...... – 2013-03-11 13:30:09

+0

這是C的預期行爲。解決方案是修復由編譯器引發的任何警告,其中包括「call未申報的功能「。 – 2013-03-11 13:32:20

+1

這個問題應該歸咎於那些決定將int保留在32位的編譯器編寫者, 「int」爲64位(平臺的自然數據大小)的64位數據模型可以很好地處理這種情況。 – ecatmur 2013-03-11 14:42:14

回答

3

未申報的功能不是有效的C。只需添加-Werror=implicit-function-declaration,問題就會迎刃而解。你不需要以使其他警告(主要是對sylistic的考慮)進入錯誤狀態

這裏是一個GCC拒絕無效C錯誤的警告錯誤選項列表,沒有任何誤報。它錯過了一些東西(這GCC有捕不支持),但它主要完成:

  • -Werror=implicit-function-declaration
  • -Werror=implicit-int
  • -Werror=pointer-sign
  • -Werror-pointer-arith
  • -Werror=return-type
  • -std=c99(或-std=c11等根據需要)
  • -pedantic-errors(可選;拒絕一些代碼,是有效但不完全便攜式)

請注意,我最初還增加-Werror=sequence-point-Werror=array-bounds到列表中,但他們有誤報,因爲他們沒有標記違反約束,僅在運行UB。因此,只要調用UB的代碼不可訪問,包含這些警告的程序仍然可以是正確的程序(作爲一個很好的例子,考慮如if (sizeof(int)==sizeof(long)) { ... } else { ... }這樣的分支,其中未採取的分支根據結果調用UB運營商sizeof)。