個人認為SSD6 Exercise 1是卡內基有史以來最經典的題目,本來想寫一篇大家都看得懂的長篇大論,無奈時間不夠,只好延續以往記流水帳的風格,把心得一條一條列出來。
心得1.從彙編的層次去理解c語言傳值和傳地址的區別.看執行個體
- int main (int argc, char *argv[]) {
- int start = 10;
- int stride = 3;
- int key3 = -1;
- int key4 = 777;
- char * msg1;
- process_key34(&key3,&key4); //傳地址
- msg1 = extract_message(start,stride); //傳引用
- return 0;
- }
把這段代碼對應的彙編為:
- process_keys34(&key3, &key4);
- lea -0x28(%ebp),%eax 取key3的地址,即&key3
- mov %eax,0x4(%esp) 把&key3存到%esp+4
- lea -0x24(%ebp),%eax 取key4的地址,即&key4
- mov %eax,(%esp) 把&key4存到%esp
- call 804858d 調用函數process_keys34
- msg1 = extract_message1(start, stride);
- mov -0x10(%ebp),%eax 取實參start的值
- mov %eax,0x4(%esp) 得到start的副本,存到%esp+4
- mov -0x14(%ebp),%eax 取實參stride的值
- mov %eax,(%esp) 得到stride的副本,存到%esp
- call 80485ba 調用extract_message1方法
- mov %eax,-0xc(%ebp) 把傳回值賦給msg1
調用process_keys34之前和之後的棧幀結構如下:
調用前main函數的棧幀: 調用後main函數和process_keys34函數的棧幀:
%ebp <-----幀指標%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----棧指標%esp ........
&key3 <--------實參&key3
&key4 <--------實參&key4
返回地址
%ebp <-----幀指標%ebp
局部變數
...... <-----棧指標%esp
返回地址是main函數調用process_keys34後繼續執行地方,如果我們想讓程式執行完process_keys34後跳到其他地方,就可以修改這個返回地址,要想修改返回地址的值,必須先得到儲存返回地址的那部分記憶體位址,在gdb裡面可以通過$ebp+4得到,而在程式裡面只能通過返回地址上面那個變數得到,即&key4對應的地址減1.理解了這點是解題的關鍵。
同理調用extrat_message之前和之後的棧幀結構如下:
調用前main函數的棧幀: 調用後main函數和extrat_message函數的棧幀:
%ebp <-----幀指標%ebp %ebp
....... .......
start start
stride stride
key3 key3
key4 key4
msg1 msg1
....... <-----棧指標%esp ........
stride的副本 <--------實參stride
start的副本 <--------實參start
返回地址
%ebp <-----幀指標%ebp
局部變數
...... <-----棧指標%esp
心得2:指標符*和地址符&的混合計算
1. (char *) data + j 與 data + j
data是一個int型的數組,data表示數組的首地址,我們知道地址其實是一個整數,任何地址都一樣,一個指向int的地址和一個指向char的地址本質上是一樣的,都是整數。但,c語言的編譯器會區別這兩種指標,如果指正ptr指向int,那麼 ptr + 1 的地址為 <ptr + 4>:ptr的地址加上4個位元組,如果ptr指向char,那麼 ptr + 1 的地址為 <ptr + 1>:ptr的地址加上1個位元組。當執行指標的加減法運算時,c語言會根據指標所指向的變數類型來確定需要加減多少地址。這裡先把data強制轉換成char類型的指標,那麼
(char *) data + j 的真真實位址是: <data + j>
data + j 的真真實位址是: <data + 4*j>
2. *( (int *)&key3 + 1 ) + 1 與 *(&key3 + 1) + 1
其中 int a = 4; int * key3 = &a;所以 key3是一個指向int型的指標,也可以說key3的變數類型為(int *),那麼&key3就是指向指標的指標,變數類型為(int **),比key3多了一個星號,但兩者本質是一樣的,都是地址,都是一個整數,都佔4個位元組,且加1後都會在原來的地址上再加4個位元組,唯一的卻別就是這兩個地址存放變數的類型不同,*( (int *)&key3 + 1 )是一個int型的值,而
*(&key3 + 1)是一個(int *)的值,兩者分別加1運算後,前者加1,後者加4,這就是區別。
3.指標和地址的區別
指標是變數,地址是值,指標的值本質上是一個地址