函式指標的值不是函式位址

2021-05-25 02:28:46 字數 4125 閱讀 6127

最初發布在: 

在寫跑在main之前

的時候,碰到了很奇怪的問題。

int initbreak()

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=

0:000> kpl

childebp retaddr

0012ff34 0041272b msvcr100d!_initterm_e(

** pfbegin = 0x0041641c,

** 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

004166240041118b00000000 00000000 00000000

0:000> dd start4 l4

004168380041118b00000000 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即可,代價是鏈結時間的變長,沒有兩全其美的方法。

函式指標位址

strlen函式 返回字串s長度 int strlen char s define allocsize 10000 可用空間大小 static char allocbuf allocsize alloc使用的儲存區 static char allocp allocbuf 下乙個空閒位置 char a...

171029 函式自學 函式位址和函式指標

前言 本篇多數說法來自個人理解。在理論 邏輯上 int a int b a cout a 取變數位址 cout a 間址訪問再看 int func func func 取函式位址,但函式只要你提供位址它就可以被訪問。所以這樣的形式也可以呼叫函式。二.函式型別 一類相同函式的抽象 注意 函式型別,取決...

返回指標值的函式

函式可以不返回值,可以返回整數 浮點數 字元型別等,也可以返回指標型別資料。如字串複製函式strcpy的函式原型為 char strcpy char strdestination,const char strsource 如字串連線函式strcat的函式原型為 char strcat char st...