【新手必看】编译知识从入门到避坑,一文吃透流程+报错排查,学完直接落地

news2026/3/28 2:49:31
文章目录第一章 编译与解释的区别别再混淆了1.1 编译型语言一次性翻译运行更高效1.2 解释型语言逐行翻译调试更灵活第二章 编译完整流程4步从代码到可执行文件2.1 预处理清理代码替换宏与头文件2.2 编译语法检查生成汇编代码2.3 汇编直译汇编代码生成目标文件2.4 链接整合资源生成可执行文件第三章 3类高频编译报错手把手教你排查3.1 预处理错误宏定义、头文件相关报错3.2 编译错误语法、语义相关报错3.3 链接错误符号引用、库依赖相关报错第四章 实用编译技巧提升效率避坑指南4.1 编译选项选择适配调试与发布场景4.2 大型项目编译提升编译速度的技巧4.3 编译环境配置避免环境相关报错第五章 编译实战案例巩固所学知识5.1 简单C语言程序编译全过程5.2 常见报错排查实战在编程开发中几乎所有编译型语言C/C、Go、Java等都离不开“编译”这一步但很多人踩坑无数却始终没摸清底层逻辑——点击编译按钮报错就慌神优化选项乱选导致调试卡壳链接错误排查半天找不到根源。作为常年和编译器打交道的开发者今天就用最通俗的表述把编译的核心知识点、实操流程、高频报错解决方案一次性讲透不管你是零基础新手还是有一定经验的开发者学完都能避开90%的编译坑直接用到实际开发中。第一章 编译与解释的区别别再混淆了1.1 编译型语言一次性翻译运行更高效编译的核心逻辑很简单就像我们把中文小说翻译成英文小说译者一次性把整本书翻译完后续读者直接看翻译好的英文版本即可不用再依赖原文。对应到编程中编译器会一次性将全部源代码转化为计算机能直接执行的机器语言二进制指令生成可执行文件比如Windows下的.exe、Linux下的.out文件。这类语言的优势很明显运行时无需依赖源代码和编译器执行速度快、效率高适合开发大型项目、底层开发等场景。常见的编译型语言有C、C、Go比如我们写的C语言代码经过编译后生成可执行文件双击就能运行这就是编译的作用。1.2 解释型语言逐行翻译调试更灵活和编译型语言相反解释型语言就像同声传译译者逐句翻译听众听完一句再听下一句不会提前把整段内容翻译完。对应到编程中解释器会逐行读取源代码边解释边执行不会生成可执行文件运行时必须依赖解释器。这类语言的优势是开发调试灵活写完一行代码就能运行查看结果不用等待完整编译过程但缺点是执行速度比编译型语言慢。常见的解释型语言有Python、JavaScript比如我们运行Python脚本时必须安装Python解释器否则无法执行。第二章 编译完整流程4步从代码到可执行文件很多新手误以为“编译”是一步到位的操作其实不然。以最常用的C/C语言为例完整的编译流程分为4个步骤每一步都有明确的作用缺一不可任何一步出错都会导致编译失败。2.1 预处理清理代码替换宏与头文件预处理是编译的第一步主要负责对源代码进行“文本级别的清理和替换”不涉及任何语法检查相当于给代码“做体检、清垃圾”。具体操作有4点每一点都和我们日常开发息息相关首先是宏定义替换比如我们在代码中定义#define PI 3.14预处理阶段会把代码中所有出现“PI”的地方全部替换成3.14替换完成后宏定义本身会被删除不会参与后续编译。这里要注意一个避坑点宏定义没有分号结尾若多写分号会导致后续编译报错比如#define PI 3.14; 会被替换成3.14;进而引发语法错误。其次是头文件处理我们写代码时常用的#include stdio.h、#include “myfunc.h”预处理阶段会把这些头文件的全部内容直接插入到当前源代码中。比如#include stdio.h会把标准输入输出库的相关声明和定义全部复制到代码中这也是为什么我们能使用printf、scanf等函数的原因。除此之外预处理还会删除代码中的所有注释不管是//开头的单行注释还是/…/的多行注释都会被彻底清理不会参与后续的编译过程。另外条件编译比如#ifdef DEBUG、#else、#endif也会在这个阶段处理根据条件判断保留或删除对应的代码块比如DEBUG模式下保留调试日志代码发布模式下删除。预处理完成后会生成一个后缀为.iC语言或.iiC语言的文件这个文件还是文本格式和原始源代码相比少了注释、宏定义多了头文件的内容整体更“干净”。2.2 编译语法检查生成汇编代码这一步才是真正意义上的“编译”编译器会对预处理后的.i/.ii文件进行严格的语法检查和语义分析确认代码没有语法错误后再将其转化为汇编语言代码。如果代码有语法错误编译阶段会直接报错停止后续所有流程这也是我们最常遇到的编译报错场景。语法检查的范围很广比如少写分号、括号不匹配、变量未声明、类型不匹配等都会被编译器检测到并且报错信息会明确指出错误所在的行号和错误类型方便我们排查。比如代码中写int a ;编译时会报错“syntax error before ‘;’”直接定位到这一行补充赋值内容即可解决。编译通过后会生成一个后缀为.s的汇编语言文件。汇编语言是机器语言的“助记符”比如mov移动数据、add加法、sub减法、call调用函数人类能勉强看懂但计算机无法直接执行它是源代码和机器码之间的中间过渡。2.3 汇编直译汇编代码生成目标文件汇编阶段由“汇编器”负责核心作用是将编译阶段生成的汇编代码.s文件逐行直译为计算机能识别的二进制机器码0和1。这一步不会做任何语法检查因为语法检查在编译阶段已经完成只要汇编代码没有格式错误就能顺利生成目标文件。汇编完成后会生成一个后缀为.oLinux/Mac系统或.objWindows系统的目标文件。这个文件已经是二进制格式里面存储的是对应源代码的机器码但它还不能直接运行原因有两个一是目标文件只包含当前文件的机器码比如我们写了main.c和func.c两个文件编译后会生成main.o和func.o两个目标文件单独一个目标文件无法完成完整的功能二是目标文件缺少依赖的系统库和入口函数无法被操作系统识别和执行。2.4 链接整合资源生成可执行文件链接是编译的最后一步也是最容易被忽略、报错最多的一步由“链接器”负责核心作用是将多个目标文件以及程序依赖的系统库、第三方库整合在一起解决“符号引用”问题最终生成可执行文件。举个最常见的例子我们在main.c中调用了func()函数但func()的实现放在func.c中编译后生成main.o和func.o两个目标文件。main.o中只包含func()的调用指令没有func()的实现代码这时候链接器就会找到func.o将其中func()的实现代码整合到main.o中同时链接程序依赖的系统库比如stdio库最终生成可执行文件。这里有一个高频避坑点如果链接时找不到某个函数的实现或者库文件路径错误就会出现“链接错误”。比如忘记将func.c加入编译列表链接器找不到func()的实现就会报错“undefined reference to ‘func’”如果使用了第三方库但没有指定库路径就会报错“cannot find -lxxx”xxx为库名。第三章 3类高频编译报错手把手教你排查开发中遇到的编译问题本质上都对应上述4个流程中的某一步只要找准报错对应的流程就能快速排查解决。下面整理了3类最常见的编译报错包含报错原因、排查步骤和解决方案新手直接套用就能解决问题。3.1 预处理错误宏定义、头文件相关报错预处理错误主要发生在编译的第一步常见的报错信息有两种“undefined reference to XXX”宏未定义和“No such file or directory”头文件找不到这两种错误都很好排查重点关注宏定义和头文件路径即可。对于“宏未定义”报错首先检查代码中是否有对应的#define宏定义比如报错“undefined reference to PI”就查看代码开头是否有#define PI 3.14其次检查宏定义的作用域宏定义是从定义位置开始生效直到文件结束如果在宏定义之前使用了宏也会报错只需将宏定义移到使用之前即可另外还要注意宏定义的拼写大小写敏感比如PI和pi是两个不同的宏。对于“头文件找不到”报错分两种情况排查如果是系统头文件比如#include stdio.h大概率是编译器环境未配置好比如MinGW未安装或未配置环境变量重新安装配置编译器即可如果是自定义头文件比如#include “myfunc.h”检查头文件的路径是否正确相对路径要和源代码文件的位置对应绝对路径要写完整同时确认头文件是否存在文件名是否拼写正确大小写敏感。3.2 编译错误语法、语义相关报错编译错误是最常见的一类错误发生在编译阶段报错信息会明确指出错误行号和错误类型只要定位到对应代码就能快速解决。常见的报错信息有“syntax error before ‘;’”语法错误、“undeclared identifier ‘a’”变量未声明、“type mismatch in assignment”类型不匹配。“语法错误”主要是代码格式问题比如少写分号、括号不匹配、引号未闭合等。比如代码中写if(a 0) { printf(“a大于0”); 缺少右括号}编译时会报错只需补充对应的括号、分号或引号即可。这里要注意C/C语言中分号是语句结束的标志每个语句后面都必须加半角分号不能漏写。“变量未声明”报错是因为使用了未声明的变量C/C语言要求变量必须先声明后使用。比如直接写a 10; 没有提前声明int a;就会报错只需在使用变量之前加上对应的变量声明即可。另外还要注意变量名的拼写比如把int count写成int cout也会报未声明错误。“类型不匹配”报错是因为赋值语句左右两边的变量类型不一致。比如int a 3.14; int是整数类型3.14是浮点类型类型不匹配编译会报错只需将变量类型改为float或double或者将赋值内容转为整数类型比如int a (int)3.14;即可。3.3 链接错误符号引用、库依赖相关报错链接错误发生在编译的最后一步虽然不如编译错误常见但排查起来相对麻烦常见的报错信息有“undefined reference to ‘func’”函数未定义和“cannot find -lxxx”找不到库文件。“函数未定义”报错核心原因是链接器找不到函数的实现代码。首先检查函数是否有实现比如调用了func()但没有写func()的函数体就会报错其次检查实现文件是否被加入编译列表比如func.c没有被编译导致没有生成func.o目标文件链接器无法找到函数实现只需将func.c加入编译列表重新编译即可另外还要检查函数名的拼写大小写敏感比如把func()写成Func()也会导致链接错误。“cannot find -lxxx”报错是因为链接时找不到指定的库文件。首先检查库文件是否安装比如使用-lmmath库但系统中没有安装math库就会报错只需安装对应的库即可其次检查链接时是否指定了库路径比如第三方库安装在自定义路径下需要用-L参数指定库路径比如-L/usr/local/lib让链接器能找到库文件另外还要检查库名是否正确比如把-lmysqlclient写成-lmysql也会找不到库文件。第四章 实用编译技巧提升效率避坑指南掌握编译的基础流程和报错排查方法后再学习一些实用的编译技巧能大幅提升开发效率尤其是在大型项目中避免不必要的时间浪费同时减少踩坑概率。4.1 编译选项选择适配调试与发布场景我们在编译代码时会用到各种编译选项不同的选项对应不同的优化级别和功能核心是根据场景选择合适的选项平衡编译速度、运行性能和调试体验这里以主流的GCC编译器为例讲解最常用的几个编译选项-O00级优化显式关闭所有优化编译速度最快生成的代码与源代码结构高度一致变量优先存储在内存中函数不内联、循环不展开适合开发调试场景。调试时使用这个选项断点能精准定位到每一行代码变量值能随时查看不会出现“变量被优化掉”“断点跳行”的问题这也是编译器的默认优化级别主流编译器GCC、Clang、MSVC均如此。-O11级优化开启基础优化会对代码进行简单的优化比如常量折叠、死代码消除编译速度稍慢生成的代码体积更小、运行速度更快适合轻度发布场景比如测试环境发布。-O22级优化开启中等程度优化在-O1的基础上增加了指令重排、循环展开、函数内联等优化编译速度较慢但生成的代码运行效率大幅提升是生产环境最常用的优化级别适合正式发布场景。-O33级优化开启最高级别优化在-O2的基础上增加了更复杂的优化比如全局优化、循环优化编译速度最慢生成的代码运行效率最高但可能会导致调试困难且部分代码可能因过度优化出现异常适合对运行性能要求极高的场景如底层算法、高性能计算。-Og专门为调试设计的优化级别在-O1的基础上保留调试友好性既进行了基础优化又能保证调试时的体验适合需要兼顾调试和运行效率的场景。这里有一个避坑点不要在调试时使用-O2、-O3优化级别否则会出现变量被优化掉、断点跳行、调用栈混乱等问题导致无法正常调试正式发布时不要使用-O0级别否则代码运行速度慢、体积大影响程序性能。4.2 大型项目编译提升编译速度的技巧对于大型项目源代码文件众多每次全量编译都会花费大量时间尤其是修改少量代码后全量编译非常浪费时间这里分享两个实用技巧大幅提升编译速度第一个技巧是“增量编译”只编译修改过的文件而不是全量编译。比如我们修改了main.c文件只需重新编译main.c生成main.o然后重新链接所有目标文件即可无需编译其他未修改的文件。大部分IDE如VS Code、Qt Creator都支持自动增量编译也可以通过Makefile手动配置增量编译规则。第二个技巧是“多线程编译”利用CPU的多核心优势同时编译多个文件缩短编译时间。GCC编译器中使用-j参数指定编译线程数比如-j4表示使用4个线程编译线程数建议设置为CPU核心数比如8核CPU设置-j8能大幅提升编译速度。需要注意的是线程数不宜过多否则会占用过多系统资源导致系统卡顿。4.3 编译环境配置避免环境相关报错很多新手都会遇到“本地编译正常换一台电脑就报错”的问题核心原因是编译环境配置不一致这里整理了3个环境配置的关键要点避免环境相关的编译报错首先统一编译器版本不同版本的编译器语法支持、编译选项可能存在差异比如GCC 7.0和GCC 11.0对部分C11、C17语法的支持不同可能导致在高版本编译器中编译正常在低版本编译器中报错建议团队开发时统一编译器版本。其次配置环境变量确保编译器、汇编器、链接器能被系统识别。比如安装MinGW后需要将MinGW的bin目录添加到系统环境变量PATH中否则在命令行中输入gcc命令会报错“不是内部或外部命令”。最后统一库文件版本和路径使用第三方库时确保所有开发环境中库文件的版本一致库路径配置正确避免因库版本不兼容、路径错误导致的链接错误。比如使用MySQL数据库的C语言接口需要确保所有环境中都安装了相同版本的MySQL客户端库并且链接时指定了正确的库路径和库名。第五章 编译实战案例巩固所学知识5.1 简单C语言程序编译全过程下面以一个简单的C语言程序为例完整演示从代码编写到生成可执行文件的全过程帮助大家巩固编译流程和技巧新手可以跟着操作一遍快速上手。第一步编写源代码创建一个名为main.c的文件代码如下实现两个整数相加输出结果#include stdio.h#define MAX 100 // 宏定义// 加法函数实现int add(int a, int b) {return a b;}int main() {int x 10, y 20;int result add(x, y);printf(“两个数的和为%d\n”, result);return 0;}第二步预处理使用GCC命令进行预处理生成.i文件gcc -E main.c -o main.i。执行该命令后会生成main.i文件打开后可以看到宏定义MAX被替换成100注释被删除stdio.h头文件的内容被插入到文件中。第三步编译将预处理后的main.i文件编译成汇编代码.s文件gcc -S main.i -o main.s。执行该命令后生成main.s文件里面是汇编语言代码包含mov、add、call等指令。第四步汇编将汇编代码.s文件汇编成目标文件.o文件gcc -c main.s -o main.o。执行该命令后生成main.o目标文件二进制格式无法直接运行。第五步链接将目标文件main.o链接成可执行文件gcc main.o -o main。执行该命令后生成main可执行文件Windows下为main.exe双击即可运行输出“两个数的和为30”。如果修改了add函数的实现只需重新编译main.c生成main.o然后重新链接即可无需重新预处理和汇编这就是增量编译的优势。5.2 常见报错排查实战下面模拟两个常见的编译报错场景演示排查过程帮助大家熟练掌握报错排查方法遇到类似问题能快速解决。场景一编译时报错“undeclared identifier ‘z’”。排查步骤首先查看报错行号假设报错行是int z x y; 检查代码中是否声明了变量z发现没有声明int z; 补充变量声明后重新编译即可解决。场景二链接时报错“undefined reference to ‘add’”。排查步骤首先检查add函数是否有实现发现代码中只声明了int add(int a, int b); 没有写函数体补充add函数的实现代码后重新编译链接即可解决如果有函数实现检查实现文件是否被加入编译列表确保编译时包含了所有相关文件。以上就是编译知识的全部内容从基础概念、核心流程到报错排查、实用技巧再到实战案例覆盖了开发中最常用的知识点新手可以跟着实战案例操作一遍快速掌握。你在编译过程中还遇到过哪些难以排查的报错或者有哪些实用的编译技巧欢迎在评论区留言交流一起避坑、提升开发效率。注文档部分内容可能由 AI 生成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2431854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…