NOI Linux 中的 G++ 编译

· · 科技·工程

参加 NOI 系列比赛中(如 CSP),那么熟悉 NOI Linux 下的 G++ 编译器是必不可少的。虽然平时在 Windows 上用 Dev-C++ 或者 VS Code(放心考试电脑基本不预装而且不会有 C/C++ 插件)写代码。

但是到了比赛环境下,了解如何使用 G++ 编译器以及常用的编译选项就显得尤为重要。(就算在 Windows 上写最好也可以到 Linux 上测试一下能不能运行。)

通常情况下(包括评测的时候)用的都是 G++ 编译器来编译 C++ 代码。

(一般情况下,在考试用的 Windows 的一个盘会有一个和 Linux 互通的 public 目录,可以直接把 cpp 复制进去。)

G++ 是什么?

G++ 是 GCC(GNU Compiler Collection)的 C++ 编译器。简单来说,它就是把你写的 C++ 代码翻译成计算机能执行的程序。在 NOI Linux(基于 Ubuntu)中,G++ 是默认安装好的,你可以直接在终端里使用。

(终端是 Terminal 应该不用教了吧。)

打开终端输入:

g++ --version

可以看到类似 g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 这样的输出。

最简单的编译

假设你写了一个 hello.cpp 文件,内容大概是这样的:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, NOI!" << endl;
    return 0;
}

要把它编译成可执行文件,最简单的命令就是:

g++ hello.cpp

执行完这条命令后,你会发现当前目录下多了一个叫 a.out 的文件(这是默认的输出文件名)。运行它:

./a.out

就能看到 Hello, NOI! 的输出了。

不过这个 a.out 名字有点奇怪对吧?我们可以用 -o 参数给编译出来的程序指定一个名字:

g++ hello.cpp -o hello

这样生成的可执行文件就叫 hello 了,运行的时候也更方便:

./hello

比赛中常用的编译参数

在正式比赛中,评测系统通常会使用特定的编译参数来编译你的代码。了解这些参数很重要,因为它们会影响程序的性能和行为。通常是类似这样的:

g++ source.cpp -o program -O2 -std=c++14 -static

咱们一个一个来看这些参数都是干啥的。

-O2:开启优化

G++ 提供了好几个优化级别:

比赛中一般使用 -O2,因为它能在保证正确性的前提下显著提升程序运行速度。你的程序可能会快个几倍,这在时限卡得紧的题目中就很关键了。

举个例子,如果你写了一个暴力算法,没开优化可能会超时,但开了 -O2 后说不定就能过了(当然,更好的做法还是优化算法本身)。

g++ mycode.cpp -o mycode -O2

-std=c++14:指定 C++ 标准

C++ 一直在发展,有 C++98、C++11、C++14、C++17、C++20 等多个版本。不同版本支持的特性不太一样。

在 NOI 系列比赛中,目前(2025 年)大多数比赛使用的是 C++14 标准。这意味着你可以使用 C++11 和 C++14 的新特性,比如:

但要注意,C++17 和 C++20 的一些新特性可能就用不了了。

g++ mycode.cpp -o mycode -std=c++14

如果你不指定标准,G++ 会使用默认的标准。不过为了保险起见,建议显式指定 -std=c++14

-static:静态链接

-static 参数会让编译器进行静态链接,把程序依赖的库都打包到可执行文件里。这样做的好处是程序不怎么依赖动态库,并且避免了动态链接可能带来的一些问题。

不过静态链接也有缺点,就是生成的可执行文件会大很多。这在比赛中一般不是问题,因为对可执行文件大小通常没有限制。

g++ mycode.cpp -o mycode -static

(其实 static 差不多,毕竟 NOI Linux 预装的 G++ 通常编译出来是可以在 NOI Linux 上运行的。)

把它们组合起来

在实际比赛中,你通常会看到这样的编译命令:

g++ mycode.cpp -o mycode -O2 -std=c++14 -static

这条命令做了这些事:

  1. 编译 mycode.cpp
  2. 输出文件名为 mycode
  3. 开启 O2 级别优化
  4. 使用 C++14 标准
  5. 进行静态链接

警告信息

编译器的警告信息经常能帮你发现代码中的潜在问题。虽然有警告不代表程序一定有错(它还是能编译通过的),但很多时候警告指向的地方确实有 bug。

-Wall:显示常规警告

-Wall 会打开一大堆常见的警告选项(虽然名字是 "all",但其实并不是所有警告)。比如:

g++ mycode.cpp -o mycode -Wall

举个例子,如果你的代码是这样的:

#include <iostream>
using namespace std;

int main() {
    int x;
    cout << x << endl;  // x 没有初始化!
    return 0;
}

加了 -Wall 后编译会给出警告:

warning: 'x' is used uninitialized in this function

这就提醒你 x 在使用前应该先赋值。

-Wextra:额外的警告

-Wextra 会启用更多的警告,包括一些 -Wall 没有包含的。比如:

g++ mycode.cpp -o mycode -Wall -Wextra

注意 -Wextra 通常和 -Wall 一起使用,它们是互补的。

看个例子:

int main() {
    int a = 10;
    int b; // 没有使用!
    cout << a << endl;
}

-Wextra 会提醒你参数 b 定义了但没用到,可能是你忘了什么?

平时练习开着

g++ mycode.cpp -o mycode -O2 -std=c++14 -Wall -Wextra

虽然比赛时评测系统编译你的代码不会开这些警告,但养成习惯能让你的代码质量提高不少。很多低级错误都能在编译阶段就被发现。

当然,有时候你可能会遇到一些「假警告」——代码逻辑其实没问题,但编译器还是报警告(比如 scanf 返回值被忽略)。这种情况下你可以根据具体情况判断。但大多数时候,警告指出的问题是真实存在的。

其他实用的参数

除了上面提到的,还有一些参数在某些情况下也很有用。

-g:生成调试信息

如果你需要用 GDB 调试程序,就需要加上 -g 参数:

g++ mycode.cpp -o mycode -g

这会在生成的可执行文件中包含调试信息,让你能够用 GDB 看到源代码、设置断点、查看变量值等。

-lm:链接数学库

在某些老版本的编译器上,如果你用了 <cmath> 里的函数,可能需要显式链接数学库:

g++ mycode.cpp -o mycode -lm

不过在现代的 G++ 版本中(包括 NOI Linux 用的版本),这通常不是必需的。但了解一下也无妨。

-DDEBUG:定义宏

有时候可能想在代码里写一些调试输出,但提交时又不想让它们运行。可以这样:

#ifdef DEBUG
    cerr << "Debug info: x = " << x << endl;
#endif

编译时加上 -DDEBUG 就会定义一个编译时宏,就会启用这些调试代码:

g++ mycode.cpp -o mycode -DDEBUG

不加就不会编译这部分代码。这在调试时很方便。

完整的比赛编译命令

总结一下,如果你要完全模拟 NOI 系列比赛的编译环境,使用这条命令:

g++ source.cpp -o program -O2 -std=c++14 -static

(如果你还想要警告信息帮助你检查代码):

g++ source.cpp -o program -O2 -std=c++14 -static -Wall -Wextra

一些实用技巧

保存编译命令

每次都敲这么长的命令很麻烦对吧?你可以把它保存成一个脚本文件。创建一个 compile.sh 文件:

#!/bin/bash
g++ $1.cpp -o $1 -O2 -std=c++14 -static -Wall -Wextra

然后给它添加执行权限:

chmod +x compile.sh

以后编译的时候只需要:

./compile.sh mycode

就会自动编译 mycode.cpp 生成 mycode 可执行文件了。

编译并运行

如果你想编译完立即运行,可以用 && 连接命令:

g++ mycode.cpp -o mycode -O2 -std=c++14 && ./mycode

只有编译成功了才会运行程序。

批量编译

有时候你可能需要编译多个文件,可以用循环:

for i in *.cpp; do
    g++ $i -o ${i%.cpp} -O2 -std=c++14 -static
done

这会把当前目录下所有的 .cpp 文件都编译一遍。

常见问题和注意事项

编译错误 vs 运行错误

编译错误是指代码语法有问题,G++ 无法将其翻译成可执行文件。这时候需要根据错误信息修改代码。

运行错误是指程序能编译通过,但运行时出了问题(比如数组越界、除零等)。这就需要调试了。

不要过度依赖优化

虽然 -O2 很强大,但不要用它来掩盖算法的低效。如果你的算法复杂度就是不够,再怎么优化编译选项也无济于事。该优化算法还是得优化算法。

写在最后

建议在日常练习中就使用和比赛一致的编译选项和编译器,这样能更好地模拟考场环境,避免「本地能过,考场 WA」的尴尬情况。