记:关于编译时为何基础偏移为PC

11 篇文章 1 订阅
2 篇文章 0 订阅

0x00前言

本文源于前几周与同事的讨论,在汇编完成后的嵌入式程序中(甚至有可能是所有编译类型的语言生成的程序下)基准的参考指针是PC?而不是单纯的绝对或者是相对寻址?

阅读本文,您可能需要掌握的知识:

技能熟练度
汇编语言了解
C语言熟悉
微机原理熟悉
文件结构了解

0x10 起因

刚开始,源于前几周与同事的讨论,在汇编完成后的嵌入式程序中(甚至有可能是所有编译类型的语言生成的程序下)基准的参考指针是PC?而不是单纯的绝对或者是相对寻址?

原本可能是一个及其简单的小问题。结果在同事中引起了不小的讨论。大家也都没啥具体的了解,但是觉得这个事情也蛮奇怪的,笔者这边查询了一些资料。发现这个现象也是蛮有意思的,富含程序最基础的思想,遂整理如下的文章。

0x20 思考问题的角度原因

首先,作为一个标准的程序员,这里建议读者建立一种思维方式,就是使用多种角度下对某种现象实现的方式进行建模。这也是笔者下面讨论的所有前提。作为一个程序员,一般思考角度都是在语言实现的角度上。而这个问题的原因其实是由于当前编译器的编译思维引起的。也就是说来话长了。

0x30 抽象语法树

现代的编译型语言,基本上都是三步走:预编译/预处理,编译,链接并且合并参数。

作为正经的程序员,最擅长的就是把大家都看得懂的简单语法写成大家都不懂的奇葩实现😄。比如下列代码:

#define DEFINE_DISP_TYPE_PAGE   page	      //页面
#define DEFINE_DISP_TYPE_TXT    txt	  //文本框
#define DEFINE_DISP_TYPE_PROGTXT    prog_txt	  //滚动文本框
#define DEFINE_DISP_TYPE_NUMBER    num	  //数字框
#define DEFINE_DISP_TYPE_BUTTON		button	   //按键
#define DEFINE_DISP_TYPE_SLIPPER	slipper	      //滑块
#define DEFINE_DISP_TYPE_PROGRESS	progress	     //进度条
#define DEFINE_DISP_TYPE_TOUCH		touch	    //触摸热区
#define DEFINE_DISP_TYPE_BT_SWITCH	bt_switch	    //双态按钮
#define DEFINE_DISP_TYPE_QR_CODE	qr_code	    //双态按钮


#define disp_set_int(OBJ,I) disp_##OBJ##_set_##I(char* obj_name,int level)\
    {\
        S_DISP_BASE_NODE* now =  gui_private.now_page->disp_page;\
        for (int i = 0; i < GUI_MAX_PAGE_COMPONENT; ++i)\
            {\
                if(now[i].type == DISP_TYPE_LAST)\
                    return;\
                if(strcmp(now[i].obj_name, obj_name) == 0)\
                {\
                    now[i].obj.OBJ.I = level;\
                    now[i].editor = 1;\
                }\
            }\
    }

#define disp_set_string(OBJ,STRING) disp_##OBJ##_set_##STRING(char* obj_name,char* string)\
    {\
    	S_DISP_BASE_NODE* now =  gui_private.now_page->disp_page;\
        for (int i = 0; i < GUI_MAX_PAGE_COMPONENT; ++i)\
            {\
                if(now[i].type == DISP_TYPE_LAST)\
                    return;\
                if(strcmp(now[i].obj_name, obj_name) == 0)\
                {\
                    strcpy(now[i].obj.txt.txt,string);\
                    now[i].editor = 1;\
                }\
            }\
    }

#define disp_get_int(OBJ,I) disp_##OBJ##_get_##I(char* obj_name,int level)\
    {\
        S_DISP_BASE_NODE* now =  gui_private.now_page->disp_page;\
        for (int i = 0; i < GUI_MAX_PAGE_COMPONENT; ++i)\
            {\
                if(now[i].type == DISP_TYPE_LAST)\
                    return;\
                if(strcmp(now[i].obj_name, obj_name) == 0)\
                {\
                    now[i].obj.OBJ.I = level;\
                    now[i].editor = 1;\
                }\
            }\
    }

#define disp_get_string(OBJ,STRING) disp_##OBJ##_get_##STRING(char* obj_name,char* string)\
    {\
    	S_DISP_BASE_NODE* now =  gui_private.now_page->disp_page;\
        for (int i = 0; i < GUI_MAX_PAGE_COMPONENT; ++i)\
            {\
                if(now[i].type == DISP_TYPE_LAST)\
                    return;\
                if(strcmp(now[i].obj_name, obj_name) == 0)\
                {\
                    strcpy(now[i].obj.txt.txt,string);\
                    now[i].editor = 1;\
                }\
            }\
    }

void disp_set_int(DEFINE_DISP_TYPE_TXT,pco);
void disp_set_int(DEFINE_DISP_TYPE_TXT,bco);
void disp_set_int(DEFINE_DISP_TYPE_TXT,pw);
void disp_set_int(DEFINE_DISP_TYPE_TXT,pic);
void disp_set_string(DEFINE_DISP_TYPE_TXT,txt);

void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,pco);
void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,bco);
void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,en);
void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,dir);
void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,dis);
void disp_set_int(DEFINE_DISP_TYPE_PROGTXT,tim);
void disp_set_string(DEFINE_DISP_TYPE_PROGTXT,txt);

void disp_set_int(DEFINE_DISP_TYPE_NUMBER,pco);
void disp_set_int(DEFINE_DISP_TYPE_NUMBER,bco);
void disp_set_int(DEFINE_DISP_TYPE_NUMBER,val);
void disp_set_int(DEFINE_DISP_TYPE_NUMBER,lenth);

void disp_set_string(DEFINE_DISP_TYPE_BUTTON,txt);

void disp_set_int(DEFINE_DISP_TYPE_SLIPPER,pco);
void disp_set_int(DEFINE_DISP_TYPE_SLIPPER,bco);
void disp_set_int(DEFINE_DISP_TYPE_SLIPPER,val);

void disp_set_int(DEFINE_DISP_TYPE_PROGRESS,pco);
void disp_set_int(DEFINE_DISP_TYPE_PROGRESS,bco);
void disp_set_int(DEFINE_DISP_TYPE_PROGRESS,val);

void disp_set_int(DEFINE_DISP_TYPE_TOUCH,id);

void disp_set_string(DEFINE_DISP_TYPE_BT_SWITCH,txt);
void disp_set_int(DEFINE_DISP_TYPE_BT_SWITCH,val);

void disp_set_string(DEFINE_DISP_TYPE_QR_CODE,txt);

就是简单了利用宏定义,实现了这种类似于代码生成模板的操作(这里只是用来标识基本的复杂应用的方案,实际上最好使用inline的函数展开更好一些)

类似这种代码,基本上都是在预处理阶段就会被处理完毕。

随后,编译器就会根据这个代码生成一个“抽象语法树”,如果有对于编译器有些研究或者是对于指令解析器有部分实现的人都会有过这种经验:需要一种格式来描述解析到的各种数据。如数组、变量、函数、指令内的参数与外部的输入参数。

编译器的抽象语法树就是将人类能看懂的代码变成程序好处理的抽象结构。最后生成一个基本的程序雏形。

0x40切换视角——编译器

下面,请读者切换视角,想象一下自己是一个编译器开发人员(或者是编译器实体,请抛弃一切应用软件开发者的思维)。

拿到一段字符串,现在有一套比较完整的抽象语法树。那么,一个函数的结构可能是如下接口

typedef struct
{
    //函数名称
    char* codexxxxx_string;
    //函数入口点
	unsigned int codexxxxx_input;
//输入
    void* value1_arg;
    int value1_count;
    ……
    void* valuexx_arg;
    int valuexx_count;
//输出
    void* value1_arg;
    int value1_count;
    ……
    void* valuexx_arg;
    int valuexx_count;

}FUNCTION_CODE_XXXXXT;

这里就能大致看出来一个基本的结构,这样既可以满足当前函数的输入输出,也能满足当前函数作为模块化的选项的要求。需要注意的是,这里的函数入口点是一个未定义项。这项必须要到运行或者最后链接分配位置的时候才能确定,这样就可以最大程度上满足对于模块化程序的要求(各种各样的二进制类型elf库)。也就是说,编译好的程序就是一大堆电脑散件,虽然不是很散,但是实际上也不成样子。链接就是将其组成一个完整的电脑,让别人可以完成使用。

编译完成的程序模块
编译完成的程序模块

链接完成
链接完成

下面,作为一个编译器,需要对当前函数内部进行转译,将人类喜欢的代码转换成机器喜欢的二进制数据。如果笔者对于汇编语言有所了解就应该知道一句经典语句:代码及数据,数据及代码,取决于使用者的角度。所以对于一个在编译期间还不知道自己函数入口指针的数据块,编译器要怎么处理呢?

找到一个基准指针,读者们很聪明,一下就能想到这个选项。那么,熟知CPU寄存器组成的读者应该知道,CPU寄存器基本分为三类:数据寄存器、特殊寄存器、程序指针寄存器。

数据寄存器,存放暂时使用的数据,用来存放一个基准指针代价极大(一般芯片没几个数据寄存器,每个都要用在刀刃上)

特殊寄存器,有的存堆栈,有的存一些特殊的参数(GPIO等都是)一般来说无法使用。

程序指针寄存器(PC,其他的寄存器或多或少都有别的名称,如ARM的Rx,x86的AX等,但是程序指针寄存器基本上缩写都是PC,也有叫PC指针的),存放的是当前程序运行的指针。

所以说,编译器选择了程序指针寄存器作为基准指针。于是可以看到,几乎所有程序都是这样实现的,这也是成本与效率下的几乎最优解了。

不得不说,有时候这种知识完全不需要知道(因为99%的人完全不会注意到),但是通过自己查询资料知道为什么这样实现,也是蛮好玩的一件事😄。

0x50 总结

不得不说,有时候这种知识完全不需要知道(因为99%的人完全不会注意到),但是通过自己查询资料知道为什么这样实现,也是蛮好玩的一件事😄。

本文首发自 记:关于编译时为何基础偏移为PC,更多文章可进入我的博客详查。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GreenDreamer

如果帮到了你,还望请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值