--- 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++ )  他們分別是編譯、執行、編譯並執行。當你寫好一份程式,只要戳戳編譯再戳戳執行,程式就跑起來了,但是他們在背後做了什麼事?他做的事真的是我想做的事嗎? ## 編譯語言 > 編譯語言(英語:Compiled language)是一種程式語言類型,通過編譯器來實作。它不像直譯語言一樣,由直譯器將程式碼一句一句執行,而是以編譯器,先將程式碼編譯為機器碼,再加以執行。 > —維基百科〈編譯語言〉 C++ 是一種編譯語言。也就是說,一份程式碼不能直接執行(他就只是一份純文字檔),這份文字檔要給「編譯器」一番操作才能變成可以執行的機器碼。  這個召喚編譯器出來做事的過程都給 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"
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.