推 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