精華區beta C_and_CPP 關於我們 聯絡資訊
我想請問的是printf中%d和%f的事實上是如何作用的。 例如說printf("%s");會記憶體錯誤,但printf("%d");只是輸出一個垃圾值, 是什麼原因造成這種差別? -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 210.60.107.236
twotwoone:中斷點,Step into 12/21 19:02
suhorng:啊, 囧, 全國賽筆試題 12/22 19:41
> -------------------------------------------------------------------------- < 作者: jlovet (偷拿程式碼的八卦) 看板: C_and_CPP 標題: Re: [語法] %d和%s的實際行為? 時間: Mon Dec 21 19:15:05 2009 ※ 引述《raincole (冷雨)》之銘言: : 我想請問的是printf中%d和%f的事實上是如何作用的。 : 例如說printf("%s");會記憶體錯誤,但printf("%d");只是輸出一個垃圾值, : 是什麼原因造成這種差別? printf 有很多參數 第一個是格式 "%s" or "%d" ... 然後後面一個一個是要印出來的值 當你沒有指定的時候 也不知道他是去stack還是哪裡讀得參數... 那如果讀到1000,他就印出數字1000 或是,他就去記憶體位址1000的地方找字串來印... 問題是那個位址不一定是有效的位址...所以就出錯了 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 211.74.186.18
raincole:我就是問他到底會去哪裡讀參數..?還是沒規範? 12/21 19:56
raincole:我猜讀字串他會一直讀直到找到\0而記憶體錯誤 12/21 19:57
raincole:但是%d呢? 12/21 19:57
MOONRAKER:從自己的堆疊區裡面取值出來,指到哪裡就是哪裡。 12/21 20:12
jlovet:va_list,va_args,... 12/21 20:23
raincole:所謂「堆疊區」代表是已宣告的記憶體?所以不會出錯? 12/22 00:12
LPH66:應該說它預期在那裡要有一個值告訴他資料是什麼 12/22 05:12
LPH66:這個「那裡」(你想問的) 就是你傳參數時參數放在的位置 12/22 05:13
LPH66:那裡由於是個堆疊 故也叫堆疊區 12/22 05:13
> -------------------------------------------------------------------------- < 作者: legnaleurc (CA) 看板: C_and_CPP 標題: Re: [語法] %d和%s的實際行為? 時間: Tue Dec 22 12:07:10 2009 ※ 引述《jlovet (偷拿程式碼的八卦)》之銘言: : 標題: Re: [語法] %d和%s的實際行為? : 時間: Mon Dec 21 19:15:05 2009 : → raincole:我就是問他到底會去哪裡讀參數..?還是沒規範? 12/21 19:56 : → raincole:我猜讀字串他會一直讀直到找到\0而記憶體錯誤 12/21 19:57 : → raincole:但是%d呢? 12/21 19:57 沒有這麼複雜吧 ... 推文都有人說它是用 va_list 做了 ... 下面是簡單的做法: void printf( const char * format, ... ) { char * f = NULL; int amount = _parse( format, f ); va_list vl; va_start( vl, amount ); for( int i = 0; i < amount, ++i ) { if( f[i] == 'd' ) { int val = va_arg( vl, int ); // print as int } else if( f[i] == 's' ) { const char * val = va_arg( vl, const char * ) ; // print as string } else { // blah blah } } va_end( vl ); free( f ); } 簡單的說,va_list 的參數其型別資訊會消失 單看你給的格式字是什麼它就強制轉型成什麼 -- 自High筆記(半荒廢) http://legnaleurc.blogspot.com/ -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 123.205.248.119 > -------------------------------------------------------------------------- < 作者: suhorng ( ) 看板: C_and_CPP 標題: Re: [語法] %d和%s的實際行為? 時間: Tue Dec 22 20:18:26 2009 在 IA32 with Windows or Linux 之下, 每個程式都有一塊用途特殊的記憶體區, 稱為堆疊, 由 ESP 指向其頂端 (EBP 也可) 對於呼叫與回返的指令 (CALL / RET), 它會把下一步要執行的指令位址 (由 EIP 指向) 推到堆疊裡 / 從堆疊拿出來, 並且 ESP 也會做增減的動作 PUSH 時是 ESP 先減 4 再放值, POP 時是先取值 ESP 再加 4 當你使用過量時就會 RE. 所以要小心遞迴過深. 此外對於大部分的 C Compiler, 會把區域變數以及函式呼叫的參數都放在堆疊當中 以區域變數來說的話, 許多 Compiler 會直接把 ESP 增減一個值, 然後用 EBP 去指向原先 ESP 的位址, 以 EBP ~ ESP 的區域做為你的堆疊框架 區域變數就是放在這之中, 這也是為什麼開太大的區域變數會 RE 此外對於函式呼叫的部份, 除了在 CALL 指令時會把下一個指令的位址推入堆疊 在 CALL 之前編譯器還會把呼叫的參數推入堆疊 以 C 調用者約定來說, 是 : 把參數由右到左推入堆疊、調用者清理堆疊 也就是說 C 的被調用者可能不知道你到底給了他多少參數, 因此 C 支援可變長度參數, 如 printf, scanf 就是可變長度的 (用 '...' ) 宣告 假設有個函式 void Q(int a, int b, int c) { } 那你呼叫 Q 時, 在 Q 建立自己的堆疊框架前, 堆疊可能長得像這樣 | ? | +--------+ | c | <-- ESP+C +--------+ | b | <-- ESP+8 +--------+ | a | <-- ESP+4 +--------+ |ret addr| <-- ESP +--------+ | ? | 而 printf 函式就是從 "假定" 有參數的地方開始去找 比如有可能在建立堆疊框架後, EBP+8 是 const char* format 那麼依序下去 EBP+C, EBP+10, EBP+14, ... 就該是你一去傳給他的參數了 比如我們可以直接用 printf("%p\n%p\n%p\n%p\n"); 來印出堆疊中部份的內容 至於函數的回傳值則是視情況放在 al / ax / eax / edx:eax 當中 (char ~ long long) 那題給的程式是 #include <stdio.h> void main() { printf("%s%s%s%s%s%s%s%s%s%s%s%s%s"); // 我也不知道有幾個 '%s' ... } 這樣幾乎一定會 RE 有可能, 第一個 %s 抓到 Old EBP, 第二個抓到 main ret addr (probably to CRT) 第三個抓到 char** argv, 第四個抓到 int argc, 再來就不知道可能是什麼了 ※ 引述《raincole (冷雨)》之銘言: : 我想請問的是printf中%d和%f的事實上是如何作用的。 : 例如說printf("%s");會記憶體錯誤,但printf("%d");只是輸出一個垃圾值, : 是什麼原因造成這種差別? -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.137.66.67
hilorrk:跪下了... 12/22 20:49
suhorng:忽然發現某些細節有寫錯 ... Orz 12/22 21:05
shik:淚推.. 12/22 21:21
bibo9901:推強者 12/22 21:49
小修一下內文錯誤:P ※ 編輯: suhorng 來自: 220.137.66.67 (12/22 22:13)
VictorTom:拜....<(_ _)> 12/22 22:39