两个-I都能编译成功,结果却不同:沿TinyCC的TCCState追一次头文件查找
我想研究TinyCC最主要的原因是它小感觉自己能读得下去。打开源码以后还是会遇到一个很大的结构体TCCState。里面有头文件路径、库路径、用户选项也有编译过程中留下的信息。如果从第一个字段开始逐个看很容易知道名字却不知道它们怎样一起工作。我先挑一个自己用过的选项-I。它告诉编译器去哪里找头文件这个目录到底怎样留到后面使用同名头文件两个目录都能找到我准备了两个目录都放一个叫blog_choice.h的文件。A目录定义HEADER_VALUE11B目录定义HEADER_VALUE21。程序把它与命令行宏提供的3、库函数返回的5相加。只交换两个目录的顺序实际结果是这样目录顺序编译运行输出-Iinclude-a -Iinclude-b成功value19-Iinclude-b -Iinclude-a成功value2919是113529是2135。其他输入没变差别来自选中了不同的头文件。完整程序和可执行命令放在附录可以自己换一下顺序。这次用的是#include blog_choice.h。我们先跟着这一种包含方式走不把双引号、绝对路径和include_next一起塞进来。-I读进来以后先放在哪里参数解析遇到-I会调用tcc_add_include_path。它的函数体很短{ tcc_split_path(s, s-include_paths, s-nb_include_paths, pathname); return 0; }这儿没有打开blog_choice.h连文件名都没有传进来。传的是目录pathname以及状态结构体中两个成员的地址。对应源码include_paths指向一组路径指针nb_include_paths表示其中有效的路径数量。先加A、再加B和这次查找有关的内容就像下面这样include_paths[0] - include-a include_paths[1] - include-b nb_include_paths 2这里的2是有效数量不是结构体大小也不是数组容量。两条路径的字符没有直接塞进TCCState而是通过指针接到外面分配的存储。现在还要确认A、B真的按这个顺序放进去了吗第二次添加时哪里发生了变化加第二条路径不会覆盖第一条吗继续进tcc_split_path能看到它把解析出的路径交给追加函数dynarray_add(p_ary, p_nb_ary, tcc_strdup(str.data));再看dynarray_add末尾动作是先在下标nb处放入新指针nb加1再把数量写回。追加函数、路径拆分对于这两条用户目录第一次nb是0A放在[0]数量变成1第二次nb是1B放在[1]数量变成2。不是每次都写[0]所以前面的路径不会被后面的覆盖。先看只有A的状态。include_paths保存的是数组的地址[0]里另存着字符串的地址。这是两次沿指针找东西先到数组再从[0]找到字符。再加B时[0]仍然能找到A新路径放到[1]计数变成2。两条字符串分别有自己的存储不是把A那份字符改成B。图只展开这两条用户目录的有效元素不画备用容量也不是实际内存地址快照。两个图里把数组放在同一位置便于对照真实追加时重分配可能让数组地址改变不能把图上的位置当成地址不会变。到这里也能回答前面的函数为什么传两个成员的地址。追加时计数要改空间不够时还要重新分配指针数组数组地址也可能变。辅助函数需要把这两种变化写回TCCState不能只改一份局部副本。那句tcc_strdup先留着稍后看。先去找谁会读取刚放好的A、B。到了include怎样从A走到B处理包含文件的代码在tccpp.c。搜索循环中目录列表这一支是int j i - 2, k j - s1-nb_include_paths; path k 0 ? s1-include_paths[j] : s1-sysinclude_paths[k]; pstrcpy(buf1, sizeof(buf1), path); pstrcat(buf1, sizeof(buf1), /);这两个减法为什么要这样写看外围循环才知道i的前两个位置另有用途i0检查绝对路径i1处理双引号包含时的当前文件目录。我们的文件名不是绝对路径用的又是尖括号这两处会跳过到了i2才开始读目录列表。循环与打开文件的完整上下文所以减2是把循环位置转换成目录下标。再减用户目录数量是为了判断是否已经越过用户目录、进入系统目录。先按A在前、B在后算前三个目录位置iji-2kj-2读哪一项20-2用户目录[0]A31-1用户目录[1]B420系统目录[0]最后一行还要求系统目录列表非空循环才会走到这个位置。我们这次找到A就结束不会真的走遍这三行。代码把取出的目录复制到buf1接上斜杠再接文件名。第一次尝试的就是include-a/blog_choice.h后面tcc_open若失败会continue去试下一项成功则进入这个头文件的处理跳出当前搜索不会再打开B把A的定义替换掉。这个区别很重要不是把两个同名头文件都读完再挑其中一个。现在把参数换成B在前追加后的[0]变成B。相同的i2、j0这次拼出来的是include-b/blog_choice.h于是用到21运行结果变成29。命令行里的先后顺序就这样经过“追加到列表末尾”和“从列表前面查找”留到了最终结果里。两次都能编译成功并不奇怪因为两份头文件都满足程序的需要成功退出本身区分不了选的是哪一份。回头看为什么还要复制路径字符串刚才追加时用了tcc_strdup(str.data)没有直接把str.data放进数组。str是拆分路径时的临时字符串添加完这一项就会被清理。数组如果只留下这个临时缓冲区的地址等后面处理include再来读取就不能指望那份字符还有效。因此要复制一份。数组元素保存副本的地址临时字符串按自己的流程清理预处理器以后仍然能读到路径。不过复制出来的字符串不会凭空消失。继续找状态清理函数tcc_delete会看到dynarray_reset(s1-include_paths, s1-nb_include_paths);dynarray_reset先逐项释放路径字符串再释放装这些地址的数组并把数组指针置空计数降到0。状态清理、数组清理实现对应前面的图就是先释放右边的A、B字符串再释放左边的指针数组。只释放数组会丢掉找到那两份字符串的入口并没有释放字符串本身。再去看那个大结构体现在认识include_paths不只是知道“这里放头文件路径”。我们已经知道谁追加它数量怎样变搜索时从哪一项开始以及路径为什么能留到搜索阶段、最后由谁释放。这也是我想从TinyCC开始读编译器的原因能从熟悉的小选项出发沿几段函数把一个过程走完再扩大范围而不是先背下整个结构体。TCCState定义下一项可以看库路径但别直接把刚才的行为照搬过去-L目录增加搜索位置-l名字要求加入一个库library_paths不是把库文件内容存进去的数组。-D又会涉及宏与符号处理不是改一个整数成员就结束。本文源码固定在release_0_9_27对应提交CLI实验使用已有的Windows版TinyCC 0.9.27没有从这个提交重建二进制。前面字段和函数关系来自源码19/29来自实际运行不把CLI输出说成直接观察到了状态内存。完整命令见下面版本和其他边界放在最后。附录一完整源码与成功对照不需要拿一个大项目猜。我准备一个程序让三项输入都影响最终结果头文件提供一个值命令行宏提供一个值静态库函数提供一个值。在一个新建、可丢弃的实验目录中准备四个源文件。只运行下面的自有固定程序不使用不可信C代码。需要已安装的TinyCC 0.9.27 x86_64 Windows与PowerShell以下命令块在同一个窗口按顺序执行。目录结构实验目录/ include-a/blog_choice.h include-b/blog_choice.h main.c answer.c lib/ 命令会创建include-a/blog_choice.h#define HEADER_VALUE 11include-b/blog_choice.h#define HEADER_VALUE 21answer.cint blog_answer(void) { return 5; }main.c#include stdio.h #include blog_choice.h #ifndef USER_VALUE #error USER_VALUE must be supplied #endif int blog_answer(void); int main(void) { printf(value%d\n, HEADER_VALUE USER_VALUE blog_answer()); return 0; }在上述实验目录执行确保tcc已在PATH不要在现有项目根目录运行。本实验会创建同名对象、库和exe文件不改系统路径或编译器全局配置。$ErrorActionPreference Stop $PSNativeCommandUseErrorActionPreference $false $tcc (Get-Command tcc -CommandType Application | Select-Object -First 1).Source function T { $tcc args; if ($LASTEXITCODE -ne 0) { throw tcc failed: $args } } New-Item -ItemType Directory lib | Out-Null T -c answer.c -o answer.o T -ar rcs lib/libblogstate.a answer.o T -Iinclude-a -DUSER_VALUE3 -Llib main.c -lblogstate -o baseline.exe $output ./baseline.exe if ($LASTEXITCODE -ne 0 -or $output -ne value19) { throw baseline mismatch } $output这一轮的结果是value191135。我们同时检查输出和进程退出码不能仅凭“成功生成exe”认定程序行为正确。接着一次只改一个选项T -Iinclude-b -DUSER_VALUE3 -Llib main.c -lblogstate -o header.exe $output ./header.exe if ($LASTEXITCODE -ne 0 -or $output -ne value29) { throw header path mismatch } $output T -Iinclude-a -DUSER_VALUE7 -Llib main.c -lblogstate -o macro.exe $output ./macro.exe if ($LASTEXITCODE -ne 0 -or $output -ne value23) { throw macro mismatch } $output对照只改变什么本轮输出基准头文件11、宏3、库函数5value19更换-I头文件由11变21value29更换-D宏由3变7value23这说明选项改变了可观察的编译结果。但它不是直接观察TCCState内存的实验具体经过哪些字段和函数需要前面的源码阅读来解释。尤其不能把-D简化成“给结构体某个整数成员赋值”宏定义处理还有自己的符号和预处理逻辑。两个-I都有同名头文件会选哪个这次不换文件内容只交换两个搜索目录的顺序T -Iinclude-a -Iinclude-b -DUSER_VALUE3 -Llib main.c -lblogstate -o ab.exe $output ./ab.exe if ($LASTEXITCODE -ne 0 -or $output -ne value19) { throw A before B mismatch } A before B: $output T -Iinclude-b -Iinclude-a -DUSER_VALUE3 -Llib main.c -lblogstate -o ba.exe $output ./ba.exe if ($LASTEXITCODE -ne 0 -or $output -ne value29) { throw B before A mismatch } B before A: $outputA在前得到19B在前得到29。在这组使用尖括号、自有头文件且两个目录都有同名文件的实验中先搜索的目录决定选中哪一份头文件。路径列表不仅保存“有哪些目录”还保留影响查找的顺序。两次编译都成功却用了不同的文件。这也解释了为什么“能编译”不是路径配置正确的充分证据还要检查实际选中的内容。这里用输出区分内容不把这个小对照扩大成所有include搜索规则的证明。附录二缺少选项时失败也要符合预期五个成功对照之外再分别去掉-I、-D、-L。它们不能都只记成“编译失败”需要核对对应诊断。去掉什么本轮应出现的诊断为什么检查它-Iinclude-ablog_choice.h不存在缺的是自有头文件搜索路径-DUSER_VALUE3USER_VALUE must be supplied命中程序主动设置的#error-Llibblogstate库不存在缺的是实验静态库搜索路径三个对照还必须返回非零退出码。否则错误文字碰巧出现也不能算验证通过。下面的失败检查程序依赖前面生成的实验库需要在同一窗口、同一目录继续执行。为了避免环境变量暗中补上路径本轮自动验证移除了子进程环境中的CPATH、C_INCLUDE_PATH和LIBRARY_PATH在独立目录执行库使用实验专属名字。它不修改用户的系统环境变量。自行复现若诊断不同应先记录环境不把其他失败当作预设失败通过。前面的表格已分别列出成功结果和失败诊断不再用一张缩小后的图片重复它们。两个-I的顺序对照见前面的命令和说明自动验证的完整命令、退出码和stdout/stderr保留在本地证据中。在前面成功对照的同一个PowerShell窗口、同一个实验目录执行下面的程序。它复用$tcc并依赖已经生成的实验库。function ExpectFailure { param([string]$diagnostic, [string[]]$compilerArgs) $saved $ErrorActionPreference try { $ErrorActionPreference Continue $messages $tcc compilerArgs 21 $code $LASTEXITCODE } finally { $ErrorActionPreference $saved } if ($code -eq 0 -or (($messages | Out-String) -notmatch $diagnostic)) { throw expected failure or diagnostic missing } $messages | Out-String } ExpectFailure blog_choice\.h.*not found (-DUSER_VALUE3,-Llib,main.c,-lblogstate,-o,no-include.exe) ExpectFailure USER_VALUE must be supplied (-Iinclude-a,-Llib,main.c,-lblogstate,-o,no-macro.exe) ExpectFailure library.*blogstate.*not found (-Iinclude-a,-DUSER_VALUE3,main.c,-lblogstate,-o,no-library.exe)失败调用不用成功实验的T包装我们需要保存非零退出并检查错误而不是遇到预期失败就中断整个实验。临时调整PowerShell错误偏好是为了收集原生命令的stderr结束后立即恢复不是把所有错误忽略。退出码和对应诊断有一个不符合就让验证程序报错。版本、验证边界与相关阅读本文源码固定为TinyCC的release_0_9_27提交d348a9a51d32cece842b7885d27a411436d7887b实验使用已有的TinyCC 0.9.27 x86_64 Windows没有从该提交重建二进制。因此源码阅读与CLI实测分开陈述不声称构建逐字节对应。自动验证从正文提取源文件和命令检查五个成功结果、三个预期失败还故意改错基准预期值、交换搜索顺序但保留原预期确认检查程序会在对应断言处报错而不是只会输出“通过”。CLI实验没有直接观察TCCState内存也没有测试libtcc宿主、并发或其他后端。这个版本仍有全局tcc_state不能凭API接收上下文指针便断言线程安全它也保留inline函数token列表不能把单趟编译理解成任何内容都只访问一次。这些问题值得另写一篇不在这里扩成未经验证的结论。已有《Tinyc编译器翻译》承担手册查询《tcc -run不是解释C》展开内存编译执行。这篇的重点是从一个结果差异追到配置的保存、使用和释放。

相关新闻

连续出现三次,不是总共出现三次:修正一道SQL题里的错误分组

连续出现三次,不是总共出现三次:修正一道SQL题里的错误分组

旧草稿里有一道“连续数字”的SQL题:从Logs中找出连续出现至少三次的值。 先看这两串记录,按id从小到大排: 1、1、1 找到1 1、2、1、2、1 找不到1两边的1都出现了三次,但第二串每次都被2隔开。所以只写GROUP BY num…

2026/10/19 12:06:19 阅读更多 →
Svelte 作为前端框架,本身并不是 Python 技术栈的一部分,但在 Python 项目中,它非常适合作为高性能前端层使用

Svelte 作为前端框架,本身并不是 Python 技术栈的一部分,但在 Python 项目中,它非常适合作为高性能前端层使用

Svelte 作为前端框架,本身并不是 Python 技术栈的一部分,但在 Python 项目中,它非常适合作为高性能前端层使用。2026 年的主流方案通常是:前端采用 Svelte 5 SvelteKit,后端采用 FastAPI / Django / Flask&#xff0c…

2026/10/19 12:06:19 阅读更多 →
如何安全地给 Windows 11 减肥:清掉预装软件,让电脑快起来

如何安全地给 Windows 11 减肥:清掉预装软件,让电脑快起来

在我的 Windows 11 专业版 25H2 测试机上,一次全新安装后,系统里冒出大约 18 到 20 个我根本没要过的预装应用;用不了几天,任务管理器里的开机进程数就飙到三位数。这哪叫干净的操作系统,简直是个展厅。 不过好消息是,微软允许我们直接在「设置」里把这些用不上的预装应…

2026/10/19 12:06:19 阅读更多 →

最新新闻

HTTPS加密过程详解:从TLS握手到密钥协商与排障实战

HTTPS加密过程详解:从TLS握手到密钥协商与排障实战

前阵子某公司同事拿了个抓包文件来找我,说某个内部系统的接口访问时好时坏,偶尔卡住十几秒然后报错。我打开抓包软件扫了一眼,发现大量TLS握手中的重传报文,再往下看,原来他压根没把HTTPS加密过程当回事——以为是“握…

2026/10/19 13:01:18 阅读更多 →
Agentic AI落地社会价值:提示工程架构师如何拆解信任与责任难题

Agentic AI落地社会价值:提示工程架构师如何拆解信任与责任难题

Agentic AI的社会价值最近被反复讨论,但真正落到实际业务场景里,落地障碍往往不是模型能力,而是隐藏在提示工程里的一系列细节。作为一个长期和Agent打交道的提示工程架构师,我越来越清楚一件事:我们写的每一段Prompt&…

2026/10/19 13:01:18 阅读更多 →
售货机管理系统设计:设备状态、库存预警与远程指令三重闭环

售货机管理系统设计:设备状态、库存预警与远程指令三重闭环

简介:本资源是一套基于RuoYi框架(SpringBootVue前后端分离)开发的售货机管理系统,面向Java全栈初学者、Web应用开发者及后台管理平台实践者,聚焦智能硬件运营场景中的商品、库存、设备、支付与销售数据等核心管理需求。…

2026/10/19 13:01:18 阅读更多 →
CMSSW离线数据处理框架:从LHC原始数据到物理分析的全链路实践

CMSSW离线数据处理框架:从LHC原始数据到物理分析的全链路实践

简介:CMSSW(Compact Muon Solenoid Software)是CERN大型强子对撞机CMS实验核心的开源离线数据分析框架,面向粒子物理研究人员、高能物理方向研究生及计算科学开发者,用于处理海量探测器数据、执行事件重建、物理对象识…

2026/10/19 13:01:17 阅读更多 →
ROS 2中VS Code无法识别rclcpp?从环境变量到CMake配置全排查

ROS 2中VS Code无法识别rclcpp?从环境变量到CMake配置全排查

前阵子在技术群里看到一位刚转到ROS 2的同行提问&#xff1a;新建的rclcpp项目在VS Code里打开&#xff0c;#include <rclcpp/rclcpp.hpp>这一行直接画了红色波浪线&#xff0c;代码提示完全失灵&#xff0c;尝试编译又报出find_package找不到rclcpp包的错误。这个问题我…

2026/10/19 13:01:17 阅读更多 →
C标准库源码深度剖析:从printf到malloc的底层实现与移植指南

C标准库源码深度剖析:从printf到malloc的底层实现与移植指南

简介&#xff1a;这份资源是C标准库的完整源代码集合&#xff0c;面向希望深入理解C语言底层实现、提升系统级编程能力的开发者与学习者。它涵盖输入输出、字符串处理、内存管理、数学运算、时间日期、文件系统接口等核心模块&#xff0c;适合在阅读源码、优化性能或进行底层开…

2026/10/19 13:00:15 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →