Chapter II. 新手上路
實作技巧
如何看錯誤訊息
作者WiwiHo
先備知識實作技巧 / 編譯器

編譯程式失敗的話,編譯器一定會告訴你是什麼地方出錯了,千萬不要嘗試自己盯著程式碼尋找錯誤,而不理會錯誤訊息。

很多常見的語法錯誤,看一眼編譯時的錯誤訊息就會知道在哪裡了,像是:

#include <bits/stdc++.h>
using namespace std;
int main() {
    int a = 5
}

雖然讀者應該一眼就可以發現第 4 行少了一個分號,不過要是程式大一點的話,要靠自己找到就不是簡單的事了。編譯時,編譯器就會說:

semicolon.cpp: In function 'int main()': 的意思就是這個錯誤發生在 semicolon.cpp 這個檔案的 int main() 函式裡頭,下一行的 semicolon.cpp:5:1 是說這個錯誤發生在 semicolon.cpp 的第 5 行的第 1 個字元,後面的 expected ',' or ';' before '}' token 則是說發生了什麼,意思是那一個 } 前面缺少了 ,;。總之只要去看看第 5 行附近,就可以馬上看出來是上一行缺少一個 ; 了。

有時候錯誤訊息會讓人有點摸不著頭緒,畢竟編譯器其實不會通靈你想做什麼,像在剛剛的例子裡面,編譯器指出錯誤的那一行只有一個 },要往前看才知道是前一行少一個分號。比較顯著的例子是,要是發生的錯誤是多層大括號之中少打一個,那編譯器當然分不出來你是少打哪一個,舉例來說:

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n = 5;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i != j) {
                cout << i << " " << j << "\n";
            }
    }
    cout << "ok!\n";
}

編譯器抱怨的是檔案最後面應該還要有一個 },不然 int main(){{ 會沒有對應的 }。不過從程式碼的縮排看起來,應該是 for(int j = ...){ 少了對應的 },要是直接照著指示補在最後面那就錯了。因此,編譯失敗時還是得自己看看前後文來發現真正的錯誤是什麼。

剛才的例子裡面,錯誤訊息裡出現了 error 和 note,有時候還會有 warning。note 是用來補充說明 error 或 warning 用的,而 error 是一定得要修好,不然不能編譯的錯誤,warning 則是編譯器提醒你這樣的寫法可能會容易出錯,但不修好也可以編譯成功。

一個會製造出 warning 的例子是 printf("%d");,編譯器會警告 printf 應該要有一個 int 參數,但沒有的話編譯也是可以過的。

除了指出問題的位置可能會怪怪的,錯誤訊息的描述有時候也會比較難懂,像是

#include <bits/stdc++.h>
using namespace std;
int count = 0;
int main() {
    cout << count << "\n";
}

天哪,ambiguous 的是他自己吧!什麼叫作 reference to 'count' is ambiguous 呢?看到下面的 note 列出了三個不同的 count,有兩個在詭異的檔案裡面,最後一個是我們的全域變數,原來是 count 跟已經存在的東西撞名了,在有 using namespace std; 的時候要特別注意全域變數取名字不要跟 STL 裡的 function 撞名。

神奇的是,區域變數取相同的名字是不會有問題的,編譯器會優先使用區域裡定義的東西。例如上面那份程式碼裡面,只要把 count 移到 main 裡就可以正常編譯,不過,這樣在 main 中就只能用我們定義 count 變數,而不能直接使用 STL 裡的 count 函式,得要寫 std::count 指定要使用 STL 中的 count 才可以。

前面的錯誤訊息都還算是可以閱讀。有時候錯誤訊息會有超長一大串,例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    vector<vector<int>> a(5, 10);
}

(下面還有一長串,讀者可以自己試試看。)

看這種訊息的方法就是直接忽略下面全部的東西,找出編譯器指出我們的錯誤的地方,也就是直接看到最上面的 vector.cpp:4:32,需要看的部分就只有這裡而已,總之就是 vector 初始化的寫法錯了。在使用 STL 裡提供的容器時很容易出現這樣的錯誤訊息。

以上介紹了錯誤訊息大致的讀法,可能會出現的錯誤訊息還有很多種,別忘了編譯失敗時要先從錯誤訊息下手,而看到一大串錯誤訊息也不要驚慌,只要找出寫著錯誤位置的部分就可以了。至於看不懂錯誤訊息的話,通常直接複製錯誤訊息的敘述,拿去搜尋就可以找到解法了。