用Keil开发HC32L13x刚把官方工程或同事给的代码拖进MDK满怀期待点下编译结果一上来就是一堆红色报错其中最常见的一条就是“__WEAK未声明”、“unknown type name __WEAK”或者是“expected ; before __WEAK”。这个报错看着不起眼但它会直接卡住整个工程编译而且如果你不搞懂来龙去脉就算这次让你蒙过去了下一次换个SDK版本、换台电脑、换个编译器版本它照样会把你拦在门口。这篇东西就是给你彻底解决这个问题用的。我会从__WEAK这个关键字的本质讲起再到Keil不同编译器版本之间的行为差异然后分几种路线给出完整可落地的修复方案最后结合HC32L13x这个芯片的中断函数定义场景把从启动文件到驱动库、再到用户应用层写中断服务函数的完整链条走一遍。不管你是刚接触单片机的小白还是被工程迁移折磨的老手照着这篇文章操作都能把“__WEAK不识别”这个问题从根上干掉。1. 先弄明白__WEAK到底是什么1.1 弱符号为什么你的中断函数能被“接管”要理解__WEAK先得理解“弱符号”这个概念。C语言里我们平时定义的函数在编译链接时都是强符号也就是说整个程序里只能有一个同名的函数定义如果你重复定义了两次链接器会直接报错“multiple definition”。但中断向量表、驱动库默认处理函数这类场景需要一种“有人接管就归你管没人接管就我来兜底”的机制。弱符号就是干这个用的。举个例子。HC32L13x是Cortex-M0内核它的中断向量表在启动文件里写着EXPORT BOD_IRQHandler [WEAK] EXPORT WDT_IRQHandler [WEAK][WEAK]的意思是这个符号在链接时是弱符号。如果整个工程里只有启动文件里这个弱符号链接器就把它当作中断向量表里对应槽位的真正入口如果你在自己的C文件里定义了一个同名的BOD_IRQHandler函数那链接器会优先把你定义的强符号填进向量表里弱符号自动被覆盖掉。C语言里声明一个函数是弱符号在ARM Compiler 5也就是armcc里最常用的写法就是void BOD_IRQHandler(void) __WEAK;函数的实现还是普通的void BOD_IRQHandler(void) { // 默认处理通常是死循环或空操作 }你再看官方SDK里那些驱动文件会发现大量这种写法。它的好处非常明显库作者可以先提供一个默认的弱实现你不用改了所有驱动文件只需要在自己的应用层定义同名的强符号就能“接管”中断处理。这对芯片厂商维护SDK、对用户快速二次开发都是很优雅的设计。1.2 armccAC5里的__WEAK与armclangAC6里的差异问题来了。Keil MDK在发展过程中经历了一次很大的编译器切换。老版本Keil MDK5.36之前的很多版本默认用的是ARM自家的armcc编译器后来Keil官方把编译器切换成了基于LLVM/Clang架构的armclang编译器也就是我们常说的ARM Compiler 6。这两个编译器在语法支持上有很大的差异。在armccAC5时代__weak、__packed、__align这些都是编译器的原生关键字直接在代码里写没有任何问题大小写形式__weak和__WEAK往往都能被识别因为很多SDK的头文件里也已经帮你做好了宏适配。到了armclangAC6时代事情变了。armclang基于Clang它的标准做法是用GNU风格的__attribute__((weak))来声明弱符号。Clang为了ARM生态兼容--mdk模式下也支持很多armcc的扩展关键字包括__weak但对于全大写的__WEAK它默认并不认识。这就是“__WEAK不识别”报错最核心的原因你手上的HC32L13x工程代码是按AC5时代语法写的而你的Keil MDK已经默认用了AC6编译器两边对不上。再看看Keil MDK的版本演进事情就更好理解了。Keil 5.36以前很多工程默认还能用AC5从5.37以后官方逐步弱化AC5到5.38、5.39、5.40默认几乎都是AC6了。你从老同事那拷来的HC32L13x工程很可能当初就是用AC5环境创建的拿到新版本Keil中一打开编译器被自动切到了AC6然后就是连锁式的编译失败。1.3 看清楚报错出现在哪里才是关键“__WEAK不识别”这个报错看起来简单但每次实际报错的位置可以差很远处理思路也不完全一样。我在实际开发中见过的常见报错形式有这么几种报错位置典型报错信息原因汇编启动文件Unknown opcode WEAK汇编文件里的[WEAK]写法被当成了指令一般是因为汇编器配置不对C头文件unknown type name __WEAKarmclang不认这个宏/关键字C源文件expected ; before __WEAK编译器把__WEAK当成普通标识符语法解析直接失败链接阶段undefined symbol弱符号没有生效导致某个中断入口没有对上如果报错发生在C文件或C头文件而且信息里明确指向__WEAK这个标识符那就是我们这篇文章要重点解决的核心问题。2. 报错现场的深度剖析2.1 为什么“不识别”不只是大小写的问题很多人看到“__WEAK不识别”后第一反应是把__WEAK改成__weak。这个操作有时候能解决问题但有时候还是不行因为实际原因可能藏在更深的地方。armccAC5对编译器关键字的大小写处理比较宽松而且armcc的老版本早就通过内置头文件把__WEAK这类大写形式定义好了。armclangAC6就不一样它面向的是C11/GNU语法标准体系标准做法就是__attribute__((weak))它没有专门为__WEAK这种大写形式预留内置宏。如果你在代码里直接写__WEAK编译器第一不敢认它是关键字第二又不认识这个标识符直接抛“unknown type name”。那为什么有些人的工程在AC6下能编译通过呢因为他们用的SDK或CMSIS头文件里早就帮你做了兼容适配。比如CMSIS的cmsis_compiler.h里有这样的逻辑#if defined ( __CC_ARM ) #define __WEAK __attribute__((weak)) #elif defined ( __ARMCC_VERSION ) ( __ARMCC_VERSION 6010050 ) #define __WEAK __attribute__((weak)) #elif defined ( __GNUC__ ) #define __WEAK __attribute__((weak)) #endif也就是说__WEAK最终会通过__attribute__((weak))被“翻译”过去。如果你的工程把头文件包含顺序搞错了、宏定义冲突了、或者SDK里的某个头文件没有正确包含CMSIS头文件那__WEAK就会在编译的某个瞬间暴露出来。2.2 大工程里的典型现场我把常见的报错现场整理成场景你可以对号入座场景一老工程换新IDE编译。原来的HC32L13x工程是在Keil 5.23上建的用的AC5编译器。你换到Keil 5.39上打开工程默认编译器已经变成了AC6一编译外部中断、定时器中断相关的源文件里满屏爆“__WEAK”。场景二直接用官方或第三方SDK的较老版本。有些SDK的版本比较老代码里全是裸写的__WEAK和__packed这些代码在AC5时代没问题在AC6默认配置下就会报错。场景三手动把GCC工程移植到Keil。很多开源工程或者别的IDE的例程用的是GCC编译器。程序员把它改成Keil工程时代码里可能既有__attribute__((weak))又有__WEAK混合编译时出现诡异的编译错误。这些场景背后统一的根源就是编译器变了代码的语法恰好和编译器不兼容。2.3 连编译器版本都无法选择这是另一个坑其实在解决__WEAK之前还有一层更坑的事。改到Keil 5.38之后官方默认只给你装AC6AC5只是作为可选的软件组件存在而且在部分新版本里你有可能连“使用AC5”这个选项都找不到。如果工程里的代码大量依赖AC5专有特性你会在安装编译器这一步就先崩溃一遍。所以我们不能只盯着“回退到AC5”这一条路真正的王道是让代码同时兼容AC5和AC6。这个思路我会在下面详细展开。3. 解决方案从应急到彻底根治3.1 方案一最快应急在Keil里切回ARM Compiler 5如果你的工程代码很短、改动范围很小、暂时不想动代码并且你的Keil还能找到AC5最简单粗暴的办法就是把编译器切回AC5。操作步骤打开Keil MDK找到菜单Project-Options for Target或者直接点魔术棒图标。切到Target选项卡找到ARM Compiler下拉框。如果你安装过AC5下拉框里会显示类似Use default compiler version 5的选项选它。如果你的下拉框里只有V6.x说明AC5编译器组件没装。你需要去Keil官网找到MDK的软件包下载并安装对应的ARM Compiler 5版本组件。安装后重启Keil再切一次。切回AC5之后再编译很多__WEAK报错会瞬间消失。为什么会消失因为armcc本身就是支持__WEAK大写写法的历史上很多芯片SDK都是基于这个编译器验证过的。但切回AC5不是长久之计。原因有两点第一AC5已经停止功能更新越来越老的编译器意味着你享受不到armclang带来的编译速度和代码优化第二AC5在一些新版本Keil里安装过程很折腾而且和部分新版芯片包配合不够融洽。我的建议是AC5只用来“救火”工程稳定运行之后还是要过渡到AC6兼容方案。3.2 方案二改代码用标准属性统一适配既然armclang认的是__attribute__((weak))而armcc也支持GNU风格的属性写法那我们完全可以直接把代码里的__WEAK替换成__attribute__((weak))。例如原代码void BOD_IRQHandler(void) __WEAK; void BOD_IRQHandler(void) { while(1); }改成__attribute__((weak)) void BOD_IRQHandler(void); void BOD_IRQHandler(void) { while(1); }你会发现这种写法在AC5和AC6下都能通过编译因为你用的不是某一家编译器私有的扩展关键字而是GNU风格、armclang和armcc都支持的属性语法。但直接手改每个文件工作量还是不小的。对于HC32L13x这种动辄几十个中断、十几个驱动文件的工程手改不仅慢还容易漏。更推荐的做法是在工程里加一个公共头文件统一把这些兼容适配做掉。比如新建一个hc32l13x_compiler.h内容如下#ifndef HC32L13X_COMPILER_H #define HC32L13X_COMPILER_H /* 统一弱符号声明宏 */ #if defined(__ARMCC_VERSION) (__ARMCC_VERSION 6010050) /* ARM Compiler 6 (armclang) */ #define WEAK_ATTR __attribute__((weak)) #else /* ARM Compiler 5 (armcc) 或其他编译器 */ #define WEAK_ATTR __attribute__((weak)) #endif #endif看到没有其实不管哪个编译器最后都用__attribute__((weak))所以这个宏几乎是通用写法你不必做太多条件判断。然后在所有需要声明弱中断函数的地方把__WEAK替换成WEAK_ATTR即可。这个方案最贴近工程化。因为宏是集中管理的以后就算你迁移到GCC、迁移到IAR只需要把宏定义那一处修改即可其余业务代码完全不用动。3.3 方案三保留源码不动在头文件层面做宏兼容如果你手头的HC32L13x SD
阅读完成 · 觉得有帮助?