[C 語言] i++ 和 ++i 是怎麼回事。

剛好看到同學關於資管賓果的噗,中間那格就是 i = ++i,因為這是一個『C 語言很貼近機器實際運作』的一個例子,其實是很有趣的東西(雖然不是好的寫法),可惜現在很多人一開始就是寫高階語言,遇到這種東西就會覺得很莫名其妙不知道這個運算到底要怎麼解釋,所以就來聊一下 i++ 和 ++i 到底是啥順便灌個水好了。

簡單的範例如下:

int main ()
{
    int i = 10;
    i++;

    int j = 10;
    ++j;

    printf("i = %d\n", i);  // i = 11
    printf("j = %d\n", j);    // j = 11
}

由上面的範例我們可以看出來,i 和 j 都是 11,所以我們可以合理的推論 i++ 和 ++i 是一樣的,就是把該變數加一這樣嗎?

錯!大錯特錯!

為什麼呢?!再看下面的程式碼:

int main ()
{
    int i = 10;
    int x = 0;
    x = i++;

    int j = 10;
    int y = 0;
    y = ++j;

    printf("x = %d\n", x);  // x = 10
    printf("y = %d\n", y);  // y = 11

}

如果說 i++ 和 ++i 是一樣的,為什麼當我們把 i++ 和 ++j 指定給一個全新的變數之後,答案突然就變成了 10 和 11,和第一個程式的行為不一樣呢?

原因很簡單:++i 和 i++ 是兩個『完全不同』的運算子,翻成機器語言之後是完完全全不同的。

另外,C 語言也有一個有趣的特性:每一個 expression 會在暫存器上留下一個值,而在做 = 號的 assignment 的時候,看的是暫存器上的值!

到這邊,如果你不知道我說的是什麼,我會建議先把這份程設講議的『電腦內部運作』那章給看完,特別是最下面的那些簡易機器指令,因為下面會用到。

所以,平常我們看到的 int z = x 這件事,實際上並不只是『把 x 的值指定給 z』這麼簡單,要達成這件事,以小人電腦來說實際上至少需要兩個步驟:

  • LOAD x # 把 x 的值放到暫存器上
  • STORE z # 把暫存器上的值放到 z 這個變數所在的位置去

依此類推, int z = x + 1 這件事,至少需要以下三個步驟:

  • LOAD x # 把 x 的值放到暫存器上
  • ADD 1 # 把暫存器上的值加 1,所以現在暫存器上的值是 (x+1)
  • STORE z # 把暫存器的值放回 z 所在的位置去

以上是最基本幾個運算,而 i++ 和 ++i 的差別就在於翻譯成上面的小人電腦機指令集後,做運算的順序是完完全全不同的!

下面就是把上面翻成小人電腦機器指令後的結果,你會發現這兩個 expression 非常的相似又非常的不同,不過是的 ADD/LOAD 順序對換,但所造成的結果是天差地遠的……

int main ()
{
    int i = 10;
    i++;    // LOAD  i (現在暫存器上的值是 10)
            // STORE i (所以現在 i = 10,暫存器還是 10)
            // ADD   1 (現在暫存器上的值是 11,但 i 還是 10)
            // STORE i (現在 i = 11 了)

    int j = 10; 
    ++j;    // LOAD  j (現在暫存器上的值是 10)
            // ADD   1 (現在暫存器上的值是 11)
            // STORE j (現在 j = 11)
            // STORE j (現在 j 還是 11)

    printf("i = %d\n", i);  // i = 11
    printf("j = %d\n", j);  // j = 11
}
int main ()
{
    int i = 10;
    int x = 0;
    x = i++;    // LOAD  i (現在暫存器上的值是 10)
                // STORE x (所以現在 x = 10,暫存器還是 10)
                // ADD   1 (現在暫存器上的值是 11)
                // STORE i (現在 i = 11)

    int j = 10; 
    int y = 0;
    y = ++j;    // LOAD  j (現在暫存器上的值是 10)
                // ADD   1 (現在暫存器上的值是 11)
                // STORE j (現在 j = 11)
                // STORE y (現在 y = 11)

    printf("x = %d\n", x);  // x = 10
    printf("y = %d\n", y);  // y = 11
}

這就是 i++ 和 ++i 的真相,同時也是我很佩服和感謝大學時程設老師願意從那麼基本的東西教起的原因。你不會這些東西當然也可以寫程式(實際上這些用法都是不被鼓勵使用的),但如果你有這些基礎的概念,就會發現這些東西是符合一定規則在運作的,你可以很輕易地推論出最後的結果是啥,特別是當有了小人電腦 / Stack / Heap Space 的觀念後,C 的 pointer 和 Java 的 reference 就會非常容易理解了。

PS. 實際上在 C 裡 i = i++ 或 i = ++i 是 undefined,因為 assignment 或 increment 誰先做並沒有明確的規定 ,但在其他有規定的語言裡,這樣的運算有可能是合法的

回響