函數指標的值不是函數地址?

來源:互聯網
上載者:User

最初發布在: http://user.qzone.qq.com/31731705/blog/1302859584

在寫跑在main之前的時候,碰到了很奇怪的問題。

int initBreak(){DebugBreak();return 0;}typedef int (*pInit)();pInit start3 = initBreak;

initBreak是函數名,start3 是指標,它們的值竟然不一樣。

開始學習C語言的時候,就知道函數名代表函數地址,可以被賦值給函數指標,方便後面的調用。為什麼這裡的值會不一樣了?

還是使用 跑在main之前 (2) 代碼例子,繼續用windbg分析。

0:000> g
Fri Apr 15 17:03:10.492 2011 (UTC + 8:00): Breakpoint 0 hit
eax=00000000 ebx=7ffdf000 ecx=0041956c edx=00130000 esi=00000000 edi=00000000
eip=0f9186a6 esp=0012ff30 ebp=0012ff34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
MSVCR100D!_initterm_e+0x6:
0f9186a6 c745fc00000000 mov dword ptr [ebp-4],0 ss:0023:0012ff30={testC!__native_startup_lock (0041956c)}
0:000> kPL
ChildEBP RetAddr
0012ff34 0041272b MSVCR100D!_initterm_e(
<function> ** pfbegin = 0x0041641c,
<function> ** pfend = 0x00416a40)+0x6

0012ff80 0041263f testC!__tmainCRTStartup(void)+0xdb
0012ff88 77773c45 testC!mainCRTStartup(void)+0xf
0012ff94 77ce37f5 kernel32!BaseThreadInitThunk+0xe
0012ffd4 77ce37c8 ntdll!__RtlUserThreadStart+0x70
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> dd start l4
00416728 00411186 00411023 0041118b 00000000
0:000> dd start2 l4
00416208 00411186 00411023 0041118b 00000000
0:000> dd start3 l4
00416624 0041118b 00000000 00000000 00000000
0:000> dd start4 l4
00416838 0041118b 00000000 00000000 00000000
0:000> ln 41118b
(0041118b) testC!ILT+390(_initBreak) | (00411190) testC!ILT+395(__controlfp_s)
Exact matches:
0:000> ln start3
(00416624) testC!start3 | (00416728) testC!start
Exact matches:
testC!start3 = 0x0041118b

0:000> ln start4
(00416838) testC!start4 | (0041693c) testC!pinit
Exact matches:
testC!start4 = 0x0041118b
0:000> ln initBreak
d:\project\mine\vs.net\testc\testc\testc.c(47)
(004120f0) testC!initBreak | (00412150) testC!main
Exact matches:
testC!initBreak (void)

能夠看到,start3和start4均為0x0041118b,而函數initBreak則是004120f0,它們的值並不一樣,這是為什麼呢?

再前進一點點就有答案了,

0:000> u 41118b -8
testC!ILT+380(__RTC_Initialize)+0x2:
00411183 250000e985 and eax,85E90000h
00411188 0e push cs
00411189 0000 add byte ptr [eax],al
testC!ILT+390(_initBreak):
0041118b e9600f0000 jmp testC!initBreak (004120f0)

testC!ILT+395(__controlfp_s):
00411190 e987300000 jmp testC!controlfp_s (0041421c)
testC!ILT+400(__StackOverflow):
00411195 e9d6040000 jmp testC!_StackOverflow (00411670)
testC!ILT+405(_GetSystemTimeAsFileTime:
0041119a e90d310000 jmp testC!GetSystemTimeAsFileTime (004142ac)
testC!ILT+410(_f1):
0041119f e96c0d0000 jmp testC!f1 (00411f10)

真相如此簡單,就是一條5個位元組的JMP指令。

另一個問題隨之而來,編譯器為何如此做呢,不是降低效率,多此一舉嘛。Google了一下,答案在此:

什麼是Incremental Link Table呢?

假如一個程式有連續兩個foo和bar (所謂連續,就是他們編譯串連之後函數體連續存放), foo入口位置在0x0400,長度為0x200個位元組,那麼bar入口就應該在0x0600 = 0x0400+0x0200。程式員在開發的時候總是頻繁的修改code然後build,假如程式員在foo裡面增加了一些內容,現在foo函數體佔0x300個位元組了,bar的入口也就只好往後移0x100變成了0x0700,這樣就有一個問題,如果foo在程式中被調用了n次,那麼linker不得不修改這n個函數調用點,雖然linker不嫌累,但是link時間長了,程式員會覺得不爽。所以MSVC在Debug版的build,不會讓各個函數體之間這麼緊湊,每個函數體後都有padding(全是彙編代碼int 3,作用是引發中斷,這樣因為古怪原因運行到不該啟動並執行padding部分,會發生異常),有了這些padding,就可以一定程度上緩解上面提到的問題,不過當函數增加內容太多超過padding,還是有問題,怎麼辦呢?MSVC在Debug build中用上了Incremental Link Table, ILT其實就是一串jmp語句,每個jmp語句對應一個函數,jmp的目的地就是函數的進入點,和沒有ILT的區別是,現在對函數的調用不是直接call到函數進入點了,而是call到ILT中對應的位置,而這個位置上什麼也不做,直接jmp到函數中去。這樣的好處是,當一個函數入口地址改變時,只要修改ILT中對應值就搞定了,用不著修改每一個調用位置,用一個冗餘的ITL把時間複雜度從O(n)將為O(1),值得,當然Debug版的二進位檔案會稍大稍慢,Release版不會用上ILT。

所以,想得到正確的結果,disable incremental linking即可,代價是連結時間的變長,沒有兩全其美的方法。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.