ScarShow

< IS >

關於一些程式語言裡的遞增運算子

前幾天在教學弟 C 程式,其中有出現最常考學生的遞增運算子,像是下面這樣子:

#include <stdio.h>

main() {
    int n = 0;
    n = ++n + ++n + ++n + ++n;

    printf("%d", n);
}

它算出來的結果是11跟實際上的結果有些許的不同,一般來說答案應該是:

10 = 1 + 2 + 3 + 4

測試了一下會發現實際上變數中的數值卻是,跟所想的有出入:

11 = 2 + 2 + 3 + 4

什麼? 為什麼1變成了2,一定有哪邊有誤會,所以就用 GCC 把組合語言輸出看一下到底哪邊出問題了,發現其中一段程式碼:

; ...

movl    $0, -4(%rbp)   ; rbp = 0

addl    $1, -4(%rbp)   ; rbp = 0 + 1 = 1

addl    $1, -4(%rbp)   ; rbp = 1 + 1 = 2
movl    -4(%rbp), %eax ; eax = 2
addl    %eax, %eax     ; eax = 2 + 2 = 4

addl    $1, -4(%rbp)   ; rbp = 2 + 1 = 3
addl    -4(%rbp), %eax ; eax = 4 + 3 = 7

addl    $1, -4(%rbp)   ; rbp = 3 + 1 = 4
addl    %eax, -4(%rbp) ; rbp = 4 + 7 = 11

; ...

程式在遞增並相加的時候有點問題,所以才造成11這個結果,再試了一下C++也是一樣,所以這時候我在想其他語言應該沒有這個問題吧? 所以就多試了一些語言,像是 PHP 及 Javascript,另外還多試了 Visual C++ 及 C#,執行結果如下:

PHP

<?php

$n = 0;
$n = ++$n + ++$n + ++$n + ++$n;

// 10 = 1 + 2 + 3 + 4
echo $n;

Javascript

'use strict';

var n = 0;
n = ++n + ++n + ++n + ++n;

// 10 = 1 + 2 + 3 + 4
console.log(n);

Visual C++

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    int n = 0;
    n = ++n + ++n + ++n + ++n;

    // 16 = 4 + 4 + 4 + 4
    printf("%d", n);

    return 0;
}

C Shape

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = 0;
            n = ++n + ++n + ++n + ++n;

            // 10 = 1 + 2 + 3 + 4
            System.Console.Write(n);
        }
    }
}

結論

從測試的結果來看 PHP、Javascript 及 C# 與預期的答案相符,不過 Visual C++ 的結果就不是這樣了,Visual C++ 算出來是16,不過形成原因也比較滿合理的,所以這還是各家編譯器設計的問題。

但 C/C++ 的結果是 feature 嗎...? XD

但其實這是所謂的未定義行為(Undefined Behavior),因每個語言的編譯程式的設計不同所造成問題,而在語言中也沒有明確定義所造成的行為。但是正常來說只要知道因起問題的程式是甚饃就可以了,但是不知道為什麼在學校裡的教授卻特別喜愛拿出老考學生,要求學生去背編譯結果這是沒有意義的。

但除了運算子的問題之外還有其他問題也是歸類於未定義行為,可以參考以下的連結。

Update: 2014/03/16