1. 程式人生 > >深入剖析C函式引數的結合順序及a++和++a的區別

深入剖析C函式引數的結合順序及a++和++a的區別

C語言函式引數的結合順序

今天上課時老師提出了一個關於C語言的函式引數的結合順序的問題以及a++和++a有什麼區別的問題,為了弄清楚這個問題,我寫了如下的程式碼反彙編進行分析:

#include <stdio.h>
#include <stdlib.h>

int func(int a, int b, int c, int d);

int main()
{
    int a = 1;
    int b = 2;
    func(a++, ++a, ++a, a++);
    system("pause");
}

int func(int a, int
b, int c, int d) { printf("%d %d %d %d\n", a, b, c, d); return 0; }

在這個程式中,我再呼叫func的那一行程式碼設定斷點,並且進行反彙編,反彙編後的程式碼:

這裡寫圖片描述

由於呼叫func函式時,傳入的引數都是a,因此呼叫func函式的那一行程式碼反彙編後都是對a進行操作的程式碼,不容易判斷出各個引數的執行順序,所以我將呼叫func時改為傳入四個不同的引數,修改程式碼如下:

這裡寫圖片描述

重新反彙編:

這裡寫寫圖片描述

從上面的彙編程式碼就很容易看出來了,呼叫func這個函式時,會依次將d、c、b、a四個引數從記憶體中取出來,然後壓入堆疊中,最後使用call呼叫func函式,因此可以推斷C語言函式引數的執行順序是從右至左的

a++及++a的區別

弄清楚了函式引數的執行順序就可以繼續通過反彙編弄清楚a++和++a的區別了。
還是使用最開始的程式碼:

這裡寫圖片描述

根據上面引數執行順序的規則,執行func(a++, ++a, ++a, a++)時引數從右至左執行,於是我進行判斷,最右的a++是先使用值再自加,於是傳入1,從右至左第二個a這時等於2,因為是前置++,所以判斷傳入3,從右至左第三個引數同理判斷傳入4, 從右至左第四個引數因為是後置++,因此判斷傳入4,最後執行完後a=5,所有我判斷傳入引數是4 4 3 1,但是執行結果:

這裡寫圖片描述

為什麼會產生這樣的結果呢?通過對呼叫func的這一行程式碼進行反彙編:

這裡寫圖片描述

通過引數從右向左執行的原則,可以推斷出從右至左的a++、++a、++a、a++對應的彙編程式碼,從這裡我們就看出來了a++和++a的區別:

a++:

這裡寫圖片描述

++a:

這裡寫圖片描述

從上面可以看出,++a是將a從記憶體中取出,存入暫存器,加1後再送回a所在的記憶體空間中;而a++則是先將a從記憶體中取出來,再存入ebp暫存器中的內容減去0DC所得值作為記憶體偏移地址所在的記憶體空間中,為什麼要先將a存入另外的記憶體空間呢?繼續分析引數執行完後的彙編程式碼:

這裡寫圖片描述

在程式真正跳轉到func之前,還執行了這些程式碼,這些程式碼的作用就是將函式的引數壓入棧中,最後呼叫func函式,而func函式中則從棧中取出這些引數,這個過程就是引數傳遞的過程。在上述程式碼中,我們看第一個和第四個箭頭所指的程式碼,這剛好對應第一個和第四個引數,它們是從分別從ebp-0DC和ebp-0E0地址所對應的記憶體中取出值,然後壓入棧,而第二和第三個箭頭對應的第二個和第三個引數,則是直接從a所在記憶體區域取出a的值,然後壓入棧中。

從這裡我們就可以看出a++和++a的區別了,a++會將a的值儲存到記憶體的一個臨時區域,或者說存放在一個臨時變數中,然後a自加1,取a++的值時就從記憶體或這個臨時變數中取出a的值,這時取出的值是a自加之前的值,最後再使用這個值;而++a則是直接將a的值自加1,使用它的值時也是從a所在記憶體中直接取出的。

對於結果為什麼會是4 5 5 1,通過反彙編也可以很清楚的看出來了,呼叫func時是先從右至左執行完後才傳入引數的,對於++a直接使用最後的a的值,對於a++則使用執行這條語句之前a的值,如示例程式碼中,執行第一個a++時,a的值為4,因此傳入的值就是4,而最後執行完這四條語句後a的值為5,因此中間兩個引數傳入的值為5,所以最後的結果為4 5 5 1。