☰
编译器-O优化全解析:从-O0到-Ofast的取舍与避坑
2026/10/9 3:08:04 网站建设 项目流程

如果你写过几年 C/C++,一定听过“开 -O2”这种说法。但说实话,大部分人对-O优化的理解停留在“开高一点跑得快”这个层面。我也是被坑了几次才真正意识到,-O后面那个字母和数字,其实是编译器和你的程序之间一场精心设计的“交易”——你用可读性、可调试性、甚至标准合规性,去换运行速度和资源占用。这个系列参数,是编译器所有优化行为的入口,也是每个写代码的人迟早要面对的考题。

这篇文章我把-O优化从头到尾拆一遍,包括-O0到-Ofast到底改了什么东西、编译器背地里做了哪些见不得人的小动作、不同场景下该选哪个等级,以及我实际踩过的一堆坑。不管你是刚开始学 GCC 的新手,还是在嵌入式项目里被优化搞得头皮发麻的老手,这篇应该都能给你一些参考。

1. -O 优化到底是什么:四个等级一张表说清楚

先把最基础的概念摆出来。-O是 gcc、clang 这些编译器提供的优化开关参数,后面跟不同的字母或数字,代表不同的优化强度。注意这个 O 是大写字母 O,不是数字 0,命令行里写-O0是“零优化”,写-O(后面什么都不跟)等同于-O1,这一点经常有人搞混。

1.1 从 -O0 到 -Ofast:每一档都在做什么

标准优化等级一共四档,加上两个常用变体,我直接列个表方便对比:

优化等级编译速度运行速度代码体积调试体验典型场景
-O0最快最慢最大最好日常开发调试
-O1较快有所提升略有减小尚可需要一点优化又怕出问题
-O2较慢明显提升明显减小一般常规发布版本首选
-O3最慢上限最高可能增大较差数值计算、性能敏感
-Os较慢接近 -O2最小一般嵌入式、存储受限
-Ofast最慢极限追求可能增大较差数学密集、明确接受风险

-O0就是不做任何优化。所有变量保持在内存里、所有函数调用保持原样、代码执行顺序严格按照你写的来,这是调试时最舒服的状态,因为断点、单步、变量监视都能如实反映源码逻辑。代价就是程序跑起来慢得让人怀疑人生,尤其循环多的代码,-O0和-O2差个十倍都不奇怪。

-O1做基础优化,主要是消除局部死代码、简化表达式、调整指令顺序这些不会改变程序语义的操作。编译速度快,优化效果也不算差,适合那种“我想要点优化但还没准备好面对意外”的阶段。

-O2是大多数项目的发布标配。它开启所有不涉及空间换时间的优化,包括函数内联、循环优化、全局寄存器分配等等。我实测过不少项目,从-O1升到-O2往往能带来 20% 到 50% 的性能提升,而且极少出幺蛾子,所以它是我默认的推荐等级。

-O3在-O2基础上加了更激进的循环展开、向量化、函数重排。性能上限确实更高,但代价是编译时间变长、代码体积可能膨胀、bug 也可能被优化放大。我曾经把一个图像处理函数从-O2切到-O3,单帧耗时又降了 15%,帧率测试很漂亮——然后客户现场跑了一个小时才暴露浮点精度问题,后面细分到原因就是-O3下的一项自动向量化改写了浮点累加顺序。所以-O3不是不能碰,但你得知道自己在干什么。

-Os的目标是“尽量减小代码体积,同时不牺牲太多性能”。编译器会优先选择更紧凑的指令序列,比如能用短跳转就不用长跳转。对嵌入式项目这种 flash 空间按 KB 算的环境,-Os往往比-O2更合适,代价是某些场景下会慢一点。

-Ofast是最特殊的一个,它等于-O3加上-ffast-math等一批突破标准限制的选项。最典型的影响是改变浮点运算行为——比如假设不会出现 NaN 和 Inf、允许重排浮点运算顺序。如果你做的是物理仿真、实时音频这类对性能极端敏感而且清楚数值边界的项目,-Ofast能压出性能来;但如果是普通业务代码,我劝你别碰,因为 IEEE 754 浮点语义被破坏之后,bug 极其隐蔽。

注意:-O后面只能跟数字或特定字母,写成-O4这种不存在的等级,GCC 会直接报错。另外不同编译器对相同-O等级的具体实现并不同,同样的代码在 GCC 和 MSVC 下开最高优化,生成的汇编差距可能相当大。

1.2 优化等级背后的设计逻辑

你可能会问,为什么编译器不直接一步到位,做最高等级的所有优化?答案很简单:优化不是免费午餐,每一档都是对“正确性、编译时间、运行性能、代码体积、可调试性”这五个维度的取舍。

拿-O3的循环展开来说,比如一个循环体只有三行代码、要跑一万次,编译器可以选择把循环体复制成一万份差不多的代码直接顺序执行,省去每次判断和跳转的开销。这在计算上是划算的,但生成的二进制体积会放大几十倍。如果这个函数被几百个地方调用,整个镜像膨胀就更明显。-Os存在的意义就是反过来思考:循环不展开了,空间是省了,每次迭代多花几个时钟周期的代价我认了。

还有个关键点:优化等级越高,编译器对源程序的“改写”越激进,很多源码层面的“朴素逻辑”到了机器码层面已经完全不同。这就是为什么调试疑难杂症时,第一步永远是退回-O0重新编译——在优化过的代码里设断点,单步执行顺序和源码对不上,变量值被优化到寄存器里监视窗口一片空白,这种体验能逼疯人。

2. 编译器在幕后都干了什么:优化原理拆解

很多人把优化理解为“编译器把代码改快一点”,但实际上编译器做的事情是一系列基于数据流分析和控制流分析的等价变换。我用大白话拆一下最常见的几类优化手段,你就明白为什么-O2和-O0的行为差距那么大了。

2.1 函数内联、循环优化和常量折叠

函数内联是应用最广也最好理解的手段。编译器看到一个小函数被频繁调用,会把函数体直接“粘贴”到调用处,省掉函数调用产生的压栈、跳转、返回值处理等开销。比如:

static inline int add(int a, int b) { return a + b; } int main() { int x = add(1, 2); return x; }

-O0编译会保留真正的函数调用,跑汇编你能看到call指令;-O2编译后整个main可能就剩几条指令,add函数体直接嵌入,最终甚至因为所有参数都是常量,连加法都不需要执行,直接返回常量结果。

这意味着什么?意味着你写代码时为了可读性拆出的小函数,在优化后并不会带来运行时开销。很多人不敢用函数、觉得调用慢,实际上-O2下内联完全解决了这个问题。但内联也有隐患——如果一个函数体特别大或者调用了很多次,全内联会导致代码膨胀,反而影响指令缓存命中,这就是编译器内部要权衡的地方。

循环优化是另一大块。最典型的两种是循环展开和循环不变式外提。循环展开我刚才提过;循环不变式外提,用生活化类比就是:你每次进超市都看一眼货架位置确定牛奶在哪,然后买完出来,但如果你知道牛奶常年放在第三排第二格,就不用每次进去都看一遍货架了。编译器会把循环里面那些每次计算结果都相同的表达式,挪到循环外面只算一次:

for (int i = 0; i < n; i++) { arr[i] = arr[i] * scale; // 如果 scale 在循环体内不变 }

优化的结果并不是真的把scale挪出去,而是用一个寄存器一直保存它的值,避免每次循环都从内存重新读。这种优化在-O1就会出现,效果非常显著。

常量折叠和常量传播更好理解。int a = 3; int b = a * 2;这种代码,编译器在编译期就算出b = 6,运行时连乘都不用乘。如果再把变量赋值链追踪下去,很多中间操作会被整个消除,这就是为什么你写的代码和最终运行的代码,有时候看起来完全是两个程序。

2.2 寄存器分配的力量

C 语言的局部变量,源码层面是“放在内存里”的。但 CPU 访问寄存器比访问内存快一个数量级以上。编译器优化的重要任务之一,就是决定哪些变量能一直待在寄存器里,哪些在某个时间段必须写回内存。

寄存器分配有复杂的算法,比如线性扫描、图着色之类。-O0不分配,所有局部变量默认在栈上,每次运算移来移去;-O2会精确分析每个变量的生存周期,把最热门的变量塞进寄存器。

这就是为什么优化后调试时经常监视不到变量值——变量的“家”被搬到了寄存器,而调试器读取内存那一套逻辑跟不上。你用print想看x是多少,结果调试器告诉你<optimized out>,不是程序坏了,是编译器认为这个变量根本没必要存在内存里。

另外还有一个容易忽略的点:编译器知道函数的调用约定,知道哪些寄存器是调用者保存、哪些是被调用者保存。优化时它可以把变量稳定放入某个不被破坏的寄存器,整个函数都不需要额外保存恢复操作。这个优化在循环里尤其重要,所以你会看到开优化后循环性能大幅提升。

2.3 死代码消除、尾递归和其他“看不见的手”

死代码消除是最基础的优化之一。如果一段代码的结果永远不会被使用,编译器会直接删掉。举个例子:

int foo(int x) { int y = x * 3; // 如果后面没用 y int z = x + 1; return z; }

开优化编译后,y的计算整个被删掉,因为计算了也没人看。这种优化看起来人畜无害,但它也可能误伤——如果你的“计算”本身有副作用,比如写了一个函数调用、访问了 volatile 变量,编译器会保留;但纯算术计算真的会被无情删除。

尾递归优化也很经典。如果一个函数的最后一步是调用自身,编译器可以把调用转换成跳转,不浪费新的栈帧。递归深度原本可能一万层就爆栈,优化后跑一亿层都没事。但有个前提——你得写出真正的尾递归形式,比如:

int factorial_tail(int n, int acc) { if (n <= 1) return acc; return factorial_tail(n - 1, acc * n); }

优化后这个函数执行时栈深度固定为 1。如果你写的是普通递归n * factorial(n - 1),编译器就没法做这种转换,优化等级再高也帮不了你。

还有指令调度、分支预测优化、公共子表达式消除、全局变量去重等一大票手段。我不打算一一细说,但记住一个结论:现代编译器的优化在微观层面是极其激进的,很多你想当然的源码行为,在机器码层面早就不存在了。

3. 怎么选优化等级:从开发调试到产品交付的完整决策流程

“用哪个优化等级”是一个工程师每天都在做的决策。很多人习惯一种设置打天下,这在大型项目里其实是隐患,我分享一下我自己验证过的一套流程。

3.1 分阶段策略:开发期、测试期、发布期各用各的

开发调试阶段,我用-O0加-g。-g是生成调试信息,和优化等级是两回事,可以组合使用,比如-O0 -g、-O2 -g都合法。调试阶段追求的是“源码和行为的严格一致”,不要优化干扰判断。这一步省不了,因为排查一个在-O2下偶现的 bug,代价远远高于测试阶段多跑几分钟。

测试阶段,我切换到-O2 -g,这是我最推荐的组合。-O2能暴露优化带来的大小问题,-g保留调试信息方便出问题时定位。在测试环境就跑到-O2,目的是让优化相关的问题在发布前就浮出水面。我见过太多项目直接在测试阶段用-O0,到了发布前夕切-O2,跑起来立刻崩,然后手忙脚乱开始排查——这是最典型的错误姿势。

发布阶段,看场景决定。通用处理器上的普通应用,无脑-O2不会错。CPU 密集型的科学计算、图像处理,可以单独用-O3编译那几个核心编译单元,其他文件还是-O2。嵌入式 flash 紧张的话,全文-Os。这里分享一个技巧:GCC 支持按文件粒度覆盖优化等级,你可以在 Makefile 里给不同文件分配不同优化参数,不必一刀切:

# 核心计算模块激进优化 image_proc.o: image_proc.c $(CC) $(CFLAGS) -O3 -c $< -o $@ # 稳定性优先的模块 network_stack.o: network_stack.c $(CC) $(CFLAGS) -O2 -c $< -o $@

这一招我用了很久,特别适合“大部分代码可以-O2,但某一个热点函数值得用-O3压榨性能”的混合场景。

3.2 怎么验证优化到底有没有效果

别凭感觉说“好像快了”,我实测经验里至少一半的“优化”都是心理作用。基础做法是编译两遍,一遍-O0一遍-O2,跑同一组测试数据,对比时间。命令类似:

gcc -O0 -o app_slow app.c gcc -O2 -o app_fast app.c time ./app_slow time ./app_fast

time 输出里的 real 时间就是总耗时,多跑几轮取平均值更靠谱。如果性能提升不明显,可能瓶颈不在 CPU 而在 I/O 或者锁竞争,这时候优化等级帮不了你。

另外一个深入的做法是看汇编。用objdump -d查看关键函数的汇编码,或者直接让编译器输出带源码行的汇编:

gcc -O2 -S -fverbose-asm app.c

-S让编译器输出汇编文件而不是直接编译成目标文件,加上-fverbose-asm会在汇编注释里标注对应的源码变量名。这样你能看到-O0版里一大堆mov、push指令在你写的代码下面,而-O2版的汇编可能只有寥寥几条指令。对比着看,你会对编译器做的事有非常直观的认识。

还有一个现代工具值得用:perf stat(Linux)或者gprof。虽然不在编译器范围内,但优化是一个系统工作——先用分析器找到热点函数,再决定给哪个文件开高优化等级,效果会好很多。盲猜热点然后全局-O3,很可能把编译时间浪费在不重要的函数上。

4. 你身边绕不开的编译器:GCC、Clang、MSVC 那些事

热搜词里有很多人搜“编译器和编辑器的区别”、“gcc编译器下载安装”,这说明不少新手刚入门时对编译工具链的认知是模糊的。我先把基础讲清楚,再展开不同编译器对-O优化的实践差异。

4.1 编辑器是记事本,编译器是翻译官

很多初学者混淆编辑器和编译器,其实这两个东西完全不同。编辑器(VS Code、Vim、Notepad++)就是给你写代码用的,产出的是纯文本源文件;编译器(GCC、Clang、MSVC)负责把源文件翻译成机器能执行的指令。你可以在记事本里写 C 代码,写完用命令行调用 GCC 编译,完全没有问题——编辑器写代码、编译器翻译代码,它们之间没有绑定关系。

编译器的工作过程大致是:预处理(处理#include、#define)→ 语法分析(看代码是否符合语法)→ 语义分析(检查类型是否匹配)→ 生成中间表示 → 优化 → 生成汇编 → 汇编 → 链接。-O优化主要发生在中间表示层和汇编生成层。理解了这条流水线,你就明白为什么编译器需要专门做优化这一步,而不是“写完代码直接出结果”。

4.2 三大主流工具链怎么选

GCC 是 Linux 和嵌入式领域的事实标准,老牌、稳定、支持架构极广。你现在用的几乎所有 Linux 系统软件,底层都是 GCC 编译出来的。它的-O优化体系也是最经典的,本文讲的参数在 GCC 里全部有效。安装很简单:

# Debian/Ubuntu sudo apt install build-essential # CentOS/RHEL sudo yum install gcc gcc-c++ # Windows 用 MSYS2 或 MinGW-w64

Clang 是 LLVM 项目的前端,和 GCC 高度兼容,-O0到-Ofast这套参数基本通用。它的优势在于更快的编译速度和更友好的错误提示,而且基于 LLVM 架构可以跨平台做更灵活的优化。有些大厂(比如 Apple 生态里的 Xcode 默认工具链)就在用 Clang 而不是 GCC。如果你在 Linux 开发,Clang 和 GCC 经常一起装,对比同一段代码在两个编译器下的优化产出,也是很有意思的事。

MSVC 是微软在 Windows 上的编译器,它的优化参数不叫-O,而是/O1、/O2、/Od(注意大写的 O 后面是 d,代表 disable,就是关闭优化)等,通过 Visual Studio 的项目属性页设置。MSVC 对新硬件的向量化支持也很积极,但它的优化行为细节和 GCC/Clang 有差异。这解释了为什么同样的代码在 Linux 下好端端的,移植到 Windows 用 MSVC 编译就出问题——很大可能是优化行为不同。

还有两个特殊工具链值得一提。Intel 编译器(icx/icc)在 Intel CPU 上往往能压榨出不可思议的性能,因为它对自家硬件最了解,能生成更高级的指令组合,特别是数学库的优化非常激进。Fortran 编译器则是科学计算领域的老古董还在持续更新,现代 Fortran 编译器(如 gfortran、ifort)的-O参数和 C 编译器一脉相承,数值代码开启高等级优化后性能差距非常可观。

Windows 用户配置编译环境我推荐 MSYS2,它在 Windows 上提供了一个接近 Linux 的开发环境,能装 GCC、Clang、Make、CMake 等一整套工具。安装后配置好环境变量,在终端里就能直接敲 GCC 命令,-O2这些参数完全通用,学习成本和 Linux 几乎一样。

4.3 优化等级在不同编译器里的细微差异

同样的-O2,GCC 和 Clang 的实现细节并不同。比如自动向量化这一项,Clang 在-O2下就会尝试把循环转换成 SIMD 指令,而 GCC 要到-O3才会做得比较全面。一个现实例子:同样的图像像素亮度调整代码,Clang-O2可能自动生成 AVX2 指令,GCC-O2还是普通的逐像素循环,两者性能差出三倍都可能。

这意味着什么?意味着如果你追求极致性能,优化不是“开个等级就完事”,还要结合工具链的具体行为做针对性调整。一个常见的做法是给编译器提供 CPU 架构信息,尽量使用对目标 CPU 适配的文件生成选项。比如现代 x86_64 机器用-march=native,编译器就能针对本地 CPU 支持的指令集做最大限度的代码生成。这几项配合-O3用,性能往往比默认-O2高出不少,但要注意换了机器跑可能因为指令集不兼容而报非法指令——这些都是发布时需要考虑的取舍。

5. 优化坑实录:调试、嵌入式、链接时常踩的五个问题

最后这部分是重点中的重点。我做过的项目里,因为优化引发的问题五花八门,而且每一种都让当时的我焦头烂额。这里筛选五个典型场景,附上排查思路和解决方案,希望能帮你少走弯路。

5.1 编译器提示“未包含 main 类型”:入口函数都没找到,别急着去碰优化

有些人编译时报错说程序缺少main类型或main函数未定义,第一反应是去调优化参数,这其实搞错了方向。main是程序的入口点,链接器必须有它才能生成可执行文件。这个错误通常跟优化无关,更多是这三种情况:源文件没写完就编译了、文件名对了但入口函数名字写错了(比如写成mian)、或者你编译的是静态库/目标文件而不是可执行文件。

排查方法很简单,nm命令查一下目标文件里的符号表:

gcc -c -O2 app.c -o app.o nm app.o | grep ' main'

看有没有main定义。如果是嵌入式裸机开发,根本没有main,而是Reset_Handler、startup之类的启动入口,那就不要用生成可执行文件的默认链接方式,需要指定链接脚本和入口点。这个和-O优化真的没多大关系,别在优化参数上浪费时间。

5.2 嵌入式中断函数被优化掉的经典问题

有个热搜词提到“ch32v 在 gcc 编译器下定义中断函数”,这个问题在嵌入式开发里非常经典,而且和优化直接相关。CH32V 是 RISC-V 内核的国产 MCU,用 GCC 交叉编译器开发时,很多人会发现:明明写好了中断服务函数,编译开了优化之后,中断就是不触发,或者有时触发了但变量像没更新一样。

原因出在 GCC 对函数的处理上。中断函数不是普通函数,不能像普通函数那样随意内联和重排。为了告诉编译器“这个函数是中断处理程序”,需要给函数添加中断属性。RISC-V 的 GCC 写法类似:

__attribute__((interrupt)) void TIM1_IRQHandler(void) { // 中断处理 }

如果你不加这个属性,编译器可能按普通函数处理,优化时改变调用约定,导致中断返回时寄存器状态错乱,程序就诡异了。另一个相关坑是中断里和主循环共享的变量,一定要加volatile修饰,否则编译器可能把变量优化成只存寄存器,中断里改了值,主循环里读到的还是旧缓存。我见过一个项目,标志位变量忘了加volatile,-O2下主循环死循环出不来,退到-O0就正常——这就是典型的优化引发的“幽灵 bug”,排查了一个下午,发现就少了一个关键字。

英飞凌 TC264 这类芯片也用类似范式。TC264 有自己的 TriCore 编译器(比如 Tasking 或者 HighTec 版的 GCC),中断函数同样有专门的关键字或函数属性定义,比如IFX_INTERRUPT宏。只要是嵌入式,进入中断函数前第一件事永远是查编译器文档里中断函数的正确声明方式,不要靠猜。

5.3 编译器的堆空间不足:优化有时候反而让内存更紧张

报“堆空间不足”或 “region 'FLASH' overflowed” 这类链接错误时,很多人潜意识里觉得开高优化能省内存。这个想法对了一半:-Os确实能减小代码体积,但-O3因为激进内联和循环展开,会让代码体积反向膨胀,flash 不够用的情况反而更严重。

我建议的做法是,先把优化等级整体降到-Os,如果还不够,再用-flto(链接时优化)配合裁剪,让编译器在链接阶段跨编译单元做代码消除。LTO 有时候能干掉很多平时发现不了的重复代码,对体积帮助很明显:

gcc -Os -flto -o firmware.elf main.c app.c driver.c

另外堆空间不足也可能是栈空间配置的问题,和优化等级无关。嵌入式链接脚本里通常有_estack、_min_heap_size这类符号,你需要手动调整栈和堆的大小。这种情况先确认是 flash 溢出了还是 RAM 溢出了,错误信息里一般写得很清楚。

5.4 优化后调试信息全乱:<optimized out>和跳来跳去的断点

这种情况我猜所有做过-O2调试的人都有体会。设置好断点,运行后断点位置和源码对不上;watch 窗口里变量显示<optimized out>;单步执行直接从第一行跳到第十行。不是说你的程序出了问题,是编译器在优化时改变了指令和源码的对应关系。

调试这种场景,我不建议硬顶。如果你需要在调试器里精确跟踪变量变化,用-O0 -g重新编译一份带完整调试信息的程序,先把逻辑问题定位清楚。如果必须在-O2下调试(比如 bug 只在优化后出现),可以试试给关键函数单独加__attribute__((optimize("O0"))),让它不优化:

__attribute__((optimize("O0"))) int debug_me(int x) { // 这段代码不会被优化 }

这是 GCC 提供的函数级优化覆盖,Clang 也有类似的方式但语法略有不同。我实际用下来,这比全局降优化等级精准得多,尤其适合“整个工程要-O2,但只有某个热点函数需要保真调试”的情况。

还有一种情况是开启优化后变量查不到,但你想确认某个值到底有没有被算对。我有个土办法:往 stderr 里打印一下,fprintf(stderr, "debug x=%d\n", x);因为 I/O 是有副作用的,编译器通常不会把这段代码优化掉。虽然打印本身会影响性能,但定位问题的时候管不了那么多。

5.5 未定义行为被优化放大的事故:越界、溢出、别名

这是最高级也最坑的一类。程序写出来就有未定义行为(UB)——比如数组越界读、有符号整数溢出、一个指针指向的内存类型不匹配等。这些代码在-O0下可能“碰巧”正常,但优化之后编译器会假设代码里没有 UB,然后基于这个假设做激进的变换,最终产生完全超乎预期的后果。

举一个真实案例。我排查过一个有符号整数溢出问题:int sum = a + b;其中a、b很大,和超过了INT_MAX。-O0下 sum 变成负数,程序继续跑,行为虽然不对但结果“可预测”;-O2下编译器假设没有溢出,做出sum < 0的检查结论恒为假,直接把后面一段错误处理逻辑剪掉了,导致程序直接走错分支,表现出诡异的行为。从今天的视角回看,根子不是优化,是代码本来就错了,但优化把错误放大成不可理解的事故。

排查这种问题的思路是:出问题时先用-O0复现,如果-O0不出现、-O2才出现,除了疑心编译器 bug,更要怀疑代码里有 UB。用工具辅助定位是最快的路径——-fsanitize=undefined加在编译参数里,运行时会自动检测未定义行为并报告位置:

gcc -O1 -g -fsanitize=undefined -o app app.c ./app

这个工具能在 UBSan 的提示下直接指出哪一行代码存在越界、溢出、对齐问题。开启 AddressSanitizer(-fsanitize=address)还能检测内存越界和泄漏。我的经验是,优化引发的诡异问题里至少一半最后都指向 UB,代码本身带病才是根源。

写在最后的一点个人经验

做编译器优化这个方向,我最大的感受是:-O既不是越高越好,也不是越低越稳。它是一套需要你理解原理、尊重取舍的参数体系。我自己的习惯是,新建项目第一时间就在 CMake 里区分 Debug(-O0 -g)和 Release(-O2)两种配置,而且从项目第一天起就用-O2做集成测试,绝不拖到发布前才突然切优化。另外,遇到任何“开优化才出现、关优化就消失”的怪问题,第一反应不是“编译器 bug”,而是用 UBSan 和 ASan 扫一遍代码里的未定义行为——十次里有八九次都是开发者自己的代码在高等级优化下被显了形。希望这篇能把-O优化的原理和套路讲透,让你下次在-O0和-O3之间做选择时,心里更有底。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询