编译器编译内核原理以及其应用
编译器编译内核原理以及其应用
上篇文档简要介绍了一下编译器的编译内核,当然介绍的很简单,没有深入进去,俗话说不深入怎么High
,所以这里我们深入进去搞一下,看看里面都有些什么有用的东东。
这里以市面上用的最多的Keil MDK为例,我们来研究下。
1、先上一张老图,然后我们仔细看下这个器、那个器的都是干啥的:
上一篇文章的图片,上一篇文章也简要介绍了下,但你可能会问,这东西了解了又能怎样?有什么用吗?那么好,司机我就来给大家带带路,看看了解编译原理是不是真的那么没用?
司机嘛,首先先来带大家认认路,你说这几个东东很重要,但是在Keil里面我们怎么找到他们呢?
你可以在"keil根目录\ARM\ARMCC\bin"下找到他们,如下图所示:
就是这几个东东,他们就是keil编译器的内核,把你编写的C代码变成可以烧写到MCU中执行的2进制文件就是这几个东东来完成的,其中armar.exe就是预处理器、armcc.exe是编译器、armasm.exe是汇编器、armlink.exe是链接器、fromelf.exe是elf文件的实用工具集。
有了这几个东东你可以甚至都不用Keil的编程界面都可以干活,直接在windows的shell界面里面通过命令行就可以编译链接,但这个是真没啥用,纯装B,有IDE不用非要自己敲命令行这部找罪受嘛,这里就不介绍这种没啥用的招数了。
当然了你也可以在集成开发环境中见到这几个货:
如上图:
1、汇编器的相关选项;2、编译器的相关选项;3、预处理器的相关选项;4、链接器描述文件(*.map)中包含的内容;
还有这个:
上图是专门的链接器相关配置的界面;那么这些东东都是干嘛用的呢?
让我们先来举个栗子,考虑比如下面这样一段代码:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR r0, =errorfunc
BLX r0
LDR r0, =__main
BX r0
ENDP
启动代码中的Reset_Handler代码,大家可以一眼看出来这段代码是错误的,其中红色字体的errorfunc是我故意填在这里的一个不存在的函数,编译必然报错,那么我们看看编译器是怎么报错的?
重点在途中蓝色底纹的部分,注意第一个单词:assembling……,然后后面跟着出错信息,没错,这是一个汇编器错误;再看下面,我再代码里面给出一个错误(我在main.c里面删掉了一个头文件):
还是看蓝色底纹部分,注意第一个单词:compiling……,然后后面跟着出错信息,没错,这是一个编译器错误,跟上面的不同,这是编译器报错,我们再看下面一个错误,代码如下:
int main(void)
{
char ch;
/* Init board hardware. */
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
BOARD_InitPins();
BOARD_BootClockFROHF48M();
BOARD_InitDebugConsole();
PRINTF("hello world.\r\n");
while (1)
{
ch = errorfunc();
PUTCHAR(ch);
}
}
错误的是红色字体的不存在函数errorfunc,我们make一下整个工程,看看怎么报错:
还是看蓝色底纹部分,注意第一个单词:linking……,然后后面跟着出错信息,这次是一个链接器错误!
发现了吗?大家经常说的编译器报错,其实是几个不同的东西再报错,编译器、汇编器、链接器都会报错,那你可能会问,知道这个有啥用呢?当然是有用的,比如汇编器报错,基本跟C语言没关系,基本可以断定是汇编语言语法错误或者是嵌入C语言的汇编语言出错(在C中嵌入汇编是一种非常有效的编程手段);如果是链接器报错,那就基本跟C语言语法无关,不是你的C语法上出错,很可能是你调用了不存在的函数或者链接器脚本写错了,或者使用了不存在的标号Symbol;而只有编译器报错,才总是你的C写的有问题,而我见过很多程序猿,看见报错就马上在自己的C代码里面翻来覆去的找,结果根本找不到错误,所以看到错误的第一反应是看看是谁报错?汇编器、编译器、还是链接器,这样可以排除掉N多可能性,可以帮助你迅速排查错误,以上示例代码很简单,大家可以一眼就看到错误,但是如果你的代码十分巨大,内部代码调用关系十分复杂的情况下你就会发现这个技巧相当有效。
2、你就是老板,当然要学会看工作报表:
我就是个写代码的,不是BOSS,干好活就得了。但是做开发环境的可不这么想,对于IDE来说,他们才是干活的,你是告诉他们怎么干活的老板,他们干了哪些事情,会通过报表的形式汇报给你,这就是编译器生成的中间文件,要当一个好老板当然得会看报表,所以一个合格的写代码的要学会看编译器的中间文件。
看下图……:
这是一个LPC5408的hello world例程由编译器生成的中间文件,这些就是你的“预处理器员工”、“编译器员工”、“汇编器员工”、“链接器员工”的工作报表。我们来一起看一下:
首先是扩展名为“.i”的预处理器文件,这货几乎很少用到,基本就是把C文件中用到的头文件内容导入到C语言中,如果头文件部分有问题,是比较容易找到问题的,很少会需要“.i”的协助,这里就不列出来看了,大家有兴趣自己打开这种文件看一下。
然后是“*.lst”和“*.txt”编译器&汇编器的报表文件,也很少用到,这里略过。
最后是真正有用的链接器描述文件“*.map”非常有用,绝对是你的“核心员工”的工作报表,也是最复杂的一个,工程配置里面可以看到其配置项,非常的多,在工程配置的Listing选项卡里面,如下图:
整个*.map文件也是巨长,里面大概的内容有:
(1)标号表
链接器生成的所有标号,包括所有函数名&全局变量名称都可以生成标号,每一个标号的地址以及其占有的存储空间情况。
(2)存储空间占用情况
这里只列一部分,map文件里面更详细,连每一个函数的占用情况都会列出来。
(3)所有被链接(不是编译)的函数列表及其地址,没有被链接的也有,如果你有兴趣看的话
如下图,截取了一小部分
但这些东西具体有什么用,我们会在以后的文章中大量涉及,这里很难单独说清楚,先打下一个基础,我们以后的文章会经常涉及这个map文件,如果你真的把这个文件看明白了并能熟练应用,那么恭喜你,你就解锁了一个重要技能包。
这里对编译器部分介绍很简单,以后其他文章里面我们会深入探讨,然后我也打算写一篇介绍GCC/GNU工具链用于嵌入式开发的文章,那是一片更广阔的天地。
原本打算写一篇深入介绍编译器原理&应用的文章,但写到这里还是写不下去了,主要是相互依存的点很多,避开这些点单独谈编译器原理&应用是非常不现实的。想来想去,这些体系性的东西还是要循序渐进,以后随着我们解锁了更多的技能包我们再会深入探讨这部分内容。