【C進階】17、++和--操作符分析

bryson發表於2021-10-27

Summary

1)++和--參與混合運算結果是不確定的,如r = (i++) + (i++);等

  • C++只規定了++和--對應指令的相對執行次序取值和自增的相對順序)
  • ++和--對應的彙編指令不一定連續執行
  • 在混合運算中,++和--的彙編指令可能被打斷執行 (取值和自增可能被打斷了,中間插入了其他程式碼)

2)編譯器的貪心法

  • 編譯器以從左向右的順序,一個一個地儘可能多的讀入字元
  • 當讀入的字元不可能和已經讀入的字元組成合法符號為止

3)空格可以作為C語言中一個完整符號的休止符編譯器讀入空格後會立即對之前讀入的符號進行處理

++和--操作符剖析

1、Demo1

  • 以下程式碼的輸出是?

    int i = 0;
    int r = 0;
    
    r = (i++) + (i++) + (i++);
    printf("i = %d\n, r = %d\n", i, r);
    
    r = (++i) + (++i) + (++i);
    printf("i = %d\n, r = %d\n", i, r);
  • 程式碼初步分析:

    第一行輸出:i = 3, r = 3;    // 按照從左向右的計算方法,i先取值,再自增,
                                // 則r = 0 + 1 + 2 = 3;
    第二行輸出:i = 6, r = 16;   // 同上,r = 4 + 5 + 6 = 15;

以上程式碼在VS2015編譯器的實際輸出結果為:

i = 3, r = 0;
i = 6, r = 18;

藉助Vs編譯器檢視反彙編結果:
image.png
程式碼分析:

對於r = (i++) + (i++) + (i++);    // 先取了i的值做了相加,賦值給r;然後i自增3次
    在彙編層面,做的操作依次是:
    1)00C542BC mov 將i代表的這個地址中的值放到暫存器eax中,為0
    2)00C542BF add eax的值和i的值累加,累加和仍然為0
    3)00C542C5 mov 將eax裡的值放到r變數代表的記憶體裡,
            所以r的值為0
    4)後面做了3次相同的操作:將i變數記憶體裡的值放到ecx暫存器裡,然後加1,
       再把ecx裡的值放回i變數的記憶體裡;重複2次
            所以i的值為3

對於r = (i++) + (i++) + (i++);    // 先給i自增了3次;然後把i的值加3次給了r
    在彙編層面,做的操作依次是:
    1)009A42F8 到 009A42FE 將i代表的這個地址中的值放到暫存器eax中;
                            然後eax裡的值自增1;
                            寫回i的記憶體裡,此時i的值為4
                            重複2次後,i的值為6
            所以i的值為6
    2)009A4313 mov 把i裡的值移動到eax暫存器中,eax裡的值為6
    3)009A4316 add eax裡的值加上i的值,即6+6,eax裡的值為12
    4)009A4319 add eax裡的值加上i的值,即12+6,eax裡的值為18
    5)009A4319 mov eax裡的值寫回r代表的記憶體裡
            所以r的值為18
                            

這一段程式碼,我們的分析和實際的編譯器結果大不相同;那麼其他編譯器又如何?
bcc編譯器和vc編譯器的結果一致,而gcc編譯器的結果則是“r = 0 和 r = 16”

以上程式碼在java編譯器中的輸出結果:

// test.java
public static void main(String[] args) {
    System.out.println("test.java file name must be equal to the class name test");
    int i = 0;
    int r = 0;
    
    r = (i++) + (i++) + (i++);
    System.out.println("i = " + i + ", r = " + r);

    r = (++i) + (++i) + (++i);
    System.out.println("i = " + i + ", r = " + r);
}

// 編譯:javac test.java
// 執行:java test
// 輸出:i = 3, r = 3
        i = 6, r = 15   和分析的一致

以上的測試說明:++和--參與混合運算結果是不確定的

  • C++只規定了++和--對應指令的相對執行次序
  • ++和--對應的彙編指令不一定連續執行
  • 在混合運算中,++和--的彙編指令可能被打斷執行

2、Demo2

  • 以下程式碼的輸出是?

    int i = 0;
    int j = ++i+++i+++i;
    
    int a = 1;
    int b = 4;
    int c = a+++b;
    
    int* p = &a;
    b = b/*p;   
    
    printf("i = %d\n", i);
    printf("j = %d\n", j);
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);

編譯器的貪心法

  • 編譯器以從左向右的順序,一個一個地儘可能多的讀入字元
  • 當讀入的字元不可能和已經讀入的字元組成合法符號為止

程式碼分析:

int j = ++i+++i+++i;    
    // 編譯器先讀到1個'+',不知道啥意思;
    // 繼續讀到1個'+',它覺得這是個前置的'++'
    // 然後讀到了i,確定了這是個'++i'表示式
    // 繼續讀到1個'+',這可能是一個加法的運算子 '+'
    // 繼續讀到1個'+',這時候判斷是一個後置的 '++',後面再讀任何數都不對了,讀到變數,不合法;讀到符號,也不對;
    // 這時候編譯器就停止處理了,所以編譯器就得到了 “++i++”
    // 然後計算得到了 “1++”,對一個右值1進行自增,自然會編譯錯誤!

int c = a+++b;
    // 編譯器依次讀了3個字元'a++',知道這是個後置的++
    // 然後讀到了1個'+',覺得這個可能是個加法運算子
    // 繼續讀到了b,後面也沒有其他字元了,所以得到了 (a++) + b
    // 計算得到了c = 5

b = b/*p;    // error,編譯器會將/*識別為註釋,因此整個程式會編譯失敗
// 如果願意是用b的值除以*p的值,那麼就應該用括號或者空格表明
b = b / (*p); 或 b = b / *p; 

本文總結自“狄泰軟體學院”唐佐林老師《C語言進階課程》。
如有錯漏之處,懇請指正

相關文章