简介:这款免安装版SIMGUI是面向C++与Python开发者的代码查重工具,基于Electron与Element UI构建,内置SIM相似性检测算法,无需安装依赖即可运行。压缩包共87个文件,约70MB,主要包含exe主程序、dll与pak运行组件、bin数据文件以及png操作截图,其中pak多用于界面多语言支持,exe为程序入口,dll负责运行时依赖,png为操作指引截图,结构清晰,解压后直接使用。已有529人学习下载,适合教学作业查重、学术论文代码比对及团队代码质量审查等场景。使用时通过文件选择器导入代码,可调整查重灵敏度、忽略大小写/空格/注释等参数,点击检测即可定位重复代码段,并以可视化列表展示比对结果,支持导出完整报告。免安装特性省去环境配置环节,尤其适合需要批量检测Python/C++作业相似度的师生,或希望快速梳理项目冗余代码的开发者。
1. 收代码收到手软时,SIMGUI 是你最该先装的那个工具
期末或者季末收代码的日子,我见过太多人抱着几十个压缩包一个个解压,再用编辑器的”在文件中查找”肉眼比对两个文件的重复片段。一个人改个变量名、把函数顺序调一调,肉眼根本看不出来,等提交到查重系统里被打回,师生两边都难受。这个标题里的 SIMGUI,说的是把 SIM 相似度检测算法包装成图形界面,同时提供 C++ 编译好的核心程序和 Python 调用接口,做成免安装绿色版,解压就能用。它解决的就是代码初筛问题:把”哪些提交大概率互相抄了”先筛出来,让人把精力放在复核而不是大海捞针上。适合三类人:收大作业和毕设的教师助教、做代码审计的技术负责人、以及想在上交前自己预检一遍的学生。
2. 为什么偏偏是 SIM:查重算法选型与 C++ / Python 双版本分工
2.1 先分清 MD5、diff 和 SIM 各自的适用边界
很多人一提到查重,第一反应是比对 MD5,或者用 diff 看两文件差异。MD5 只能告诉你两个文件是否完全相同,只要少一个空格、换一个变量名,哈希值就完全变了,对”改改就交”的场景毫无威慑力。diff 是逐行比较,它能列出差异行,但它天生是给版本控制设计的,目标是展示”改了哪几行”,而不是量化”整体有多像”。两份代码如果函数顺序被整体打乱,diff 会输出一大堆”删除+新增”,看起来像完全重写,实际上只是挪了个位置。
SIM 算法不一样。它不关心顺序是否完全一致,而是去找两个文本之间所有的公共连续片段,也就是”运行(run)”,再用公共片段的总长度除以参考文本长度,算出相似度百分比。打个粗糙的比方:它把一段代码拆成连续的小块,凡是两边都对得上的块都记下来,最后加总。所以哪怕你给人家的代码换了变量名、改了函数名、调整了声明位置,只要句子和结构还是照抄的,SIM 给出的相似度依然会很高。这正是代码查重需要的”宽松匹配”。
表格对比一下:
| 工具 | 能查什么 | 查不出什么 | 适合场景 |
|---|---|---|---|
| MD5 | 完全相同的文件 | 任何微小改动 | 文件完整性校验 |
| diff | 逐行差异 | 结构搬移、改名换序 | 代码审阅、版本对比 |
| SIM | 公共连续片段量化相似度 | 语义重写的两套代码 | 抄袭初筛、重复度检测 |
2.2 SIM 的相似度是怎么算出来的:公共片段与归一化
SIM 的核心计算过程可以简化成三步。第一步,把输入文本按行做切分,每行再切成更细的单元,这个单元可以是词、是符号、也可以直接是字符,取决于你用的是哪个变体和什么参数。第二步,在两个文本之间寻找公共连续的单元序列,凡是能连起来匹配的都算一个 run,每个 run 的长度被记录下来。第三步,把所有 run 的长度加总,除以参考文本的总长度,得到相似度百分比。
这个”除以参考文本”的细节很关键。同样一对文件,用 A 做参考还是用 B 做参考,得到的百分比可能不一样,因为如果 A 是 1000 行、B 是 2000 行,公共部分占 A 的比例和占 B 的比例自然不同。所以命令行版的 sim_c 才需要显式指定哪个是参考文件、哪个是测试文件。做批量初筛时,习惯上是把”疑似被抄的原始版本”当作参考,把”学生交的版本”当作测试。
SIM 对空白和换行的容忍度也比较高,因为它匹配的是单元序列而不是原始字符串。这个特性是查重需要的,但也带来一个副作用:代码格式化工具跑一遍之后的”美化版”,照样是高相似度;反过来,”故意打乱空行、拆行长表达式”并没有太多作用。理解这一点,后面调试结果时就不会误判。
2.3 C++ 和 Python 两个版本怎么分工:核心与外壳
标题里同时出现 C++ 和 Python,不是冗余,而是两个版本各自的位置不同。C++ 的 sim_c 是算法核心,负责跑匹配,速度很快,几十 KB 到几 MB 的源码文件都是毫秒级完成。但 C++ 版本不好改:要在 Windows 上编译需要装 MinGW 或 Visual Studio 工具链,改一行算法逻辑就得重新编译一次,它对大多数老师来说门槛太高。
Python 版本的价值在于”能嵌进流程”。你可以写一个几十行的脚本,遍历一个目录里的所有 .cpp/.py 文件,两两调用核心计算,生成一个 CSV 报告,把所有相似度超过阈值的配对列出来。这个批量能力是 GUI 版没有的。常见的做法是:Python 脚本用 subprocess 调用 C++ 编译好的 sim_c.exe,自己只负责文件枚举、结果解析和报告输出。这样性能由 C++ 保证,灵活度由 Python 提供,两边各干各擅长的事。
至于 SIMGUI,本质上就是给这个流程包了一层界面:左边选参考文件、右边选待查文件、中间放一个阈值输入框,点一下按钮调用核心,最后把相似度百分比显示出来。免安装版则是把编译好的程序、依赖的动态库、可选的 Python 脚本和说明文件打成一个压缩包,不写注册表、不装服务,解压后直接运行。对机房电脑和没有管理员权限的办公电脑来说,这是最稳妥的部署方式。
3. 把免安装版跑起来:从解压到批处理的最小操作路径
3.1 免安装版的目录结构长什么样
常见的免安装版压缩包解开后,通常是这样一个结构:一个可执行文件(比如 sim_c.exe)、一个带界面的程序(比如 simgui.exe 或者 simgui.pyw)、一个说明文档,外加一个 data 目录放临时文件。C++ 版可能还会带一两个运行库 DLL,比如 vcruntime140.dll。如果缺这个文件,双击会直接报错,后面第 5 章会专门讲。
我一般建议先把包解压到一个纯英文路径下,避免之后因为中文路径或空格出问题,路径直接写成 D:\sim 或者 C:\checker 都是好习惯:
D:\sim\ ├─ sim_c.exe # 命令行核心程序,C++ 编译 ├─ simgui.exe # 简单图形界面封装 ├─ run.bat # 免安装启动器 ├─ batch_check.py # Python 批量调用脚本 └─ README.txt不要小看这个结构。命令行程序在服务器和批处理里用,GUI 程序给不熟悉命令行的同事用,Python 脚本给需要二次开发的人用,三者各司其职。如果你拿到的压缩包里没有 batch_check.py 这种脚本,完全可以自己写,后面 3.4 节会给一个可以直接抄的版本。
3.2 命令行方式:最可靠的第一个动作
不管是哪一家的免安装版,第一个最小验证动作永远是跑命令行,而不是双击 GUI。打开 cmd,进入解压目录,执行下面这行:
sim_c.exe -p 30 -s reference.cpp submitted.cpp这里 -p 30 表示相似度超过 30% 才输出结果,-s 表示只输出相似度数值摘要,不把每一处公共片段都打印出来。跑完你会看到类似32%这样的输出,这个数字就是提交文件相对参考文件的相似度。如果结果超过你的预期,再不加 -s 重新跑,它能打印出具体哪些行是公共的,方便肉眼复核。
命令行还有一个隐藏好处:它不吃 GUI 那套控件状态,路径带空格、文件编码奇怪、文件特别大,这些在命令行下都很容易暴露问题。我第一次用某个绿色版时,GUI 点按钮没反应,反而是命令行报了一个明确的文件打不开错误,一下子定位到是路径中有中文。从那以后,我的排查顺序永远是命令行优先。
3.3 Python 调用核心程序:把结果变成结构化数据
命令行适合单次比对,但收一百份作业时不能一个个手动跑。写一个 Python 脚本,用 subprocess 反复调用 sim_c.exe,把输出解析成结构化结果:
import subprocess import os def sim_score(reference, target, threshold=30): """调用 sim_c 计算相似度,返回百分比数值""" cmd = ["sim_c.exe", "-p", str(threshold), "-s", reference, target] result = subprocess.run(cmd, capture_output=True, text=True) # 典型的输出形如 "45%",也可能带路径前缀 for token in result.stdout.strip().split(): if token.endswith("%"): return int(token.strip("%")) return 0 if __name__ == "__main__": print(sim_score("ref.cpp", "stu_01.cpp"))这段代码有两处值得说明。第一,subprocess.run 用 capture_output 接住程序输出,避免命令行结果直接打到屏幕上,这样脚本才能拿到返回值做后续判断。第二,解析百分比时用了”找到以 % 结尾的 token”这种宽容策略,而不是死板地按第 0 个元素解析,因为不同编译版本的输出格式可能略有差异。如果你的 sim_c 输出里百分比带小数,把 int() 改成 float() 就行。
这种调用方式还有一个好处:C++ 核心进程跑崩溃了,Python 不会跟着崩。subprocess 每个进程是隔离的,某个文件编码异常导致核心读不了,最多返回异常退出码,Python 捕获到之后可以把这个文件丢进错误清单,继续处理剩下的文件。
3.4 给 GUI 版包一个可靠的启动器
免安装版最容易翻车的环节是用户不知道双击哪个文件。写一个 run.bat 放在根目录,让使用者只认这一个入口:
@echo off chcp 65001 >nul cd /d "%~dp0" start simgui.exe %*三行各自有作用。chcp 65001 把控制台代码页切到 UTF-8,避免 README 里中文说明乱码;cd /d "%~dp0" 把工作目录切到 bat 所在的目录,这样不管用户从哪个路径双击,程序都能找到同目录的依赖文件;最后 start 开 GUI。这个启动器的价值在配好环境后几乎察觉不到,但在没配好的电脑上,它能把”为什么双击没反应”这类问题直接消灭掉。
4. 参数与预处理:让查重结果从”能跑”变成”可信”
4.1 三个必调参数:阈值、输出模式、参考方向
用 SIM 做初筛,参数不用多,但每个都要弄清楚。最重要的就是相似度阈值 -p,默认值一般在 30 左右,意思是不足 30% 的配对直接不显示。收作业时我习惯把阈值设在 50 以上,因为低于 50 的配对大多是模板代码和公共头文件造成的虚高,逐个看过去浪费时间。反过来,如果是在做论文级代码核查,想宽进严出,可以把阈值降到 20,宁肯多筛一些出来给人眼判断。
第二个要理解的是输出模式 -s。加了它,程序只给一个数字;不加,会把公共片段和行号全打出来。第一次用的人往往看着满屏公共片段不知所措,实际上你应该始终配合 -s 用:先用数字排序,锁定嫌疑对,再去掉 -s 单对复核。这才是正确的工作流。
第三个经常被忽略的是参考文件与测试文件的顺序。前面说过,SIM 是用公共部分除以参考文件长度,那么谁做参考,结果就偏向谁。做批量检测时,如果你拿学生的 A 作业做参考去比 B 作业,又拿 B 做参考去比 A,两次出来的百分比不会一样。批量脚本里必须固定规则,比如始终用先枚举到的文件做参考,并在报告里同时列出双向的两个数值。
4.2 查代码之前先做一次文本预处理
直接拿原始代码喂给 SIM,结果会虚高。原因是每个项目的模板代码、公共头文件、自动生成的注释都算进了公共长度里。我一般会先对代码做一步轻量预处理:去掉空行、去掉注释、把制表符换成空格。这一步不追求把代码”改写”,只是为了减少公共噪声。
用 Python 做预处理很简单,下面是去掉注释和空行的最小版本,针对 C/C++ 和 Python 都做了处理:
import re def clean_source(code: str) -> str: # 去掉 // 行注释和 /* 块注释(粗略版,够用) code = re.sub(r'//[^\n]*', '', code) code = re.sub(r'/\*.*?\*/', '', code, flags=re.S) # 去掉 Python 的 # 注释,注意保留字符串里的 # lines = [] for line in code.splitlines(): stripped = line.strip() if stripped.startswith('#'): continue lines.append(line) return '\n'.join(lines)注意这段代码注释里写着”粗略版”。为什么说够用?因为它不处理字符串里的 // 和 #,比如一个 URL 写在字符串里会被误删,但对查重来说,误删字符串内容对最终相似度影响极小,可以接受。预处理的目标不是让代码变得”干净”,而是让查重结果更聚焦在实质性代码上。如果你要处理的是 Python 代码,字符串里的 # 还挺常见,可以把正则改成只删除行首空白后的 # 注释行,而不是删除所有 # 开头的行。
做完预处理再喂给 sim_c,你会观察到相似度明显下降,那个下降量基本就是模板代码贡献的虚高部分。如果你发现下降幅度特别大,先别急着高兴,去看看到底是模板多还是两个人真的写得像。
4.3 批量两两对比并生成 CSV 报告
批量查重的最终产物是一张表。下面这段脚本遍历一个目录下所有 .cpp 和 .c 文件,两两组合调用 sim_c,把结果存进 CSV:
import subprocess, os, glob, csv, itertools def score(reference, target): p = subprocess.run(["sim_c.exe", "-p", "0", "-s", reference, target], capture_output=True, text=True) for tok in p.stdout.split(): if tok.endswith("%"): return float(tok.strip("%")) return 0.0 files = sorted(glob.glob("submissions/*.cpp")) with open("report.csv", "w", newline="") as f: w = csv.writer(f) w.writerow(["ref", "target", "score"]) for ref, tgt in itertools.combinations(files, 2): s = score(ref, tgt) if s > 40: # 只记录超过 40% 的嫌疑对 w.writerow([os.path.basename(ref), os.path.basename(tgt), s])这里把 -p 设成 0,是因为筛选用途下你想要拿到所有配对的实际分数,再由 Python 端做过滤。阈值写在 Python 里比写在核心程序里更灵活,你可以在跑完一遍之后不重新比对,只改过滤条件再生成一份新报告。itertools.combinations 保证每对文件只比一次,不会出现 A 对 B 和 B 对 A 都跑一遍的浪费。
跑完打开 CSV,按 score 列降序排序,嫌疑对一目了然。这个报告可以直接发给复查的人,把可疑文件对挑出来逐个看 diff。到这一步,查重初筛的基本盘就立住了:C++ 核心负责算,Python 负责编排,CSV 承载结果,整个过程不依赖任何在线服务,全部本地完成。
5. 避坑与排查:5 个最常见的翻车现场
5.1 免安装版双击没反应,运行库缺失
现象:双击 simgui.exe,鼠标转两圈就没动静,或者直接弹窗提示”找不到 VCRUNTIME140.dll”。
原因:免安装版为了减小体积,通常会动态链接 Visual C++ 运行库,而目标机器没有安装 Microsoft Visual C++ Redistributable。这不是工具坏了,是系统的公共依赖缺失。
解决:优先装运行库,安装包只有几十 MB,装完重启程序即可。如果使用场景是机房或者不允许装软件的机器,那就在解压包里补上缺失的 DLL,放在 exe 同目录下,这样能绕开系统目录的权限问题。这个问题的排查方法也简单:从别的机器拷贝一个能正常运行的完整目录过来,如果拷贝过来还是报同样的错,那就是缺系统级运行库;如果拷贝过来能跑,说明是原压缩包的 DLL 不完整。
5.2 相似度虚高:两个不相干的文件也有 40%
现象:拿两个明显不同的课程设计项目去比,相似度居然有 40% 多,吓人一跳。
原因:多数是公共基础文本造成的。同一个老师的作业要求里通常会给出统一的头文件、结构体定义、注释模板;再加上代码风格都按同一种缩进习惯写,public/private 这些关键字在每个类里必然重复出现。SIM 不区分”公共模板”和”实质性抄袭”,它只统计公共片段长度。
解决:先做 4.2 的预处理,再去掉题目自带的模板声明,最后把阈值调高。如果预处理之后虚高还是存在,就需要怀疑是不是两个文件的公共片段集中在几行特别长的相似函数上,这时去掉 -s 跑一遍,看公共片段具体落在哪几行,是模板还是实质逻辑,一眼就知道。
5.3 Python 版本跑大文件内存泄漏式增长
现象:用 Python 重写或调用的查重脚本,处理几百个文件时越来越慢,最后内存占用冲到几个 GB。
原因:常见的是在读文件时用了 readlines() 把整个文件变成行列表,再反复切片拼接;SIM 算法本身在 Python 里对长文本做匹配,复杂度会随文本长度上升,字符串拼接又会产生大量临时对象。
解决:优先用 C++ 编译的核心程序做计算,Python 只做外围调度,这是最省心的方案。如果一定要纯 Python 实现,就避免一次性读入大文件,改成按块读取,并且只用字节流做匹配,不要反复 decode 成字符串。实时监控内存可以用 Python 的 tracemalloc 模块定位到具体哪一步在涨,一般都能查到是某个正则对超长行做了灾难性回溯。
5.4 GUI 版选了文件但结果一直是 0
现象:文件都选上了,点开始,结果永远是 0%,命令行跑同一对文件却很正常。
原因:十有八九是 GUI 在拼命令时把一个文件路径里包含的空格断开了。比如文件放在 C:\Users\My Documents\code\a.cpp 这种路径,GUI 直接把它拼进命令行,没有加引号,核心程序只收到了前半段。
解决:把文件放到纯英文无空格的路径下再试一次。如果必须支持中文和空格路径,就要在 GUI 或脚本里给路径加引号,即 subprocess 调用时不要用字符串拼接,而是用列表参数,让系统自己处理引号问题。这是一个很典型的 GUI 封装翻车点,本质上是界面层拼命令不规范,跟算法无关。
5.5 大改之后相似度还是 80%
现象:对方明显改了变量名、删了注释、把函数顺序换了个遍,结果查重还是 80% 出头。
原因:SIM 本来就这样。它抓的是连续公共片段,只要函数体的逻辑结构和语句顺序没变,改名删注释都不会显著降低相似度。这既是它的优势也是它的局限,它能抓住”换皮不换里”,但对”换了种写法实现同样功能”毫无办法。
解决:不要指望 SIM 能识别语义重写。遇到相似度高但”看起来改了很多”的,正确姿势是把两个文件并排打开,对照公共片段逐段看。SIM 的价值是把 100 份里最可疑的 8 份筛出来,不是替你下最终结论。查重工具做的是初筛,人做的才是判断。
6. 进阶:把查重工具变成你的初筛流水线
6.1 用”自比”校准阈值
很多人拿到工具后第一件事是拿真实作业开跑,其实在此之前应该先做一步校准:拿同一个文件跟它自己比,理论上相似度是 100%。然后做两个实验——把一份代码里的变量名全部批量替换,看看分数掉到多少;再删掉一半函数,看看分数掉到多少。这样你才能知道你设的阈值 50 到底代表”变量名级别的修改”还是”删了一半还能查出来”。我习惯在一个小目录里放几组自己造的样本,跑一遍记录分数,以后换了机器、换了编译版本,都拿这个目录回归测试一次。
6.2 用伪造样本来验证你的批处理脚本
批量脚本最怕的不是逻辑错,而是看着对但实际输出不完整。写一个假样本集验证是最省事的办法:在 submissions 目录里放 4 个文件,其中两个完全一样,一个改了三分之一,另一个完全不同,跑完看 CSV 里能不能精准列出前两组、过滤掉最后一组。验证通过后再上真实数据。这个习惯帮我抓出过两次脚本 bug,一次是 glob 通配符没匹配到子目录,一次是 CSV 写入时把高分项漏掉了,都是假样本暴露的。
6.3 把初筛结果交给人工复核
最后要交代的是工作流的位置。SIMGUI 这类工具适合放在流程的最前面,用最低成本把嫌疑面从一百份缩到十份,但它的输出不能直接当证据用。我现在的做法是:自动初筛 → 人工复核可疑对 → 需要时再用差分对比工具确认具体复制范围。整个链路里,SIM 只负责第一道线。你别指望靠一个百分比就向其他人交代,能把公共片段具体到函数级别,才算是真正查清楚了。
用这套流程处理过几次上百人的课程后,我的感受是:工具本身不是重点,怎么把你的阈值、你的预处理规则、你的复核标准固定成一套可重复的流程,才是省时间的核心。每学期开课之前花半天把脚本调好,后面几周收作业时就再也不用半夜对着两个文件揉眼睛了。希望这些路径和坑能帮你少走一段弯路。
本文还有配套的精品资源,点击获取