
简介Cena评测软件0.8.2是一款面向C、C、Pascal编程语言的代码评测工具适用于编程学习、教学批改与算法竞赛场景。该版本压缩包共包含446个文件包括头文件h、静态库a、可执行程序exe、Pascal源文件pas、目标文件o以及配置文件xml等整体体积约10.68MB可满足本地化离线评测需求。软件内置多语言编译与标准样例比对机制能够精准定位语法错误、逻辑缺陷并通过预设规范检查编码风格。目前已有433人学习使用尤其适合需要高频提交作业、模拟竞赛评测环境的用户。借助这套打包好的评测环境用户可以快速搭建OJ评测流程减少环境配置成本更专注练习和排错。1. CENA评测软件0.8.2是什么一场评测从半小时到两分钟做过校内模拟赛或者NOIP赛前训练的人大概率都经历过那种痛苦选手交上来几十份代码你手工一个测试点一个测试点地跑比对输出记录分数再汇总排名。运气好半小时运气不好一晚上搭进去。CENA评测软件0.8.2就是用来解决这件事的——它是一个跑在Linux下的评测内核负责把选手程序编译好、逐测试点运行、和标准答案比对、给出得分。它不是一个带网页界面的在线评测平台更像一个老老实实的批处理引擎你给它题目数据、标准输出和选手源码它把分数还给你。适合学校机房的模拟赛、个人刷题的自测脚本以及那些不想为了一个评测功能就去搭整套OJ的人。2. 把CENA 0.8.2装进Linux评测机最小依赖与第一个自测用例2.1 CENA这类评测程序的工作原理不是OJ是评测内核先要把概念对齐。我们平时说的OJ平台是“前端提交页面后端判题队列沙箱执行器数据库”一整条链路而CENA评测软件0.8.2这类的定位要低一层它只做判题这件事里最关键的那一段编译选手程序、在受限环境里运行、比对输出、换算分数。它的工作方式非常朴素读取题目目录下的配置信息知道有哪些测试点、每个测试点对应哪个输入文件、分数权重是多少。编译选手提交的源码得到可执行文件。对每个测试点用该测试点的输入数据去运行选手程序同时启动计时和内存监控。把程序输出和该测试点的标准答案做文本比对一致就给分不一致或超时超内存就按规则扣分。汇总所有测试点分数写进评测结果文件。所以你会看到用CENA这套思路搭评测真正要处理的只有三样东西题目数据目录、选手代码、结果读取方式。没有数据库没有队列没有用户系统。也正因为轻它非常适合在局域网机房、单台教师机甚至自己的笔记本上快速铺起来。我一般不建议第一次用的人直接去改它的源码而是先把它当成一个黑匣子摸清楚输入输出约定用起来就顺了。2.2 最小安装步骤configure、make与目录规划CENA 0.8.2是源码包发行拿到手之后先看目录里有没有configure脚本。有的话标准三步走基本就能装上。以下是我在Ubuntu和Debian系系统上常用的最小流程# 安装基础编译依赖 sudo apt-get install -y build-essential g make # 进入源码目录生成Makefile ./configure --prefix/opt/cena # 编译并安装到/opt/cena make sudo make install # 验证可执行文件是否生成 ls -l /opt/cena/bin/cena /opt/cena/bin/cena --version逻辑说明configure的--prefix参数决定安装位置我习惯装到/opt/cena这样和后面题目数据、评测结果目录放在一起路径干净。make做编译如果系统缺依赖这里会报错最常见的是缺libpcre或者zlib装上开发包再重跑就行。参数说明--prefix不写的话默认落在/usr/local也能用但升级和清理都不方便。我建议固定一个目录方便后面写评测脚本的时候引用绝对路径。装完以后需要规划一个工作根目录。常见做法是建三个目录各管一摊mkdir -p /opt/cena/problems # 每道题的目录放这里 mkdir -p /opt/cena/submissions # 选手提交的源码放这里 mkdir -p /opt/cena/results # 评测结果输出到这里这样做的原因是CENA的评测流程只认路径不认“项目”目录规整了后面批量评测和成绩汇总才能用脚本扫目录完成。2.3 自测用一道Hello题验证评测链路通不通装完环境第一件事不是急着配比赛题而是先用最简单的一道题验证整条链路。我通常会临时建一个hello目录放一组只有1个测试点的数据。# 创建hello题目目录data目录存测试数据 mkdir -p /opt/cena/problems/hello/data # 写一个标准输入程序读一个整数输出它的两倍 cat /opt/cena/problems/hello/solution.cpp EOF #include iostream int main() { int x; std::cin x; std::cout x * 2 std::endl; return 0; } EOF # 生成测试数据输入文件1.in标准输出1.out echo 21 /opt/cena/problems/hello/data/1.in echo 42 /opt/cena/problems/hello/data/1.out逻辑说明题目目录下建data子目录放测试数据这是CENA约定的数据组织方式。solution.cpp是这个阶段手工写的参考解法用于验证评测端到端能不能跑通。1.in和1.out的命名和后续配置文件里的映射一致。参数说明测试点的编号从1开始输入输出成对出现。这里故意用21和42方便肉眼确认评测结果确实读到了数据并在运行选手程序而不是空跑一场。跑通这个hello题才说明编译、运行、比对三个环节都正常这步过了才有资格谈后面配难题。3. 配好一套题的料题目目录、testdata.ini与逐项参数3.1 题目目录的标准布局数据、配置、答案各归其位一套能被CENA 0.8.2正常识别的题目目录结构是有讲究的。我见过不少新手把输入输出文件散落在各种地方导致评测时找不到测试点。按下面的布局来做基本不会出问题/opt/cena/problems/p1001/ ├── data/ │ ├── 1.in │ ├── 1.out │ ├── 2.in │ ├── 2.out │ ├── 3.in │ └── 3.out └── testdata.ini说明一下这个布局里的约定data目录只放测试数据输入文件后缀.in输出文件后缀.out同一测试点编号统一。testdata.ini放在题目根目录是整个题目的配置文件。CENA在评测一道题时会根据这个ini文件去data目录逐个找对应文件如果文件缺失这个测试点会直接判为无法评测而不是跳过。一个我踩过很多次的细节数据文件的编号一定要连续。比如你有1.in、2.in、4.in中间断了3.in评测系统处理到异常编号时的行为五花八门有的直接停止这题评测。所以我的习惯是生成数据后先跑一个脚本检查编号连续性再开始配置。3.2 testdata.ini逐项拆解分数、时限、内存与文件映射testdata.ini是CENA这套评测体系的灵魂。它决定了一道题有多少个测试点、每个测试点多少分、程序运行时间限制多长、内存限制多大。下面给一份完整的示例以3个测试点的题目为例[problem] namep1001 time1000 memory256 score100 checkernone [test1] input1.in output1.out score30 time1000 [test2] input2.in output2.out score30 time1000 [test3] input3.in output3.out score40 time2000逻辑说明[problem]段的name是题目标识time是默认时间限制单位毫秒memory是默认内存限制单位MBscore是本题总分。[test1]到[test3]每个段描述一个测试点input和output指定文件在data目录里的名字score是当前测试点分值time可以单独覆盖默认时间限制。参数说明所有score加起来必须等于[problem]段的总分否则汇总时可能出现总分对不上的情况。time按毫秒给1000就是1秒。memory单位是MB这里的256代表256MB。这个单位很多人看字面以为是字节后面专门讲这是最坑的地方之一。关于checker参数不配的时候默认做全文本比对。如果想用特判比如输出浮点数、只比对答案的一部分这里填spj同时在题目目录放一个特判程序。对绝大多数标准答案固定的题checkernone就够用。3.3 Special Judge的接入方式什么时候需要、怎么约定Special Judge特判在CENA 0.8.2里不是一个内置功能而是通过约定接口实现。当一道题的答案不唯一或者只要求“包含指定内容”时就需要用特判。典型场景比如输出两个可行解、精度误差允许在1e-6以内的浮点数答案。接入方式我一般是三件套准备测试点输出文件里不再放完整答案而是放判题所需的参考数据。写好特判程序编译成可执行文件按约定读取选手输出和测试点信息。在testdata.ini中把checker设为spj。常见的特判程序约定是这样的#include iostream #include fstream #include cmath using namespace std; int main(int argc, char* argv[]) { // argv[1]是选手输出文件argv[2]是标准输出文件 ifstream user(argv[1]); ifstream answer(argv[2]); double a, b; user a; answer b; if (fabs(a - b) 1e-6) { return 0; // 判为正确 } else { return 1; // 判为错误 } }逻辑说明特判程序通过命令行参数拿到两个文件路径一个是选手的输出一个是标准答案。程序内部的判断逻辑完全自定义返回值0表示通过非0表示不通过。这个约定简洁也方便移植到其他评测环境。参数说明1e-6是浮点比较的容忍误差具体数值得看题目要求。很多几何题要求1e-8你如果写成1e-6部分精度要求高的数据就会误判。建议建题时把精度要求写进题面避免争议。接入特判时有个容易翻车的细节标准输出文件1.out在特判模式下可能不再是最终答案而是参考起点特判程序需要自己处理“哪些内容该读、哪些内容不该读”。如果特判程序写挂了所有测试点都会变成RE而且是评测端RE选手那边完全复现不了那种。4. 从选手源码到分数一条完整的CENA 0.8.2评测流水线4.1 编译选手代码参数选择与常见失败信号评测第一步永远是编译。这一步做不好后面全是空中楼阁。选手交上来的代码格式五花八门有些人是.cpp有些人命名是main.cpp有些还有文件读写残留。我一般会先按题目要求统一重命名再执行编译# 进入选手提交目录 cd /opt/cena/submissions/player1 # 用-O2优化编译默认C17标准 g -O2 -stdc17 -o program main.cpp -lm逻辑说明-O2是竞赛标配的优化级别能保证程序运行速度接近真实比赛环境。-stdc17是当前比较通用的标准如果题目年代较早老代码可能用到C98的语法随后需要换成-stdc98再编译一次。-o program指定生成的可执行文件名称评测阶段统一用这个名字。参数说明-lm是链接数学库用了cmath的程序经常需要它如果编译命令里漏了本地跑没问题评测机上可能爆出一堆undefined reference to sqrt。这种报错给选手的提示往往很隐晦你作为评测管理端要第一时间判断出来是编译命令的问题而不是选手代码的问题。编译失败时日志要留档。我会把编译错误输出重定向到文件这样给选手反馈时能精确指出是哪一行语法错误g -O2 -stdc17 -o program main.cpp 2 compile_error.log # 检查日志文件是否为空非空即编译失败 if [ -s compile_error.log ]; then echo Compile Error fi这里-s compile_error.log的-s是判断文件非空的测试条件非空代表编译过程产生了错误信息。把编译错误输出流和正常输出流分开是避免把错误信息混进评测日志的关键。4.2 逐测试点执行与比对一条评测循环的核心逻辑编译通过之后真正的评测循环才是重头戏。CENA评测软件0.8.2的核心执行逻辑可以用下面这条bash脚本概括。这里以刚刚配置好的p1001为例#!/bin/bash # 评测循环逐测试点运行选手程序并比对 PROG/opt/cena/submissions/player1/program DATA_DIR/opt/cena/problems/p1001/data RESULT_DIR/opt/cena/results/player1 mkdir -p $RESULT_DIR # 从testdata.ini里提取测试点总数这里手工指定为3 for i in 1 2 3; do INPUT$DATA_DIR/$i.in OUTPUT$DATA_DIR/$i.out USER_OUT$RESULT_DIR/$i.out # 限制运行时间2秒输出文件写入用户目录 timeout 2 $PROG $INPUT $USER_OUT 2 $RESULT_DIR/$i.err # 根据退出码判断运行结果 EXIT_CODE$? if [ $EXIT_CODE -eq 124 ]; then echo test $i: Time Limit Exceeded continue fi # 比对输出文件和标准答案忽略行尾空白和末尾空行差异 if diff -Bb $USER_OUT $OUTPUT /dev/null 21; then echo test $i: Accepted else echo test $i: Wrong Answer fi done逻辑说明这个循环做五件事——构造输入输出路径、限制运行时间、执行选手程序、判断是否超时、比对输出。timeout 2表示最多运行2秒退出码124是timeout命令特有的超时标记。diff -Bb中-B忽略末尾空行差异-b忽略行尾空白差异这样选手输出就算多一个空格或少一个空行也不会被判错。参数说明timeout的时间单位是秒而testdata.ini里的time单位是毫秒转写脚本时别混。2秒在ini里要写作2000。-Bb这个参数组合是NOI系列评测的通用宽容策略但如果你就是想严格判空格差异删掉-b即可。4.3 读懂评测结果AC/WA/TLE/MLE/RE的行话与排查方向评测结果的标准缩写新手项目管理者和选手都要门儿清。这些缩写不是CENA独有而是整个竞赛评测体系通用的语言搞清楚它们才能快速定位问题。缩写全称含义常见排查方向ACAccepted通过无收工WAWrong Answer输出与标准答案不一致比对选手输出与标准答案确认算法边界TLETime Limit Exceeded运行超时看选手程序复杂度确认是否死循环MLEMemory Limit Exceeded内存超限检查数组是否过大动态分配是否有泄漏RERuntime Error运行时错误段错误、非法访问、除零查退出信号我评判一道题的配置是否合格不是看题目本身难不难而是看这几种结果在评测时能不能被稳定复现。如果一个选手代码在评测机上RE在自己机器上正常优先怀疑评测运行环境比如栈空间限制、内存限制和本地环境不一致。RE在CENA这类评测系统里有一个特别常见的来源程序试图写超出内存限制的数据比如申请了一个超大数组后访问越界被系统杀掉了表现就是RE而不是WA。排查时不能只看输出对不对还要看程序退出时的信号类型这就是$RESULT_DIR/$i.err这个文件派用场的地方。5. CENA 0.8.2避坑清单五个让评测结果失真/翻车的常见坑5.1 换行符的坑CRLF让你全题零分现象选手在Windows本地写代码输出正常交到评测机上全题WA一分没有。选手在评测机上重新编译一次还是WA。原因Windows下编辑的源文件和输出文件用的是CRLF换行\r\n而Linux评测环境用的是LF换行\n。当选手程序里直接吃入了带\r的数据或者输出了带\r的内容diff比对时字符串后面多出的那个\r字符就会导致比对失败。解决评测前统一用dos2unix处理选手代码文件dos2unix /opt/cena/submissions/player1/main.cpp这个坑最讨厌的地方在于它不在编译报错里体现也不在运行报错里体现只有比对阶段默默失败。处理完换行符最好再跑一遍评测确认分数变化。5.2 内存限制的单位MB还是字节差了128倍现象一道题内存限制256MB选手开了一个200MB的数组按理说完全在限定内但评测结果出现MLE。本地运行内存占用也就230MB怎么测都不超。原因CENA评测软件0.8.2的testdata.ini里memory参数默认以MB为单位256即256MB。但如果某个版本的评测模块在内部转换时按字节处理比如把数值当作字节数直接传给系统接口那么256字节的程序根本跑不起来表现就是全MLE。解决看清自己所用版本的约定。我自己的习惯是在配置里写256然后用一个故意申请300MB内存的程序做基准测试观察评测系统给不给MLE用实测倒推单位。这比翻源码、找文档可靠得多。5.3 数据文件名角标错位1.in对上了2.out现象某道题前两个测试点AC第三个测试点开始全WA叹号数据也过不了但手动拿3.in跑选手程序输出分明和3.out一致。原因testdata.ini里第三个测试点的input或output写错了名字比如input3.in写成了input2.in选手程序跑的是第2个数据比对的却是第3个答案逻辑错位结果当然错乱。手动跑的时候你是拿对的同名文件测自然复现不了。解决写一个轻量检查脚本逐测试点读取ini中的映射确认每个input、output配置都能在data目录下找到对应文件# 检查data目录中文件是否存在 awk -F /input|output/ {print $2} testdata.ini | while read f; do if [ ! -f data/$f ]; then echo MISSING: $f fi done这个坑常见于题目数据量大、手工维护testdata.ini的时候复制粘贴把上一行的文件名带了过来。花钱花时间造的数据毁在一个文件名上很不值。5.4 全过样例却全WA选手把文件读写写死在程序里现象选手报告本地所有样例都通过评测结果却全WA连最简单的一个测试点都不给过。把评测端生成的1.out拿给选手看里面是空的。原因选手代码里写了freopen(input.txt, r, stdin)或ifstream in(data.in)程序运行时要到当前目录找文件评测环境下当前目录是选手工作目录没有这个文件程序直接读失败输出空文件或输出异常内容。解决评测时不要假设选手会遵循“标准输入输出”约定在评测打包脚本里先扫描一下源码发现文件读写就往当前目录丢一个空壳文件兜底或者直接判定为不合规。实战中竞赛题面明确写了“标准输入输出”但总有人不读或者从旧代码模板里带了出来。让评测系统快速暴露这类问题的方法很简单第一个测试点就放最弱的数据断言程序有有效输出。如果第一个测试点就全WA且输出为空优先查文件读写。5.5 行尾空格与多余空行diff策略没对齐现象选手程序输出结果在视觉上和标准答案完全一致但评测判WA。你手动用cat看两边输出看不出任何区别一对字节才发现差异。原因评测端的比对策略不同。diff -Bb宽容处理行尾空格和末尾空行但有些配置或特判程序用的是逐字节比对那么一个末尾空格就足以让整题翻车。解决确认配置文件用的是否是保持-Bb宽大策略。如果题面没有刻意要求严格模式我强烈建议宽容。因为对于大多数竞赛题行尾空格和空行差异不是选手算法问题不应该扣分。题面写上“以评测系统判定为准”这句话胜过后面对着一堆WA反复解释。6. 验证新题配置的最后一公里随机对拍与批量校对一道新题配完最危险的不是数据弱而是配置本身有坑。我吃过一次大亏自己配的新题数据生成脚本写错了边界导致第一版标准答案本身就是错的比赛当天四十多人全在WA后来查出来是标准程序的问题。从那以后我每配一道题都会做一轮随机对拍。做法很简单写一个暴力但逻辑简单的验证程序和一个随机数据生成器然后生成大量小数据跑一遍CENA评测同时把暴力程序的结果拿来和标准答案比对# 随机生成测试数据并交给CENA评测 for i in $(seq 1 10); do python3 gen.py /opt/cena/problems/p1001/data/test.in # 用暴力程序生成标准输出 ./brute_force test.in /opt/cena/problems/p1001/data/test.out # 跑一次评测确认标准程序得分 ./run_judge.sh p1001 test.in test.out done逻辑说明这个脚本的核心思想是让“标准答案”本身先经过暴力程序校验。如果标准程序和暴力结果在小数据上不一致说明标准程序有问题评测下一百遍都没意义。用随机数据反复确认一致后题目的正确性才有保障。参数说明gen.py是随机数据生成器数据规模要小这样暴力程序跑得快才能在短时间内完成几十上百组交叉验证。数据规模一大暴力程序先超时验证就卡壳。我还给自己加了一道保险所有新题正式使用前拿一台不联网的机器做一次全流程模拟赛让一个不参与出题的人当“小白鼠”选手提交一份参考解法全程观察评测日志。这一步能暴露出权限问题、路径问题、数据遗漏等各种配置阶段发现不了的细节。CENA这类评测软件本身不复杂配置链路才是一场比赛成功与否的关键。希望这些从实战里淌出来的习惯能帮你少走几趟夜路。本文还有配套的精品资源点击获取