C编译器学习笔记(一)预处理器

前言

最近用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编译器就是将这些字符串转换成机器码。

主流的C编译器如gcc、clang等都是由C语言编写的。

编译器的结构

image

ncc预处理器源代码分析

预处理器主函数是一个死循环,知道处理完所有内容才退出,对每一行代码,会分四步处理:

  1. 若list为空,读入一行,转换为token并放入list链表
  2. 处理#include#define#ifdef等宏指令
  3. 替换list链表中的宏
  4. 将list中的token输出,并从list中删除
    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。

posted @ 2022-01-02 12:19:40
评论加载中...
发表评论