--- title: 編譯器 tags: 7th 教學 --- # C++與編譯器 > Author: PixelCat > **章節目錄** > [TOC] C++ 是一個編譯式的程式語言。也就是說,給人看的程式碼會先經過「編譯」,成為給機器看的執行檔才被執行;相對於 python 之類直譯式語言。本篇概略介紹 C++ 的編譯器如何運作、以及在競賽中如何利用強大的編譯器為自己所用。 本篇所有指令使用 g++ (GCC) 12.1.0 編譯器。多數 linux 發行版有內建; windows 使用者可以安裝 MinGW (使用 Dev-c++ 或 Code::blocks 自帶的 MinGW 也可以);而 MacOS 因為我沒用過所以請自行解決 QQ 我寫這篇的目標讀者基本上是所有人,所以可以直接從自己需要的地方開始看。 ## 前言:IDE的一條龍服務 多數 C++ 初心者會選擇使用一個 IDE 來寫程式,例如 Dev-C++ 或 Code::blocks ,我也不例外。在這些 IDE 通常會有幾個按鍵是寫程式第一天就認識的(圖為 Dev-C++ ) ![](https://i.imgur.com/GA2gKjL.png) 他們分別是編譯、執行、編譯並執行。當你寫好一份程式,只要戳戳編譯再戳戳執行,程式就跑起來了,但是他們在背後做了什麼事?他做的事真的是我想做的事嗎? ## 編譯語言 > 編譯語言(英語:Compiled language)是一種程式語言類型,通過編譯器來實作。它不像直譯語言一樣,由直譯器將程式碼一句一句執行,而是以編譯器,先將程式碼編譯為機器碼,再加以執行。 > —維基百科〈編譯語言〉 C++ 是一種編譯語言。也就是說,一份程式碼不能直接執行(他就只是一份純文字檔),這份文字檔要給「編譯器」一番操作才能變成可以執行的機器碼。 ![](https://i.imgur.com/aEfHcF7.png) 這個召喚編譯器出來做事的過程都給 IDE 做好了,但是編譯器如此強大,IDE 不一定能夠幫我們榨乾編譯器的能力,假如我們能直接跟編譯器對話,就可以讓他針對我們的需求提供服務了! 因為現在多數 OJ 使用的編譯器都是 g++ ,所以本篇也用 g++ 作為範例。 ## 命令列手動編譯 我們現在有一份 C++ 程式碼,用任何文字編輯器寫出來都可以 ```cpp= // test.cpp #include <iostream> int main(){ std::string s; std::cin >> s; std::cout << "Hello, " << s << "!" << std::endl; return 0; } ``` 這時要召喚編譯器(g++),我們可以在 terminal(windows下面叫命令提示字元)找到這個檔案所在的資料夾,說:(錢號是本來就有的提示字元,不要理他) ``` $ g++ test.cpp ``` 現在你會看到一個新的 `a.out` (在 windows 下可能是 `a.exe`),像這樣執行他 ``` $ ./a.out ``` 他跑起來了! ``` $ ls test.cpp $ g++ test.cpp $ ls a.out test.cpp $ ./a.out meowowo Hello, meowowo! ``` 這樣,我們就會編譯了。 所以前面講這麼多只為了這幾個字?當然不是。編譯器何其強大,我們絕對不會滿足於此。 ### 編譯指令 我們需要編譯指令來召喚 g++ ,同時吩咐他一些事情。一個完整的編譯指令包含三個部份 ``` $ g++ [options] file... ``` 1. `g++`:這是編譯器的名字 2. `file...`:你要編譯的檔案(們),在競程的場合通常都只會有一個檔案 3. `[options]`:也就是「編譯選項」,他能讓 g++ 針對我們的需求做一些比較酷的事,這才是好玩的部份 我們的目標是透過加入各種編譯選項來調教編譯器。加入一個編譯選項可能會像這樣: ``` $ g++ -o test.out test.cpp ``` 剛剛跑出來的 `a.out` 不見了,取而代之的是 `test.out` 。這是因為我們加入了編譯選項 `-o test.out` ,他的功能是把輸出的執行檔命名為你指定的名字(`test.out`)而不是預設的 `a.out` 。 ### IDE 的編譯指令 <span style="color:red;"> TODO: 並未經過實測(因為我現在的系統沒有 dev-c++ 和 code::blocks) </span> 在許多 IDE 中,雖然都已經有預設的編譯指令,但是你還是可以在設定中加入自己想要的編譯選項。以 Dev-C++ 來說他在 Tools > Compiler Options > "Add the folowing commands when calling compiler" ,寫在這格的編譯選項會在 IDE 幫你編譯時被加入。 ## 常用、常見編譯選項(競程向) 有些編譯選項特別常使用,在這裡列出來,可以自由選用。我會建議選一些你覺得好用的選項,背起來,就像打模板一樣在比賽一開始加入自己的編輯器/IDE 設定,也許可以讓你寫程式舒服一點。一些這裡沒提到、我沒用過、但是可能有用的選項可以看[這篇 CF blog][cf-sanitizers]。 ### 一般類 - `-o {file}` 將輸出檔命名為 `{file}` 。通常這個不用動,因為 IDE 都幫你設定好了。 - `-std=XXX` 這裡 `XXX` 可以是 `c++11`、`c++14`、`c++17`、`c++20` 之類的,代表你要用哪個語法標準。現在多數比賽會選用 `c++14` 或 `c++17` ,賽前應該要先搞清楚,以免用到新語法才發現噴一坨 CE 。而多數主流 OJ 都已經支援到 `c++17` 以上所以比較不用擔心。 :::spoiler 關於語法標準 C\+\+ 的語法是會隨時間演進的,每三年會公佈一版新的語法標準,但是通常不會所有人都一瞬間跑去用新標準。現在最新的標準是 2020 年釋出的 c\+\+20 ,預計[明年](https://en.wikipedia.org/wiki/C%2B%2B23)會再有新標準。 有些新的語法在新標準才能用,比如說auto的用法在 c\+\+11 和 c\+\+14 不一樣、或者像 `auto [a,b,c] = some_tuple` 之類的語法在 [c\+\+17](https://en.cppreference.com/w/cpp/language/structured_binding) 以後才能用。我自己平常都是使用 c\+\+14 ,幾乎所有比賽都有支援到至少這個標準以上。 另外,假如你指定了語法標準,編譯器卻抱怨他不知道這啥,有可能是你的編譯器太老了,更新編譯器應該就能解決。 ::: - `-DXXX`/`-D XXX` 這裡的 `XXX` 可以是任何你想塞的東西,相當於加入前置處理器 `#define XXX` 。 ### 編譯優化類 - `-O0`/`-O1`/`-O2`/`-O3`/`-Ofast` 各種編譯優化,從 `-O0` 的什麼都不做,到越右邊的做越多優化。大部分時候 judge 會幫你開 `-O2` ,包含各種已知開了不虧的選項。到 `-O3` `-Ofast` 會有一些可能有副作用的優化,可能導致你的程式反而變慢甚至改變程式碼的行為。 **不要期望開了編譯優化就可以讓你的程式飛快從 O(N) 變 O(1) ,編譯優化效果有限請斟酌使用。** ### 編譯警告類 - `-Wall` 很多警告。開警告可以幫你找到潛在的 bug ,我不知道詳細來說他包含哪些警告,但是我一律建議開著,這會幫助你預先找到一些不小心寫出來很低能的問題。 - `-Wextra` 更多警告,我依然一律建議開著。 - `-Wshadow` 警告你有沒有 shadow 到一個變數,也就是說,一個區域變數跟外層的變數撞名了。常見的是你兩層迴圈用到同一個變數名 ```cpp= for(int i=0;i<n;i++){ // some looooong code for(int i=0;i<m;i++){ //... } } ``` 這樣他就會跟你說,欸,內層的 i 跟外層的撞名了,這可能不是你想要的。 - `-Wconversion` 在你想把某份資料塞到型別不同的變數,中間可能會有東西不見的時候提醒你。比如說把某個 `long long` 的資料塞進 `int` ,這樣他就會跟你說可能資料會出問題。 ### sanitizers ~~消毒器~~ 與 debug 選項類 需要注意的是這幾個選項通常會(跟前面幾個選項比起來)增加不少編譯時間,斟酌使用。 - `-fsanitize=address` 在執行時間幫你抓到陣列亂戳之類的記憶體存取錯誤 - `-fsanitize=undefined` 在執行時間幫你抓到一些未定義行為 - `-D_GLIBCXX_DEBUG` 讓你的 STL 容器和函數們在執行時間抓到一些錯誤,例如 vector 戳出界之類的。**大幅增加編譯時間**。 ### TL;DR 所以,把所有選項串在一起,我自己在用的的編譯指令長這樣: ```bash $ g++ -o {file}.out -std=c++14 -D LOCAL -O2 -Wall -Wextra -Wshadow -Wconversion {file}.cpp ``` 其中, `{file}` 是檔案名稱, `-D LOCAL` 的目的在後面會說明。我自己沒有開 `-fsanitize` 和 `-D_GLIBCXX_DEBUG` 是因為我覺得沒那麼常用到而且我不想每次都等他編譯那麼久,假如你覺得有賺當然都可以加進去。 ## 編譯器的一條龍服務 ## 常用、常見前置處理器(競程向) ## 我不是小孩子 ## 參考資料 1. [GCC #pragma 前置處理器(CF blog)][cf-pragma] 2. [GCC 除錯選項(CF blog)][cf-sanitizers] 3. 程式設計師的自我修養-連結、載入、程式庫(實體書) 本篇圖片素材部份取自 [PNG repo](https://www.pngrepo.com/) ,其餘皆為自製。 <!-- reference links --> [cf-pragma]: <https://codeforces.com/blog/entry/96344> "[Tutorial] GCC Optimization Pragmas - Codeforces" [cf-sanitizers]: <https://codeforces.com/blog/entry/15547> "Catching silly mistakes with GCC - Codeforces"