ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

tcc -run不是解释C:用返回码、产物和源码看内存编译

2026/10/7 10:07:16 拓冰建站 浏览量
tcc -run不是解释C:用返回码、产物和源码看内存编译 tcc -run 不是解释 C以前整理的《Tinyc编译器翻译》已经介绍过-run的用法、libtcc与代码生成不能把这些再包装成第一次发现。那篇是0.9.27参考手册的译文适合查询选项这篇只追一个能用实验验证的问题不先生成exe文件main怎样被调用返回值最后给了谁“C也能像脚本一样运行”说明了使用体验却不能据此推断是解释执行。本篇不重复罗列命令行选项而把返回码、产物与失败诊断放在一起再对照内存运行的源码路径。本轮用已安装的TinyCC 0.9.27x86_64 Windows做实验源码说明对应上游release_0_9_27标签。已安装二进制没有经过我从该标签重新构建因此版本号一致不等于我已证明它们逐字节对应。1. 不从速度宣传开始从一个返回7的程序开始将下面内容保存为demo.c#include stdio.h #define BIAS 3 int main(int argc, char **argv) { printf(argc%d\n, argc); printf(arg%s\n, argc 1 ? argv[1] : (none)); printf(value%d\n, 4 BIAS); return 7; }在新建实验目录用 PowerShell 执行tcc 已加入 PATHtcc -v tcc -run demo.c hello $LASTEXITCODE tcc -o demo.exe demo.c ./demo.exe hello $LASTEXITCODE两种运行方式在本轮都有下面输出退出码都是7argc2 arghello value7观察项-run先生成exe再运行hello进入main参数是是main返回7tcc进程退出码为7exe进程退出码为7实验工作目录新增exe没有有“目录没新增文件”只是本次工作目录观察不是磁盘全路径监控不能据此声称它不会读取头文件、库或做任何其他文件访问。-run的命令约定见 TinyCC 0.9.27手册。2. 不落exe不代表没有编译和链接再试一个只有外部声明、没有定义的函数#include stdio.h extern int missing_function(void); int main(void) { puts(MAIN_ENTERED); return missing_function(); }tcc -run unresolved.c在本轮以非零状态退出诊断包含未定义的missing_function也没有输出MAIN_ENTERED。这里不是只凭退出码判断找不到tcc、路径错误同样可能失败却不能用来证明符号解析机制。外部符号仍然需要解决-run不是绕过链接错误的捷径。3. 驱动程序在哪里分岔读 0.9.27的tcc.c 时不必一开始钻进完整语法分析器。先看输入处理后的输出分支内存输出类型进入tcc_run文件输出路径进入tcc_output_file。这是同一个编译驱动里的两种交付方式而不是两门不同的语言。继续看 tccrun.ctcc_run依次完成自动重定位、取得 main 的符号地址再通过函数指针调用它并返回其结果。可概括为源文件 - 编译产生代码/数据/符号 - 重定位让地址引用能指向实际位置 - 查main的地址 - 在tcc进程内调用main(argc, argv) - 将main返回值交回驱动这条执行链解释了三个实验现象可以不先交付一个exe文件仍可能因外部符号无法解析而失败程序的返回7会传到命令退出码。这里的图是源码流程概括不是本轮用调试器捕获的逐函数轨迹。我没有实测多个体系结构、完整重定位类型或边界检查模式所以不扩展成“TinyCC所有运行模式都这样”的结论。4. 它给我的新理解不是“编译器很小”以前我容易把“编译”想成“生成一个文件”。更准确地说编译首先形成可执行代码及相关信息文件只是其中一种载体。代码生成、符号解析、地址重定位、调用入口不必全部以独立外部工具和落盘步骤出现。也因此TinyCC编译快和生成程序运行快是两个问题。本篇没有性能基准不写“比GCC快多少倍”也不把输入源码当成安全脚本。-run执行的是本机程序文件、网络和进程权限仍属于当前执行账户。如果用它做自己的小工具可以减少编译后再启动的操作如果执行别人交来的C代码就需要真正的隔离而不是把-run当作解释器沙箱。少一步启动不等于少了全部工程问题这个实验让我更愿意按任务评价工具而不是给TinyCC贴一个“轻量所以万能”的标签。我想完成的任务这轮证据支持什么还不能推出什么运行自己的短C程序看看输出和参数-run能在一次命令中完成本例编译与执行任意现有工程都能不改配置直接运行将本例交给没有tcc的人运行显式输出路径生成了demo.exe-run本身提供了可分发文件本例exe在其他机器上的依赖已验证在脚本里判断执行是否符合预期main返回值传给了命令退出码非零一定是编译失败本例正常程序就返回7遇到外部函数没有定义链接诊断出现入口哨兵没有输出内存执行不需要解决外部符号执行别人给我的C代码它会执行本机代码编译器替我隔离了权限和副作用尤其是退出码我们的main故意返回7所以直接套用“非零就说明编译器坏了”的脚本会误判。反过来链接失败也可能非零必须结合诊断、程序约定和当前阶段区分。方便的入口并没有替调用者定义成功条件。因此我会先用它做自有小程序的快速实验和编译过程学习要交付文件就明确选择文件输出要替换项目原来的编译链还要补项目兼容性、依赖和生成程序性能测试。本篇没有做这些测试不能用一次短程序通过代替选型结论。这一点也是小工具值得研究的原因操作被压缩了底下的编译、符号解析和入口调用却没有凭空消失。真正需要理解的不是“它少了几个按钮”而是那些步骤在哪里完成、失败怎样被观察。5. 把观察写成可重复的检查下面是独立的 PowerShell 验证程序不依赖下载本人的项目。需要 Windows 上的 TinyCC 0.9.27tcc已加入 PATH或将环境变量BLOG_TCC设为它的完整路径。它只运行下面固定的两份 C 源码不接受陌生人提交的程序。程序新建随机实验目录先核对实际版本再运行-run比较显式生成exe后的输出最后检查未定义符号的失败诊断及入口哨兵。每次立即读取退出码不用最后一条成功命令覆盖先前失败。目录保留便于检查不递归清理其他路径。这里检测的是版本输出不证明二进制的构建来源。Windows PowerShell可能把外部命令的stderr转换成错误记录因此只对预期失败的那次调用暂时使用Continue随后恢复原设置再做严格断言不是忽略整段实验的错误。命令块可以直接在PowerShell中执行不需要改全局脚本执行策略。$ErrorActionPreference Stop $PSNativeCommandUseErrorActionPreference $false $tcc if ($env:BLOG_TCC) { $env:BLOG_TCC } else { tcc } $lab Join-Path $env:TEMP (tcc-run-lab- [guid]::NewGuid().ToString(N)) New-Item -ItemType Directory -Path $lab | Out-Null $previous Get-Location try { Set-Location $lab #include stdio.h #define BIAS 3 int main(int argc, char **argv) { printf(argc%d\n, argc); printf(arg%s\n, argc 1 ? argv[1] : (none)); printf(value%d\n, 4 BIAS); return 7; } | Set-Content -Encoding ASCII demo.c #include stdio.h extern int missing_function(void); int main(void) { puts(MAIN_ENTERED); return missing_function(); } | Set-Content -Encoding ASCII unresolved.c $version ( $tcc -v) $versionExit $LASTEXITCODE if ($versionExit -ne 0 -or ($version -join n) -notmatch 0\.9\.27.*x86_64 Windows) { throw expected TinyCC 0.9.27 x86_64 Windows } $version $before (Get-ChildItem -File | Select-Object -ExpandProperty Name) $direct ( $tcc -run demo.c hello) $directExit $LASTEXITCODE if ($directExit -ne 7 -or ($direct -join n) -ne argc2narghellonvalue7) { throw unexpected -run output or status } $after (Get-ChildItem -File | Select-Object -ExpandProperty Name) if (Compare-Object $before $after) { throw -run changed the lab file list } $tcc -o demo.exe demo.c if ($LASTEXITCODE -ne 0 -or -not (Test-Path demo.exe)) { throw compile failed } $binary ( ./demo.exe hello) $binaryExit $LASTEXITCODE if ($binaryExit -ne 7 -or ($binary -join n) -ne ($direct -join n)) { throw executable differs from -run } $savedErrorAction $ErrorActionPreference try { $ErrorActionPreference Continue $unresolved ( $tcc -run unresolved.c 2 unresolved.stderr.txt) $unresolvedExit $LASTEXITCODE } finally { $ErrorActionPreference $savedErrorAction } $diagnostic Get-Content -Raw unresolved.stderr.txt if ($unresolvedExit -eq 0 -or $diagnostic -notmatch undefined symbol.*missing_function) { throw expected the missing_function link diagnostic } if (($unresolved -join n) -match MAIN_ENTERED) { throw main unexpectedly entered } UNDEFINED_SYMBOL_CHECKED $diagnostic.Trim() TCC_RUN_CHECKS_PASSED lab $lab } finally { Set-Location $previous }未定义符号那一步的诊断是预期现象保存在实验目录的unresolved.stderr.txt不应为了“没有红字”把它删掉。程序同时检查非零退出、missing_function诊断与没有入口哨兵输出。核稿时把正常程序的return7故意改为return8检查会失败说明验证不只是观察两条运行路径输出相同。验证范围仍是这两份固定源码不是TinyCC兼容性测试集没有入口输出也是本例的观察证据不是覆盖任意程序启动行为的证明。