最近用gcc编译stm32f103的代码,因为stm32f103没有FPU,浮点数必须按照IEEE754的规则运算,也就是所谓的软件浮点数,编译时gcc会自动引入这些函数,但我遇到的问题是,我的程序进入浮点数处理函数就跑飞!!!这是标准库里的函数啊,为什么会出问题?
我不想花时间研究GCC,于是我换了stm32f401,它有FPU,想着这样就无需引入软件浮点数处理函数了,但是!不行!stm32f401的FPU只能处理32位的float,不能处理64位的double。根据c语言的规则,有时候float压栈会强制提升为double,所以还是要引入软件浮点数处理函数。同样,我的程序进入浮点数处理函数就跑飞。
难道GCC有BUG?GCC那么大名气咱也不敢怀疑它有BUG呀,一定是我自己没配置明白。先弄明白GCC的工作流程?瞅了一眼GCC那堪比linux的代码量!还不如自己写一个编译器!!!!
我的学习过程主要参考了下面几个项目:
你知道C语言是用C语言写出来的吗?是不是感觉有点矛盾?
准确的说是C编译器是用C语言写出来的。有句名言叫:世界上本没有编程语言,有的只是编译器。我们写的程序,只不过是一些字符串,C编译器就是将这些字符串转换成机器码。
主流的C编译器如gcc、clang等都是由C语言编写的。
#include
、#define
、#ifdef
等宏指令,并且将代码中的宏进行替换预处理器主函数是一个死循环,知道处理完所有内容才退出,对每一行代码,会分四步处理:
#include
、#define
、#ifdef
等宏指令 for (;;) {
//若列表为空,解析一行
while (list->count == 0) {
if (!fill(INPUT_LINE_NORMAL, list)) {
out("\n");
fclose(output_file);
exit(0);
}
check_directives = 1;
}
//这里对以#开头的宏指令进行处理
if (check_directives) {
directive(list);
check_directives = 0;
}
//对代码中出现的宏进行替换
if (list->first && (list->first->class == TOKEN_NAME)) {
struct macro * macro = macro_lookup(list->first->u.text, MACRO_LOOKUP_NORMAL);
if (macro) {
if (!macro->arguments) {
replace_tokens(list, 1);
continue;
} else {
int i = match_parentheses(list, list->first->next);
if (i > 0) {
replace_tokens(list, 1 + i);
continue;
}
if (i == -1) check_directives = 1;
}
}
}
//输出换行符
sync();
//输出链表中的第一个元素,并将其从链表中删除
if (list->count) {
if (list->first->class != TOKEN_SPACE)
out("%T ", list->first);
list_delete(list, list->first);
}
}
第一步比较简单,唯一值得注意的是,它将\
结尾的行与下一行算作一行,这保证#include
、#define
、#ifdef
等宏指令能够一次性读入到list中。
第二步处理#include
、#define
、#ifdef
等宏指令。
#include
的处理方式就是字符串合并,预处理器读入源文件会以字符串的方式村在内存中,当#include
一个文件时,会读入这个文件内容合并到之前的字符串中。
#define
的处理方式就是建立一个哈希表,以后的代码遇到的每个关键字都会到这个哈希表中查找,若找到就说明是一个宏,替换之。
#ifdef
、#ifndef
指令修改compiling全局变量,若不符合条件,该行list会被清空。
if (!compiling) list_clear(list);
第三步,对list中的token到宏哈希表中检查,查到就说明该关键字是宏,于是替换。由于要支持#define macro(a,b) replacement
这种带参数列表的宏,所以代码比较繁琐。
第四步,此时list内的token都已经被处理过,输出到文件,然后清空list。