1. 从一次编译报错说起:为什么-march参数值得单独写一篇踩坑记录
如果你正在做 ARM 平台的交叉编译,尤其是给带 NPU 或者 DSP 的嵌入式板子编译推理框架、图像处理库,那你大概率在某个时刻写过类似-march=armv8.2-a+dotprod+fp16这样的编译选项。我第一次看到这个参数的时候,心里想的是"不就是告诉编译器用哪个指令集嘛,能有多大事"。结果那天下午,我盯着满屏的undefined reference to和Illegal instruction愣是排查了三个多小时,最后发现问题就出在这短短一行参数上。
这篇文章要聊的核心,就是-march=armv8.2-a+dotprod+fp16这个组合到底在做什么、写错了会引发哪些连锁反应、以及怎么一步步定位和修复。它不是一个泛泛而谈的"ARM 交叉编译入门",而是聚焦在一个非常具体、非常容易踩坑的编译参数上。适合的读者是:已经能跑通基本的 ARM 交叉编译流程,但对-march、-mcpu、-mtune这些参数的区别还比较模糊,或者正在给 ARMv8.2 及以上架构的设备编译带 SIMD 加速的代码的开发者。
先说结论,方便你判断要不要继续往下看:-march=armv8.2-a+dotprod+fp16这行参数里,armv8.2-a是基础架构版本,+dotprod和+fp16是架构扩展特性。写错的方式有很多种——拼写错误、架构版本和扩展不匹配、编译器版本不支持、目标 CPU 实际不支持这些扩展——每一种都会导致不同阶段的失败。有的失败发生在编译期,报错还算友好;有的失败发生在链接期,报错信息完全指不到问题根源;最坑的是编译链接都过了,跑到目标板上才崩,报一个Illegal instruction,让你怀疑人生。
我踩过的坑包括但不限于:把dotprod拼成dotproduct、在只支持 armv8-a 的编译器上写 armv8.2-a、给一个实际是 Cortex-A53 的板子编译了带 dotprod 的代码、以及最经典的——宿主机编译器和目标板 GCC 版本不一致导致扩展特性支持列表不同。下面我会把这些坑一个个拆开讲,包括背后的原理、排查方法、以及最终怎么修。
2. 拆解-march=armv8.2-a+dotprod+fp16:每个字段到底在说什么
2.1armv8.2-a不是随便写的版本号
ARM 的架构版本命名有一套自己的逻辑。armv8-a是 ARMv8-A 架构的基础版本,引入了 64 位执行状态(AArch64)和 AArch32 的兼容模式。armv8.1-a增加了原子指令(LSE,Large System Extensions)等特性。armv8.2-a在此基础上又增加了半精度浮点(FP16)支持、点积指令(Dot Product)、以及一些内存模型相关的增强。
这里有个关键点:架构版本是向下兼容的,但不是向上兼容的。你用-march=armv8.2-a编译出来的代码,在只支持 armv8-a 的 CPU 上跑,如果代码里用到了 v8.2 才有的指令,就会触发Illegal instruction。反过来,用-march=armv8-a编译的代码在 v8.2 的 CPU 上跑没问题,只是用不到新指令带来的性能提升。
所以选择armv8.2-a而不是armv8-a,本质上是在做一个权衡:你要用 v8.2 的新指令来加速特定运算(比如 dotprod 用于量化推理中的矩阵乘),但代价是代码只能在支持 v8.2 的 CPU 上运行。如果你的目标设备是树莓派 4(Cortex-A72,armv8-a)或者某款老旧的 Cortex-A53,那写armv8.2-a就是在给自己埋雷。
2.2+dotprod:量化推理的加速利器
dotprod是 ARMv8.2-A 引入的点积指令扩展。它的核心是一条SDOT(有符号点积)和UDOT(无符号点积)指令,可以在一条指令里完成 4 组 8 位整数的乘加运算。这在量化神经网络推理里非常有用——卷积和全连接层的核心计算就是大量的 8 位整数乘加。
为什么这个扩展值得单独拿出来说?因为很多推理框架(比如 TFLite、ONNX Runtime、NCNN)在编译时会检测__ARM_FEATURE_DOTPROD这个宏,如果定义了,就会启用针对 dotprod 优化的内核。如果你在编译参数里写了+dotprod,但目标 CPU 不支持,那编译出来的二进制在运行时就会崩。更隐蔽的情况是:你编译的库启用了 dotprod 优化,但链接的另一个库没有,两者混在一起用,可能在某个特定算子调用时才触发崩溃,排查起来非常痛苦。
2.3+fp16:半精度浮点的双刃剑
fp16扩展提供了半精度浮点(16 位)的存储和运算支持。在 ARMv8.2-A 之前,FP16 只能用于存储和转换,不能直接做算术运算。v8.2 增加了 FP16 的算术指令,比如FADD、FMUL等对半精度的直接支持。
这个扩展在移动端 GPU 和 NPU 推理中很常见,因为半精度可以显著减少内存带宽和计算量。但问题在于:不是所有 ARMv8.2 的 CPU 都实现了 FP16 算术扩展。ARMv8.2-A 把 FP16 算术列为可选特性,有些 CPU 只支持 FP16 存储和转换,不支持算术。如果你写了+fp16但 CPU 不支持,同样会在运行时崩。
这里有个容易混淆的点:+fp16和+fp16fml是两个不同的扩展。fp16fml是 FP16 的融合乘加扩展,属于 armv8.4-a 的特性。如果你在 armv8.2-a 上写+fp16fml,编译器会直接报错说不支持。
2.4 参数组合的合法性与编译器支持矩阵
不同版本的 GCC 和 Clang 对-march扩展的支持是不一样的。比如 GCC 8 可能不支持+dotprod,GCC 9 才开始支持。Clang 的支持情况又不一样。如果你用的交叉编译工具链比较老,写了一个它不认识的扩展,编译器会报unknown architecture feature或者直接忽略。
下面这个表格是我实测整理的常见编译器版本对这几个扩展的支持情况:
| 编译器版本 | armv8.2-a | +dotprod | +fp16 | +fp16fml |
|---|---|---|---|---|
| GCC 7.x | 支持 | 不支持 | 支持 | 不支持 |
| GCC 8.x | 支持 | 不支持 | 支持 | 不支持 |
| GCC 9.x | 支持 | 支持 | 支持 | 不支持 |
| GCC 10.x+ | 支持 | 支持 | 支持 | 支持 |
| Clang 10.x | 支持 | 支持 | 支持 | 部分支持 |
| Clang 12.x+ | 支持 | 支持 | 支持 | 支持 |
注意:这个表格是基于我手头几个工具链的实测结果,不同发行版打包的编译器可能有差异。最可靠的方法是直接查你所用编译器的文档,或者用
gcc -march=armv8.2-a+dotprod+fp16 -E -x c /dev/null这样的命令测试是否被接受。
3. 写错-march的四种典型翻车现场与排查实录
3.1 拼写错误:最low但最常见的坑
我见过最多的错误就是把dotprod拼成dotproduct、dot-product、dotProd。GCC 对扩展名的拼写是大小写敏感的,而且不接受连字符。你写-march=armv8.2-a+dotproduct,GCC 会报:
error: unknown architecture feature 'dotproduct' for '-march=armv8.2-a'这个报错还算友好,至少告诉你哪个 feature 不认识。但如果你用的是某个 IDE 或者构建系统,错误信息可能被吞掉或者显示成别的样子。我就遇到过一次,CMake 把编译器的 stderr 重定向了,只显示"编译失败",我花了半小时才找到真正的报错。
排查方法很简单:把-march参数单独拿出来,用编译器直接编译一个空文件,看报错信息。命令如下:
aarch64-linux-gnu-gcc -march=armv8.2-a+dotprod+fp16 -E -x c /dev/null -o /dev/null如果这个命令没报错,说明参数本身是合法的。如果报错,根据错误信息调整拼写。
3.2 架构版本与扩展不匹配:+dotprod写在 armv8-a 上
dotprod和fp16算术都是 ARMv8.2-A 才引入的。如果你写-march=armv8-a+dotprod,GCC 会报:
error: 'dotprod' is not a valid feature for 'armv8-a'但有些编译器版本可能不会报错,而是默默忽略这个扩展。这就更危险了——你以为启用了 dotprod 优化,实际上没有,性能达不到预期,但代码能跑。这种"静默失败"是最难排查的,因为没有任何报错信息。
我的建议是:永远不要假设编译器会帮你检查参数合法性。每次修改-march参数后,用-dM -E导出预定义宏,确认你想要的特性宏(比如__ARM_FEATURE_DOTPROD)确实被定义了。命令如下:
aarch64-linux-gnu-gcc -march=armv8.2-a+dotprod+fp16 -dM -E -x c /dev/null | grep -i "dotprod\|fp16"如果输出里有#define __ARM_FEATURE_DOTPROD 1和#define __ARM_FEATURE_FP16_VECTOR_ARITHMETIC 1,说明扩展生效了。如果没有,说明编译器忽略了你的参数,需要检查编译器版本或参数写法。
3.3 目标 CPU 不支持:编译通过但运行崩溃
这是最坑的一种情况。你的编译器支持这些扩展,参数写法也正确,编译链接都过了,但把二进制放到目标板上跑,直接Illegal instruction。原因很简单:目标板的 CPU 不支持你启用的扩展。
比如你给一块 Cortex-A53 的板子编译了带+dotprod的代码。Cortex-A53 是 ARMv8-A 架构,不支持 dotprod。代码里如果有SDOT指令,跑到那里就会崩。更隐蔽的是,如果这个指令在某个冷门分支里,可能跑很久才崩,让你以为是别的问题。
排查这种问题,首先要知道目标 CPU 到底支持哪些特性。在 Linux 系统上,可以查看/proc/cpuinfo:
cat /proc/cpuinfo | grep -i "features"输出里会列出 CPU 支持的特性,比如asimd、fp、asimdrdm、dotprod、fphp等。如果dotprod不在列表里,那你的 CPU 就不支持这个扩展。
另一个方法是看 CPU 型号。常见的 ARMv8 CPU 和它们的特性支持如下:
| CPU 型号 | 架构 | dotprod | fp16 算术 |
|---|---|---|---|
| Cortex-A53 | armv8-a | 否 | 否 |
| Cortex-A55 | armv8.2-a | 是 | 是 |
| Cortex-A72 | armv8-a | 否 | 否 |
| Cortex-A76 | armv8.2-a | 是 | 是 |
| Cortex-A77 | armv8.2-a | 是 | 是 |
| Neoverse N1 | armv8.2-a | 是 | 是 |
| Apple M1 | armv8.4-a | 是 | 是 |
注意:Cortex-A55 虽然支持 dotprod 和 fp16,但不同厂商的 SoC 可能做了裁剪。最可靠的方法还是在目标板上跑
cat /proc/cpuinfo确认。
3.4 工具链不一致:宿主机和目标板 GCC 版本打架
这种情况在嵌入式开发里很常见:你的交叉编译工具链是 GCC 9,但目标板上的运行时库是 GCC 7 编译的。GCC 9 支持+dotprod,GCC 7 不支持。你编译出来的代码用了 dotprod 指令,但链接的某个库是用 GCC 7 编译的,没有针对 dotprod 优化。两者混在一起,可能在某个特定调用路径上触发问题。
更常见的是:你用的交叉编译工具链和目标板上的系统库版本不一致,导致__ARM_FEATURE_DOTPROD宏在一个编译单元里定义了,在另一个里没定义,结构体布局或者函数签名出现差异,引发难以定位的 bug。
解决方法是:尽量保证交叉编译工具链的版本和目标板系统库的版本一致。如果做不到,至少在编译时显式指定-march和-mtune,不要依赖编译器的默认值。另外,可以用-static静态链接,避免运行时库版本不一致的问题。
4. 正确配置-march的完整实操流程
4.1 第一步:确认目标 CPU 的实际能力
在写任何编译参数之前,先确认目标板到底支持什么。如果你有目标板的 shell 访问权限,直接跑:
cat /proc/cpuinfo重点看Features那一行。常见的特性标识包括:
asimd:Advanced SIMD(NEON)fp:浮点支持asimdrdm:SIMD 舍入双乘加(ARMv8.1)dotprod:点积指令(ARMv8.2)fphp:FP16 算术(ARMv8.2)sve:可伸缩向量扩展(ARMv8.2 可选,ARMv9 标配)
如果dotprod和fphp都在,那你写-march=armv8.2-a+dotprod+fp16是安全的。如果不在,就要降级到-march=armv8-a或者-march=armv8.2-a(不带扩展)。
如果你没有目标板的 shell 权限,那就查 CPU 型号的官方文档。比如 Cortex-A55 的 Technical Reference Manual 里会明确列出支持的扩展。
4.2 第二步:验证交叉编译工具链的支持情况
确认目标 CPU 支持后,再验证你的交叉编译工具链是否支持这些扩展。用前面提到的命令:
aarch64-linux-gnu-gcc -march=armv8.2-a+dotprod+fp16 -dM -E -x c /dev/null | grep -i "dotprod\|fp16"如果输出里没有__ARM_FEATURE_DOTPROD,说明你的工具链版本太老,需要升级。如果升级不了,那就只能放弃 dotprod 优化,用-march=armv8.2-a+fp16或者-march=armv8-a。
这里有个小技巧:你可以用-mcpu代替-march。-mcpu=cortex-a55会自动启用该 CPU 支持的所有扩展,包括 dotprod 和 fp16。这样你就不用手动列扩展了,减少拼写错误的概率。但缺点是-mcpu会同时设置-mtune,可能影响指令调度。如果你只想控制指令集,不想影响调度,还是用-march更精确。
4.3 第三步:在构建系统中正确传递参数
如果你用 CMake,不要把-march直接写在CMAKE_C_FLAGS里,因为这样会覆盖掉 CMake 自动检测的一些标志。正确的做法是用add_compile_options或者target_compile_options:
add_compile_options(-march=armv8.2-a+dotprod+fp16)如果你用 Makefile,确保-march出现在CFLAGS和CXXFLAGS里,并且不要被后续的-march覆盖。Makefile 里后面的-march会覆盖前面的,所以如果你在CFLAGS里写了一个,在某个target的编译命令里又写了一个,最终生效的是后面的那个。
如果你用 Bazel,在BUILD文件里用copts传递:
cc_library( name = "my_lib", srcs = ["my_lib.cc"], copts = ["-march=armv8.2-a+dotprod+fp16"], )注意:Bazel 的
copts只影响当前 target,不会传递给依赖。如果你的库被其他 target 依赖,那些 target 也需要单独设置copts,否则可能出现 ABI 不一致的问题。
4.4 第四步:编译后验证二进制是否真的用了这些指令
编译完成后,不要假设参数生效了。用objdump反汇编,检查二进制里是否真的出现了SDOT或UDOT指令:
aarch64-linux-gnu-objdump -d my_binary | grep -i "sdot\|udot"如果有输出,说明 dotprod 指令确实被用上了。如果没有,可能是你的代码里没有触发 dotprod 优化的路径,或者编译器没有识别到可以优化的模式。这时候可以检查一下代码里是否有适合 dotprod 的循环结构,比如 8 位整数的乘加。
对于 fp16,可以检查是否有fadd、fmul等对半精度的操作:
aarch64-linux-gnu-objdump -d my_binary | grep -i "fadd.*h\|fmul.*h"ARM 的汇编里,半精度浮点寄存器通常用h后缀标识,比如fadd h0, h1, h2。
5. 常见问题速查与避坑心得
5.1 编译期报错速查表
| 报错信息 | 可能原因 | 解决方法 |
|---|---|---|
unknown architecture feature 'xxx' | 扩展名拼写错误或编译器不支持 | 检查拼写,升级编译器 |
'dotprod' is not a valid feature for 'armv8-a' | 架构版本与扩展不匹配 | 改用 armv8.2-a 或更高 |
unrecognized command-line option '-march=...' | 编译器太老,不支持 -march | 升级工具链 |
error: '-march=armv8.2-a+fp16fml' | fp16fml 需要 armv8.4-a | 改用 armv8.4-a 或去掉 fp16fml |
5.2 运行期崩溃排查思路
如果编译链接都过了,但目标板上跑崩了,按以下顺序排查:
- 确认崩溃是否稳定复现。如果只在特定输入下崩,可能是某个冷门分支用了不支持的指令。
- 用
dmesg查看内核日志,看是否有Illegal instruction相关的记录。 - 用
gdb附加到进程,看崩溃时的 PC 指针指向哪条指令。如果是SDOT或UDOT,那就是 dotprod 的问题。 - 检查
/proc/cpuinfo确认 CPU 是否真的支持这些扩展。 - 如果 CPU 不支持,重新编译,去掉对应的扩展。
5.3 我的避坑心得
心得一:不要盲目追求最新架构。我见过很多项目为了"性能优化",直接上-march=armv8.2-a+dotprod+fp16,结果目标板是 Cortex-A53,跑起来就崩。性能优化要建立在兼容性的基础上,先保证能跑,再考虑跑得快。
心得二:用-mcpu代替-march可以减少拼写错误。如果你知道目标 CPU 型号,直接写-mcpu=cortex-a55,编译器会自动启用所有支持的扩展。这样既不容易写错,也能保证扩展和 CPU 匹配。缺点是-mcpu会同时影响指令调度,可能不是最优的,但对于大多数场景来说够用了。
心得三:在 CI 里加一个参数合法性检查。如果你的项目用 CI 构建,可以在 CI 脚本里加一步,用-dM -E检查关键特性宏是否定义。如果没定义,直接 fail,避免把问题带到运行时。
心得四:保留一份"最小可用"的编译配置。我通常会在项目里维护两套编译参数:一套是"性能优先"的,启用所有可用扩展;一套是"兼容优先"的,只用最基础的 armv8-a。如果性能优先的版本在某个设备上崩了,可以快速切换到兼容版本验证问题是否出在扩展上。
心得五:注意+fp16和+fp16fml的区别。这两个扩展容易混淆。+fp16是 FP16 算术,armv8.2-a 就有;+fp16fml是 FP16 融合乘加,需要 armv8.4-a。如果你在 armv8.2-a 上写+fp16fml,编译器会报错。如果你确实需要 fp16fml,那就得用 armv8.4-a,但这样会进一步缩小兼容范围。
5.4 一个真实的排查案例
最后分享一个我实际遇到的案例。当时我在给一块瑞芯微的板子编译 NCNN,编译参数里写了-march=armv8.2-a+dotprod+fp16。编译链接都过了,但跑推理的时候,某些模型会崩,某些不会。崩的时候报Illegal instruction,但用objdump看,二进制里确实有SDOT指令。
排查过程:先确认 CPU 支持 dotprod(/proc/cpuinfo里有dotprod),然后确认编译器支持(-dM -E有__ARM_FEATURE_DOTPROD),最后用 gdb 抓崩溃点,发现崩在 NCNN 的某个卷积核里。仔细看代码,发现那个卷积核用了内联汇编,直接写了SDOT指令,但没有做运行时检测。而板子的 CPU 虽然支持 dotprod,但那个特定型号的 CPU 在某个微架构版本上有 bug,执行SDOT会触发异常。
最终解决方法是:在代码里加运行时检测,用getauxval(AT_HWCAP)检查HWCAP_ASIMDDP标志,如果没置位就回退到非 dotprod 的实现。这个案例告诉我们:编译期检测和运行期检测是两回事。编译期检测只能保证编译器支持,不能保证目标 CPU 真的能正确执行。
6. 从-march延伸出去:交叉编译参数管理的几个原则
聊完-march=armv8.2-a+dotprod+fp16这个具体参数,我想再延伸一下,聊聊交叉编译参数管理的一些通用原则。这些原则不仅适用于 ARM,也适用于其他架构的交叉编译。
原则一:编译参数要分层管理。不要把所有参数都堆在一个CFLAGS里。我通常会把参数分成三层:架构层(-march、-mcpu)、优化层(-O2、-flto)、调试层(-g、-fno-omit-frame-pointer)。这样在不同场景下可以灵活组合,比如发布版本用架构层+优化层,调试版本用架构层+调试层。
原则二:参数要可配置,不要硬编码。如果你的项目需要支持多种 ARM 设备,不要把-march写死在 Makefile 里。用变量或者 CMake option 来控制,比如ARM_ARCH_LEVEL、ENABLE_DOTPROD这样的开关。这样在给不同设备编译时,只需要改一个变量,不用翻遍整个构建系统。
原则三:文档化你的参数选择。在项目的 README 或者构建文档里,明确写出每个-march参数的含义、适用设备、以及为什么选这个参数。我见过太多项目,构建脚本里一堆-march参数,但没人知道为什么这么写。等当初写参数的人离职了,后面的人只能瞎猜。
原则四:定期验证参数的有效性。编译器在升级,目标设备在更新,你的-march参数可能过时了。建议每隔几个版本,重新跑一遍参数合法性检查和性能测试,确认参数仍然是最优的。我一般会在每次大版本发布前,用-dM -E和objdump重新验证一遍关键参数。
原则五:不要忽略-mtune。-march决定用什么指令,-mtune决定怎么调度指令。对于性能敏感的场景,-mtune同样重要。比如-mtune=cortex-a55会让编译器针对 A55 的流水线特性做调度优化。如果你只写了-march没写-mtune,编译器会用默认的调度策略,可能不是最优的。
7. 工具链选型与版本管理的实操建议
7.1 怎么选交叉编译工具链
ARM 交叉编译工具链的来源主要有几种:Linaro 发布的 GNU 工具链、各芯片厂商提供的定制工具链、以及发行版打包的工具链(比如 Ubuntu 的gcc-aarch64-linux-gnu)。我的建议是:
- 如果是给特定芯片厂商的板子编译,优先用厂商提供的工具链。厂商通常会针对自己的 CPU 做优化,而且版本匹配度更好。
- 如果是通用场景,用 Linaro 的工具链。Linaro 的更新比较及时,对新架构特性的支持也比较快。
- 如果只是快速验证,用发行版打包的工具链最方便,但版本可能比较老,对新扩展的支持可能不全。
7.2 工具链版本管理
交叉编译工具链的版本管理是个容易被忽视的问题。我建议把工具链的版本号记录在项目的构建文档里,并且在 CI 里固定使用同一个版本。如果工具链版本变了,编译出来的二进制可能有差异,导致"在我机器上能跑,在你机器上崩"的问题。
如果你用 Docker 做构建环境,可以把工具链打包进 Docker 镜像,这样版本就固定了。Dockerfile 里明确写出工具链的下载地址和版本号,避免使用latest标签。
7.3 验证工具链的扩展支持
拿到一个新工具链后,第一件事是验证它对目标扩展的支持。我通常会写一个小脚本,批量检查常用的扩展:
#!/bin/bash CC=aarch64-linux-gnu-gcc FEATURES=("armv8-a" "armv8.1-a" "armv8.2-a" "armv8.2-a+dotprod" "armv8.2-a+fp16" "armv8.4-a") for feat in "${FEATURES[@]}"; do if $CC -march=$feat -E -x c /dev/null -o /dev/null 2>/dev/null; then echo "支持: $feat" else echo "不支持: $feat" fi done这个脚本可以快速告诉你工具链支持哪些架构和扩展,避免在编译时才发现参数不合法。
8. 关于-march参数,我最后想说的几个细节
写到这里,关于-march=armv8.2-a+dotprod+fp16的坑基本聊完了。最后再补充几个细节,都是我在实际项目中踩过或者见别人踩过的。
细节一:-march和-mcpu同时出现时,-mcpu优先。如果你同时写了-march=armv8.2-a+dotprod和-mcpu=cortex-a53,编译器会以-mcpu为准,忽略-march里的扩展。因为-mcpu=cortex-a53隐含了-march=armv8-a,所以 dotprod 会被禁用。这个行为很容易让人困惑,建议不要同时写这两个参数。
细节二:-march=native在交叉编译里没有意义。-march=native是让编译器检测宿主机 CPU 的特性,但交叉编译时宿主机是 x86,目标机是 ARM,-march=native会检测成 x86 的特性,完全错误。交叉编译时永远不要用-march=native。
细节三:注意+号前后的空格。-march=armv8.2-a +dotprod和-march=armv8.2-a+dotprod是不同的。前者会被 shell 解析成两个参数,+dotprod会被当成一个独立的参数,可能导致编译器报错或者忽略。写的时候不要加空格。
细节四:-march参数会影响 ABI。如果你编译的是一个库,并且这个库会被其他项目链接,那-march参数会影响库的 ABI。比如启用了 dotprod 的库,可能在函数调用约定上和非 dotprod 的库有差异。所以库的-march参数要和使用者的-march参数保持一致,或者至少兼容。
细节五:不要忘了-mfloat-abi。对于 32 位 ARM,-mfloat-abi决定了浮点参数的传递方式(soft、softfp、hard)。这个参数和-march是独立的,但同样重要。如果-mfloat-abi设置错了,链接时会报 ABI 不匹配的错误。对于 64 位 ARM(AArch64),-mfloat-abi默认就是 hard,一般不用改。
这些细节看起来琐碎,但每一个都可能导致编译失败或者运行时崩溃。交叉编译本身就是个细节密集的活,-march参数只是其中一环。把这一环搞清楚了,其他的参数(-mcpu、-mtune、-mfpu、-mfloat-abi)也就触类旁通了。
我在实际项目中的体会是:交叉编译的坑,80% 都出在"想当然"上。想当然地以为编译器会检查参数、想当然地以为目标 CPU 支持某个扩展、想当然地以为工具链版本无所谓。每次踩坑之后,我都会把教训记下来,下次编译前先跑一遍检查脚本。这个习惯帮我省了很多时间,也希望这篇文章能帮你少走一些弯路。