如何优雅地在 NOI Linux 下自测(2025 ver.)
last update: 2025.10.28
请在充分理解本文中命令含义后再使用。笔者不为错误使用本文中的脚本/代码造成的任何后果承担责任。
如何编译
推荐使用命令行编译。可以直接使用 g++,也可以通过 make。
make可以通过export CXXFLAGS="xxx"设置编译选项。
必须的编译选项:-std=c++14 -O2。此外,题面中其他的编译选项也建议加上。
建议的编译选项:-Wall -Wextra -Wconversion -Wshadow。并且建议仔细阅读编译器给出的 warning。
可选的编译选项:-g -fsanitize=address,undefined 或 -D_GLIBCXX_DEBUG。用来查 UB(后者可以查 STL 库内部 UB)但是这样编译出的程序效率无意义,例如后者会将 std::lower_bound 实现换成 $O(n)$ 的,建议慎用。
- 一般来说,ubsan 不会主动使程序 RE,只会在 stderr 中输出。为了方便对拍可以在编译选项中加入
-fno-sanitize-recover=undefined。- 有时调试时关闭 O2 可以更快定位错误原因。
此外,如果想查程序瓶颈,可以试一下 -pg 然后跑完程序后运行 gprof <可执行文件名>。但是效果不一定好。
如何运行程序
ulimit 命令可以为当前终端设置系统限制:
$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 62391
max locked memory (kbytes, -l) 2006336
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 62391
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited我们可以将 cpu time(-t) 调成题目时限,stack size(-s) 和 virtual memory(-v) 调成题目空限,再运行程序即可。
这样如果爆了限制会直接 kill 掉。注意一个 shell 窗口内 ulimit 限制只能调小不能调大,所以你可能需要每次新开一个 shell 窗口来运行程序。
timeout <TL> <program>也能起到超时后 kill 某个程序的效果(TL 默认单位为秒),如timeout 1.5 ./a会在 1.5s 后强制终止./a。注意这里限制的是 real time,好处是支持浮点数。
想要看更清楚的信息?\time --format="%Us [%Ssystime] %MKiB" ./a 即可。 注意这里显示的空间是 maximum resident size(最大常驻集),与程序占用的虚拟内存略有不同。
根据 官网文档(见 NOI评测系统Atbiter单机版使用介绍.pdf),评测时的内存限制为「虚拟内存」。set -e 可以使脚本在任意子进程以非 0 值退出时以非 0 值退出。
如何 check?
spj 题手写一个 checker,通过返回值判断是否合法。
普通题目用 diff 即可;如果想忽略空白字符可以使用 diff -Z -B,即忽略行末空格和 全部 空行 (有一定风险)。忘了参数什么意思可以 man diff 或者 diff --help。
如何循环?
for 循环:
for i in {1..10}; do
# do something
donewhile 循环:
while [[ true ]]; do
# run program
if [[ $? -ne 0 ]]; then
break
fi
done成品
适用于 ZJ 的 Linux 目录结构(每道题的源代码放置在主文件夹下的对应子文件夹内)
~/run.sh:
#!/bin/bash
set -e
cd ~/$1
cp ../down/$1/$1$2.in $1.in
echo "Running on $2..."
ulimit -v 512000
ulimit -s 512000
\time --format="exit %x, %Us [%S sys time] %MKiB" ./$1
diff -B -Z $1.out ../down/$1/$1$2.ans
echo "Passed."~/eval.sh:
#!/bin/bash
cd ~
make $1/$1
if [[ $? -ne 0 ]]; then
echo "CE."
exit 1
fi
for ((i=1;i<=$2;i++)); do
./run.sh $1 $i
if [[ $? -ne 0 ]]; then
echo "Failed on $i."
exit 2
fi
done
echo "Pretests passed.($2)"用法:~/eval.sh ${problem} ${pretest_count}