Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
C库_LIBC
gcc
keil_MDK
【GCC编译优化系列】从KEIL转战GCC,一个C库函数bin文件增大了十几KB
发布于 2022-04-30 16:55:38 浏览:4336
订阅该版
[tocm] > **摘要** > > 本文结合博主真实的工程案例,从KEIL转战到GCC后,原有工程代码编译出来增加了十几KB的大小。本文将案例进行拆解,分享如何一步步解决这样的疑难问题,希望能给大家带来帮助和启发。 ---- **文章目录** 1 写在前面 2 问题描述 3 场景复现 3.1 项目迁移 3.2 编译复现 4 深入分析 4.1 分析工程代码 4.2 分析编译选项 4.3 分析链接选项 4.4 分析elf文件 4.5 分析map文件 4.6 寻找突破口 5 修复验证 5.1 问题修复 5.2 问题验证 5.3 one more question 6 经验总结 7 参考链接 8 更多分享 ---- # 1 写在前面 [KEIL](https://www.keil.com/) 这个玩意,相信大家都很熟悉,我想很多人上手开发嵌入式、单片机也是采用的这款入门级IDE。回想起我当初刚学习51单片机的时候,也是使用 **KEIL-C51** 编译环境来点灯的。后面工作了,开始接触嵌入式Linux方面的开发,慢慢地使用 KEIL 的机会就越来越少了。 受多年来在Linux环境下开发的重度影响,我现在基本的操作方式都是 Linux 系统下通过samba把代码共享出来,在Windows下通过samba获取共享,然后在Windows下编辑代码,随后在Linux下编译。这样的好处是,你可以随心所欲地选用你擅长的编辑工具,而不受限于任何一个IDE;同时Linux下对编译构建的控制、代码的查找、各种酷炫命令行的酸爽,真的只有谁用谁知道。 当然,不可否认,KEIL 还是有它强大的到底,毕竟基于ARM的开发,很多还是要依赖于 KEIL-MDK 的。KEIL-MDK-for-ARM 在处理ARM平台的编译,还是有它的独到之处,对比其他编译器,无论是代码尺寸还是汇编指令效率,都有不错的优势。正如网友所说:“**毕竟是官网推荐的收费编译器,能不优秀吗?**” 不过,这次我要带来的一个问题就是,代码从 KEIL 编译环境迁移到 GCC 编译环境后,生成的固件代码居然大了 **20KB** !详细内容,请看下文分解,通过本文你将可以了解到以下内容: - **KEIL编译环境切换到GCC编译环境的一般思路和方法。** - **如何分析GCC编译器生成的各种文件?** - **bin文件生成的拆解** - **常见的编译选项和链接选项** # 2 问题描述 问题是这样的,最近我们在使用一款ARM芯片在做开发,它的内核架构是 **ARM Cortex-M0**,原厂先是提供了 **KEIL** 编译环境的基础例程,由于这方面的例程比较成熟,我们很快就在上面完成了应用部分的开发,调试和测试都没什么大问题。 后来由于各种商务原因的考虑,我们决定转战到GCC编译环境,这就需要把原本KEIL上面构建的代码全部迁移到GCC编译环境。 经过一番操作,总算是使用GCC把代码编译跑起来了,但是问题来了,在GCC编译的固件bin文件,居然比KEIL编译环境下生成的固件bin文件大了将近 **20KB**,如下图所示: ![image-20220425171914097](https://s2.loli.net/2022/04/25/6OmrSCtJ2bzoXyl.png) 从代码量来,即便GCC版本与KEIL版本有些代码做了调整,核心业务逻辑代码基本没动呀,怎么会大这么多?要知道,我们这款芯片留给应用部分的Flash容量上限也就 **50KB**,另外在预留了 **50KB** 做OTA下载缓存,现在单应用部分就 **60KB+** 了,这个肯定是不能接受的。 无奈只能硬着头皮去找根源,为何代码差异不大的情况下,**编译出来的固件bin文件大小差异这么大** ? # 3 场景复现 为了能够准确还原项目的真实场景,我分了下面两个小节来介绍。 ## 3.1 项目迁移 由于我们早前的项目都在KEIL下构建的,团队内部在GCC方面有些积累,但都是在其他项目上积累,而对已有的KEIL的工程,并没有现成的脚本来实现 **一键迁移**,所以只能手把手做项目的迁移。 这里面遇到一个最头疼的问题,就是KEIL的工程文件管理,相信大家肯定也吐槽过它的槽点。这个就是 “KEIL里面的文件管理是自定义目录的,而这个目录并不对应真实的文件系统目录”。这样设计的好处是,开发人员可以在KEIL里面自定对不同文件进行分类管理,做分层设计之类的,然后把对应的文件添加到指定的类别里面。而这样设计的最大弊端就是,如果你不熟悉整个工程,你单从KEIL的文件管理那里,很难一下子就找到你想要的文件,同时,即便你在文件系统里面新建或删除了一个文件,在KEIL里面并不会自动帮你处理,你都需要重新去添加、删除。这个真的是反人类设计,每用一次,我吐槽一次! 只好借助于KEIL的工程文件了,后的后缀名是 **.uvproj**,这个文件是一个 **XML** 格式的文件,它基本就描述了整个KEIL工程配置,包括你需要的那些迁移信息,基本都可以从中找到。 这货大概长这样(因文件篇幅原因,我把Group部分折叠了): ![image-20220425235248302](https://s2.loli.net/2022/04/25/XmDJtnSOQGEhs3T.png) 项目迁移的过程,我们主要关注里面的四大部分: - **头文件检索路径** 这部分信息,可以搜索在KEIL的工程文件中搜索 **IncludePath**,那么就可以看到KEIL工程里配置的头文件检索路径有哪些。如下所示: ![image-20220425235649716](https://s2.loli.net/2022/04/25/A9i6TGuDWm3NgBs.png) - **所有源码文件列表** 要找源文件,这里所有显示看KEIL工程文件中的 **Group**,它对应的就是 KEIL 工程里面展示的一个个文件组,把它展开就可以看到对应组下面的文件列表,字段是 **FileName** 和 **FilePath** 。这两者的区别就是,一个是没有路径名,只有文件名,而另一个是带路径和文件名的。 有一个比较高效的方法,就是整个文件通篇搜索 **FilePath>** ,这样就找到了所有的文件,包括C文件、汇编文件、TXT文件等等。 不过这里需要注意的是,在KEIL工程中,有一些文件是被 **排除** 的,在上面检索出来的文件列表中,也需要将这部分文件给排除掉。 ![image-20220425235942478](https://s2.loli.net/2022/04/25/HDvsaQtWgO5Jfoy.png) - **编译选项列表** 关于编译选项这块,KEIL工程文件中的 **Cads** 和 **Aads** 就有说明,不过这里都是一个开关标志,可读性比较差,建议还是配合着KEIL IDE target配置界面来看。 ![image-20220426000853558](https://s2.loli.net/2022/04/26/4Ke7kgFCjTprSGa.png) - **链接选项列表** 关于编译选项这块,KEIL工程文件中的 **LDads** 就有说明,与 **Cads** 和 **Aads** 类似,可读性并不是很强。 ![image-20220426001125656](https://s2.loli.net/2022/04/26/lbg3iQFR4rLy8dS.png) **注**:其实当时找编译选项和链接选项的时候,我想过能不能找到像 Makefile 那样,加个 **V=1** 就把所有编译参数、链接参数输出来;但我没找到KEIL有类似的操作,有知道该方法的,麻烦告知下。 ## 3.2 编译复现 我们大家的GCC编译环境是在Linux下构建的,从上一节中取得的各项内容,整合到Linux环境中的Makefile中。 熟悉GCC编译和Makefile的都应该清楚,上面的各项内容需要这样整合: **头文件检索路径**:这部分内容添加到 **CFLAGS** 中,使用 **-Ixxx** 把头文件路径加进去。 **所有源码文件列表**: 这部分内容添加到 **SRC-y** 中,使用(与Makefile文件的)相对路径添加进去。 **编译选项列表**: 这部分内容添加到 **CFLAGS** 中,这里主要包括两个方面,一个是传递GCC编译器的编译选项,比如 **优化等级参数、编译特性参数、警告参数** 等等;另一个是传递给源码的宏定义,这里需要对宏定义加字母D,比如 **-Dxxx** 或 **-Dxxx=yyy** 。 **链接选项列表**:这部分内容添加到 **LDFLAGS** 中,这里主要是指明链接器如何生成最终的可执行文件,常见的内容包括:链接脚本文件、生成MAP文件列表、是否启用段回收优化、是否使用标准库等等。 除了上面的部分,还有两个使用GCC编译比较关键的东西是:**启动脚本** 和 **链接脚本**,幸运的是这一块原厂提供了些支持,我们很快就搭起来了。 在Makefile中完成以上内容添加后,再加上一段使用objcopy生成 bin 文件的流程控制,就可以顺利执行make拿到基本的应用bin文件。这里需要注意的是,生成的bin文件不见得立马就可以拿来烧录,往往还需要结合原厂提供的打包工具,把应用bin文件打包或重组,生成可以被烧录工具成功烧录的固件bin文件,不过后续的流程并不在本文的讨论范围。 在上面迁移编译过程中,肯定多多少少会遇到各式各样的问题,一般来说,参考我之前的 [解决编译问题的一般思路](https://recan.blog.csdn.net/article/details/122261137),基本都可以解决。 当我们顺利编译得到固件bin文件的时候,第一时间也是惊呆了,正如上一节描述的那样,居然整整比KEIL环境编译出来的大了 **18KB**,这可完全交不了差啊! # 4 深入分析 既然问题出现了,那么就深入分析下到底是为何固件bin文件大了多?究竟是代码问题还是GCC编译器的锅? ## 4.1 分析工程代码 为了大家更好理解我下面的分析过程,我再次捋一下我们的工程情况。 1. KEIL版本工程,添加了我们的应用部门代码,编译出来大概 **46KB**; 2. GCC版本工程,原厂提供的基础demo工程,不含我们的应用部门代码,编译出来大概 **19KB**; 3. GCC版本工程,从KEIL版本工程移植过来,保留应用代码,编译出来大概 **64KB**; 4. GCC版本工程,从KEIL版本工程移植过来,删除应用部分代码,与原厂的基础demo功能是对标的,编译出来大概 **37KB**。 OK,从上面几个版本中,我们可以初步排除应用部分代码引入的bin文件增大,所以落脚点应重点放在工程2和工程4的对比上面。 这时候,BC要发挥作用了,简要拉出来对比下: ![image-20220426004123849](https://s2.loli.net/2022/04/26/TkuChbSctOiWyQq.png) 不比不知道,一比吓一跳! 这不一样的地方可太多了呀,哪个才是真正的差异啊? 原来,这个项目KEIL版本的工程原本由另一个团队基于原厂的demo调试过,修复了很多原厂的坑,但你不能说那些标红的地方都是,只能说都有可能,而且你也不能去找之前的团队说,为何会这样,毕竟人家在KEIL下跑得好好的。 虽说有改动,但整体还是延续了原厂demo的实现,只是部分代码上做了调整和优化,并没有大改动。 不过,要想从这些差异中一个个对比分析出来,难度可不小,只能再接下往下,换个思路分析看看了。 ## 4.2 分析编译选项 熟悉GCC的朋友都知道,GCC有好几个优化级别,不同的优化级别对生成的bin文件大小会不一样,而在嵌入式工程代码中,大家用得最多的,我想应该是 **Os** 优化级别,这个优化级别和-O3有异曲同工之妙,当然两者的目标不一样,-O3的目标是宁愿增加目标代码的大小,也要拼命的提高运行速度,但是这个选项是在-O2的基础之上,**尽量的降低目标代码的大小**,这对于存储容量很小的设备来说非常重要。 基于这一点认知,我认为有必要检查对比下两个工程的编译选项,结合分析的结论下,两边用的都是 **Os** 优化级别。 未果,继续分析。 我之前写过一篇文章:[【gcc编译优化系列】如何(不)回收未发生调用的函数](https://recan.blog.csdn.net/article/details/121169279),里面提到了要想缩小最终的固件bin文件,可以启用GCC的 **--gc-sections** 选项,即 **段回收** 机制。 这个选项可以把工程代码中没有被调用的函数或全局变量,在链接的时候去除掉,达到优化固件bin大小的目的。这个选项在实际使用过程中,需要与编译选项 **-ffunction-sections** 和 **-fdata-sections** 配合使用,感兴趣的可以从文章链接中找找答案。 但是,这个怀疑,在我仔细对比两个工程的编译选项之后,发现其实都启用了 **-ffunction-sections** 和 **-fdata-sections**,同时我还发现了一个我不太认识的编译选项 **-flto**。 这个 **-flto** 简单查了一下资料说是:**在链接时优化**,可以更大程度地发挥优化效果。具体其他的,没有调研没有发言权,况且现在两个工程都开了这个选项,显然很大可能不是它引入的,所以暂且跳过。 ## 4.3 分析链接选项 到了分析链接选项这一步,除了上一小节提及的 **--gc-sections** 链接选项外,还需要关注一个比较常见的链接选项 **--specs=xxx.specs**。 关于这个选项,之前我写过一篇文章,简单研究过这个参数,感兴趣的可以读一读:[GCC编译链接时的--specs=kernel.specs链接属性](https://recan.blog.csdn.net/article/details/121433402)。 简单来说,这个文件就指明了在链接阶段,链接器按照那个约定的specs文件(规范、模板文件)去执行链接;对应的,一般有 kernel.spcs、nosys.specs等等,这些spec文件可以在交叉编译工具链目录可以找到。 很遗憾,在分析链接选项的时候,这个spec文件,两边都使用的 **nosys.spec**,并无差异,所以它也不是问题的关键。 补充一句:**--specs=nosys.specs 表示使用静态库 libnosys.a** 。 ## 4.4 分析elf文件 到了分析elf文件这一步,我们可以用以下几个Linux命令做分析: - **size** 命令 size 用于查看目标文件、库或可执行文件中各段及其总和的大小,是 GNU 二进制工具集 [GNU Binutils](https://www.gnu.org/software/binutils/) 的一员。 我们来执行一下 **size** 命令,看下对应的输出: ```shell recan@ubuntu:~$ size test_app_*.elf text data bss dec hex filename 19174 2 5572 24748 60ac test_app_19KB.elf 35225 2544 5708 43477 a9d5 test_app_37KB.elf ``` 我们可以看到 37KB的这个elf文件在 **text** 代码段中,明显比 19KB 这个elf文件大了很多。由于我们的bin文件就是由elf文件转换来的,所以对elf文件 **求size**,在一定程度上就反应了bin文件的大小。 这里再简单补充一个小知识:**bin文件的大小约等于 TEXT + DATA**,注意这里是不需要加上 **BSS** 的, 原因是BSS段在初始化的时候一般都被手动清零了,不需要体现在bin里面。 - **readelf** 命令 `readelf` 用来显示一个或者多个 `elf` 格式的目标文件的信息,可以通过它的选项来控制显示哪些信息。这里的 `elf-file(s)` 就表示那些被检查的文件。可以支持32位,64位的 `elf` 格式文件,也支持包含 `elf` 文件的文档(这里一般指的是使用 `ar` 命令将一些 `elf` 文件打包之后生成的例如 `lib*.a` 之类的“静态库”文件)。 简单来说,它就是用于分析elf的组成的,它有几个选项比较常用:比如 -h 只查看elf头部的信息,-a 则查看所有elf文件内容。 为了更好地看到全貌,我采用了 -a 选项,并把结果分别导出到2个文本文件中: ```shell recan@ubuntu:~$ readelf -a test_app_19KB.elf > test_app_19KB.elf.log recan@ubuntu:~$ readelf -a test_app_37KB.elf > test_app_37KB.elf.log ``` 再次上BC,比较一下看看。 看着好像文件不是很少,但打开发现也有个千把行,但这对比看代码还是简略了很多。 这里需要对elf文件有一定的了解,内容那么多,我们需要抓重点,重点关注下 **FUNC** 字样的函数。 逐步往下翻,我看到了这一段对比,似乎有了一点点思路; ![image-20220426133501510](https://s2.loli.net/2022/04/26/IMaE7hPqGt3fylx.png) 我们是不是可以合理怀疑下 **C库** ? 当然,这里还给不了答案,我们接着往下看。 ## 4.5 分析map文件 关于如何生成map文件,可以参考下 [这里](https://recan.blog.csdn.net/article/details/84946863)。 map文件就是通过编译器编译之后,生成的程序、数据及IO空间信息的一种映射文件,里面包含函数大小,入口地址等一些重要信息。从map文件我们可以了解到: - 程序各区段的寻址是否正确; - 程序各区段的size,即目前存储器的使用量; - 程序中各个symbol的地址; - 各个symbol在存储器中的顺序关系(这在调试时很有用); - 各个程序文件的存储用量。 所以,elf的镜像分布,我们在map文件中是可以看出来的,在一定程度,为何bin文件大了那么多,多少从map文件是可以发现的。 使用BC一对比map文件,一打开的时候,就发现不对劲了: ![image-20220426164724894](https://s2.loli.net/2022/04/26/x8ZOlzYBrLHMsKe.png) ![image-20220426164815820](https://s2.loli.net/2022/04/26/eUxKzyhbNnTVdqk.png) 这样真的很难不去怀疑C库了? ## 4.6 寻找突破口 接下来,开始对常见的C库函数进行排查,排查的方法是这样的: > 先检查对应的C库函数是否在两个工程中都有使用,如果是,直接跳过;如果某个C库只在37KB的工程中出现,那么这个函数则需要重点关注。 为了,有效地准确检索函数 **关键字**,这里我强烈建议大家使用Linux下的命令行 **grep** 。因为它不仅可以精准地检索C文件、头文件、map文件,还可以检索到静态库.a文件、动态库.so文件、elf文件等等非文本文件。 由于一般我们的嵌入式工程都是启用高编译优化级别的,所以不能简单地搜索代码是否调用,更为准确的,我认为是检索elf文件,因为在高优化级别下有些看似调用了的函数在生成elf的时候却被优化移除了。 下面对常见的C库进行分类,整体上从用途上分,有以下一些: - **内存管理类** 这里包括 **malloc、free、calloc、realloc** 之类的;这几个函数常见于内存稍微富余的嵌入式工程中,比如那些可以抛 RTOS 的平台,这些函数需要重点看看。 - **字符串操作类** 这里比较典型的函数是: **strlen、strcpy、strcmp、strchr、strstr、memset、memcpy、memcmp、memmove** 等等,这些函数太常用了,常用到我基本认为不太可能会问题出在这。 - **文件操作类** 这里就包括关于文件的几个操作函数:**open、lseek、read、write、close** 等等;假如你的工程有用到文件系统或者定义了类似Linux的VFS中间层,那么这些函数你需要重点关注。 - **printf操作类** 这类函数也非常常见,主要包括:**printf、fprintf、sprintf、dprintf、vprintf、vsprintf、vfprintf、cdprintf** 等等。其实大家可以 `man 3 printf` 查看到更多关于这些函数的信息。 ```shell man 3 printf PRINTF(3) Linux Programmer's Manual PRINTF(3) NAME printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf - formatted output conver‐ sion SYNOPSIS #include
int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); int dprintf(int fd, const char *format, ...); int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...); #include
int vprintf(const char *format, va_list ap); int vfprintf(FILE *stream, const char *format, va_list ap); int vdprintf(int fd, const char *format, va_list ap); int vsprintf(char *str, const char *format, va_list ap); int vsnprintf(char *str, size_t size, const char *format, va_list ap); ``` 这一类函数,往往在嵌入式里面非常容易出问题,比如最常见的printf函数我们需要重定向到串口输出,那底层C库的实现肯定不知道你要从哪个串口输出,以及怎么输出,所以这个时候需要上层做些适配。 正是由于这样的原因,我们排查的过程中,应对这类函数多留一个心眼。 - **其他类别** 这里还包括更多,我就不一一列举,具体可以参考下面这个头文件说明列表: ```shell 标准c库函数头文件列表
诊断
字符检测
错误检测
系统定义的浮点型界限
系统定义的整数界限
区域定义
数学
非局部的函数调用
异常处理和终端信号
可变长度参数处理
系统常量
输入输出
多种公用
字符串处理
时间与日期 ``` # 5 修复验证 ## 5.1 问题修复 基于上面的线索分析,基本排查的方向就比较清晰了。因为这个问题需要不断地试探和验证,所以我采用的是每找到一个存在可能性的C库函数,就在源码工程里面(含编译输出的各种文件)grep 一下,找到了位置,再回头来看看源码,同时配合这map文件来对比分析。 这样一套思路操作下来,问题点逐渐暴露了。我发现了这么一个函数:**vsprintf** ! ![image-20220427001926968](https://s2.loli.net/2022/04/27/13IKTcVwSGpXDbt.png) ![image-20220427002013600](https://s2.loli.net/2022/04/27/Q85HBpnz27RregY.png) elf文件和map文件都符合这个特性:**37KB工程中有,但19KB工程中没有** ! 立马靠拢定位对应代码,看到代码我开始恍然大悟: ```c int uart_printf(const char *fmt,...) { int n; va_list ap; va_start(ap, fmt); n = vsprintf(uart_buff, fmt, ap); va_end(ap); uart_putchar(uart_buff); if(n > sizeof(uart_buff)) { uart_putchar("buff full \r\n"); } return n; } ``` 原来这个工程使用 uart_printf 做printf的重定向输出。 这时候立马跳出一个问题,为何19KB的工程没有啊?它没有重定向输出,显然不是! 看了下代码,原来它自个整了一个 **vsprintf** : ![image-20220427002730530](https://s2.loli.net/2022/04/27/Cb63lBmTZjv9H2a.png) 这里再捋一捋这段代码的历史,早期另一个团队用这份带**vsprintf**的工程仅在KEIL环境是编译,而原厂整的这个带 **local_vsprintf** 的工程仅在Linux GCC环境下编译。 我猜想可能原厂也遇到类似的bin大小的问题,所以在相关代码附近,依然可以看到 local_vsprintf 的源码实现。 一切都向着美好的方向进行着,下面开始重点验证。 ## 5.2 问题验证 了解了缘由后,立马调整相关代码,调整的方式也很简单,就是把 vsprintf 重新改成 local_svprintf,即可。 之后,我需要重新清理工程,再次编译生成固件bin文件。还是以基础demo工程为例,我们的目标是生成与原厂工程生成大小类似的 **19KB**。 结果一试,果然,生成的大小就是 **19KB**。 长舒一口气,看到交差的希望了。 随后,把之前屏蔽的应用部分代码打开,一编译,结果让我有些骄傲,居然比KEIL的数据还好一些,固件bin大小是 **42KB**,比KEIL工程的是 **46KB** 还有小一些。 这个时候我想起了那个 **-flto** 参数,发现还是有点东西,回头有空再研究研究。 当然,固件bin大小小了是好事,但功能不能跑偏了呀,于是下载到板子一跑,基本功能都通过了。 完美,收工! ## 5.3 one more question 看到这里,细心的朋友可能会问一个问题:**刚刚看到的都是说GCC工程大了18KB,为何之前的KEIL工程没有这种问题呢?** 回答这个问题,还得结合上文提及的SDK版本的历史;最早的KEIL版本中,的确是使用C库的 **vsprintf**,否则我也不会把原来的代码迁移到GCC之后就遇到这么 **离谱** 的问题。 回想一下这个问题的根源,或许已经有答案了。 这个 **vsprintf** 是在C库中实现的,在GCC环境下,该库位于GCC交叉编译链的系统库路径下;而KEIL环境是使用的ARMCC编译器,自然对应的C库就位于ARMCC编译器的系统库路径下。 通过这个实验,我们可以有理由的推测,ARMCC中自带的C库在某种程度上会比GCC中自带的C库,依赖会少一些,实现的代码相对较少,这个就直接影响了链接 **vsprintf** 的固件的大小。 看似一个函数接口的区别,结果固件bin却有 **18KB** 大小的区别。 遇到类似的问题,大家对C库还是持有谨慎的态度会比较好。 # 6 经验总结 - **KEIL有KEIL的优势,GCC有GCC的优势,两者有时候不可兼得**; - **KEIL(ARMCC)编译对ARM芯片有天然的优势,无论从代码性能和代码尺寸都有更佳的表现,毕竟是同一个爹妈生的**; - **GCC的优势在于开源,利于折腾;方便你做各种一键式(脚本)集成**; - **标准C库的函数多种多样,适当分类后,更容易区分掌握**; - **在嵌入式领域,谨慎使用诸如printf、vsprintf等原生的标准C库**; - **结论往往在不断实践、不断推导的过程中,变得越来越清晰**; - **GCC的编译选项,还有知识盲区,值得再深入研究研究,比如-flto**; - **了解历史代码的演变过程,可能会有助于你解决一些看似乱七八糟的问题。** # 7 参考链接 本次复盘分析中,引用了下列相关文档,若干知识点可以在下面文章中找到答案,感兴趣的可以一读。 - [【对比参考】KEIL与GCC的编译环境对比分析](https://bbs.21ic.com/icview-1724082-1-1.html) - [【GCC编译优化系列】一文带你了解C代码到底是如何被编译的](https://recan.blog.csdn.net/article/details/121709458) - [【经验科普】实战分析C工程代码可能遇到的编译问题及其解决思路](https://recan.blog.csdn.net/article/details/122261137) - [【Linux编程】如何使用GCC编译源代码时输出map文件?](https://recan.blog.csdn.net/article/details/84946863) - [【GCC编译优化系列】如何(不)回收未发生调用的函数](https://recan.blog.csdn.net/article/details/121169279) - [【GCC编译优化系列】GCC编译链接时候--specs=kernel.specs链接属性究竟是个啥?](https://recan.blog.csdn.net/article/details/121433402) # 8 更多分享 > --- > > **[架构师李肯](https://recan.blog.csdn.net/?type=blog)** > > 一个专注于嵌入式IoT领域的架构师。有着近10年的嵌入式一线开发经验,深耕IoT领域多年,熟知IoT领域的业务发展,深度掌握IoT领域的相关技术栈,包括但不限于主流RTOS内核的实现及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其实现、底层汇编及编译原理、编译优化及代码重构、主流IoT云平台的对接、嵌入式IoT系统的架构设计等等。拥有多项IoT领域的发明专利,热衷于技术分享,有多年撰写技术博客的经验积累,连续多月获得RT-Thread官方技术社区原创技术博文优秀奖,荣获[CSDN博客专家](https://recan.blog.csdn.net/?type=blog)、CSDN物联网领域优质创作者、[2021年度CSDN&RT-Thread技术社区之星](https://blog.csdn.net/szullc/article/details/123860472)、[RT-Thread官方嵌入式开源社区认证专家](https://club.rt-thread.org/ask/experts.html)、[RT-Thread 2021年度论坛之星TOP4](https://club.rt-thread.org/ask/article/3317.html)、[华为云云享专家(嵌入式物联网架构设计师)](https://bbs.huaweicloud.com/community/usersnew/id_1573655458316259)等荣誉。坚信【知识改变命运,技术改变世界】! > > --- 欢迎关注我的[github仓库01workstation](https://github.com/recan-li/01workstation/tree/master/workspace/gcc/gc_section) ,日常分享一些开发笔记和项目实战,欢迎指正问题。 同时也非常欢迎关注我的CSDN主页和专栏: [【CSDN主页-架构师李肯】](http://yyds.recan-li.cn) [【RT-Thread主页-架构师李肯】](https://club.rt-thread.org/u/18001) [【C/C++语言编程专栏】](https://blog.csdn.net/szullc/category_8450784.html) [【GCC专栏】](https://blog.csdn.net/szullc/category_8626555.html) [【信息安全专栏】](https://blog.csdn.net/szullc/category_8452787.html) [【RT-Thread开发笔记】](https://blog.csdn.net/szullc/category_11461616.html) [【freeRTOS开发笔记】](https://blog.csdn.net/szullc/category_11467856.html) 有问题的话,可以跟我讨论,知无不答,谢谢大家。
10
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
李肯陪你玩赚嵌入式
2022年度和2023年度RT-Thread社区优秀开源布道师,COC深圳城市开发者社区主理人,专注于嵌入式物联网的架构设计
文章
47
回答
504
被采纳
82
关注TA
发私信
相关文章
1
请问rtt-studio工程如何转为MDK工程?
2
时间片视频教程移植,时间片功能不起作用是什么情况
3
使用env生成keil5工程,怎么添加全局宏?
4
Rtthread-studio编译报错
5
cubemx配置keil正常输出stdio输出不了PWM寄存器的值仿真一样?
6
RTT-Studio生成的工程,子目录没有SConscript文件
7
通过ENV生成的MDK5工程,为什么默认的启动文件是context_gcc.S呢
8
试用了一下liteos,我又回来啦
9
在applications文件夹下添加了新的.c/.h文件后无法生成MDK5工程
10
pandora开发板使用cjson,内存不足。
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
7
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
3
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部