为什么要学习汇编代码二(优雅编写高质量的代码)

背景

本文章代码运行的对象是cortex M0的处理器,编译器是armcc 5,本文章是一个小系列可以观看之前的问题。
文章转载请标注来自于:原作者

问题引入

笔者是芯片原厂的软件开发的组长,由于长时间和一些运用我们芯片做方案的方案商的开发人员接触,发现做方案的开发人员对汇编代码和芯片本身的理解比较欠缺。由于这些欠缺,可以很明显的看出在解决一些隐晦的bug和代码编写的性能方面显得非常吃力。为什么学习汇编代码是一个小系列,本文以汇编为路线详细阐释芯片的启动的流程。本文推荐阅读的书籍
arm 官方的文档
上述的文档,可以在arm官网获得,也可以通过keil的book获得。

keil books
上图通过keil的开发工具找到的。

函数参数的选择

这个问题看上去好像没有什么可说的,但是我引入一段armcc 编译解释的内容:
在这里插入图片描述
如上图是优化函数参数的方法,来避免函数参数过头。

避免函数参数超出4个参数,如果每个参数是一个字或者更小的情况下

c源码:

uint32_t add_less_four_param_function(uint8_t a,uint16_t b,uint16_t c,uint32_t d)
{
	uint32_t sum = a + b + c + d;
	return sum;
}

汇编代码:
在这里插入图片描述
如上图:我们很容易看懂r0,r1,r2,r3,对应都是就是函数的参数a,b,c,d。函数的返回值就是R0。这儿我总结一下,arm cc 编译器编译cortex M0的代码,r0,r1,r2,r3一般作为函数的参数,r0是函数的返回值。那么我们不禁要问了,超过4个参数会出现什么情况呢?看下图

c语言

uint32_t over_less_four_param_function(uint8_t a,uint16_t b,uint16_t c,uint32_t d,uint8_t f)
{
	uint32_t sum = a + b + c + d + f;
	return sum;
}

汇编代码
在这里插入图片描述
函数的参数是r0,r1,r2,r3,sp + 8,分别对应a,b,c,d,f。函数入口保存了r4,lr的值,主要原因是r4的值发生修改了。至于r4的值修改了为什么就需要保存,这个是一个约定的策略,后面的教程在详细解释。我们可以看出来,如果函数的参数超过4个就需要栈进行辅助,这会导致如下几个消耗:
1、需要额外的指令把栈读取的寄存器。
2、需要额外的指令把值放到栈上面。
3、增加了栈的开销

如果的确需要超过4个参数,请保证这个函数完成了一个比较高效的内容,来抵消因为栈的额外消耗的缺点。

这表明不是一定不能超过4个参数,还是需要看函数执行的内容。

请把把相关的参数放在一个结构体,然后用指针的方式进行调用。

这个方式也是我们非常常用的一种方式。

struct __vendor_server_t
{
    /** Model handle assigned to this instance */
    access_model_handle_t model_handle;
    /** Tid tracker structure */
    tid_tracker_t tid_tracker;
    /** Model settings and callbacks for this instance */
    vendor_server_settings_t settings;
};
void app_vendor_on_connected_report(vendor_server_t * p_self)
{
   //connected with client then report device states
   //user defined here
   __MESH_LOG(LOG_SRC_APP,"vendor on connected\n");
   
}

指针p_self ,一个R0寄存器就可以表示,这样很多个变量只用了一个寄存器进行表示了。指针也是一个变量占4个字节。

减少long 的参数应用,其实不只是long,大于一个字的参数都需要更多的寄存器来表示。

long over_words_param_function(uint64_t a,long b)
{
	long sum = a + b;
	return sum;
}

在这里插入图片描述
看上去好简洁,这个需要感谢编译器的强大,至少我们能看到r1,好像没用到。但是r1真的没用到么?
其实r0,r1,是a参数,r2,r3是b参数。之所以出现了这样的优化,是由于a,b我传入的是一个常量。

printf("%ld",over_words_param_function(1,2));

由于结果不可能发生变化,就被编译器给优化了,即使优化了也能看出来r1不能被使用了。这告诉我们如果我们在加一个参数就需要栈进行辅助,这个可以对比第一条进行反思。

软件的浮点数,少用double 参数

避免使用可变参数的函数,因为函数需要把所有的参数都要放到栈上。

在这里插入图片描述
printf 函数用到了非常大的栈开销。

进阶问题

下一章详细讨论,如何通过汇编代码优化flash的空间,某些场景下如何抉择。
关注作者:持续更新

总结

这一章详细解释函数的参数的原理,以及如何优化参数达到比较好的效力。比如你看到别人的参数超过4个或者类似的问题,你就能判断代码好不好。本文最重要的是介绍了函数对应汇编的思维

猜你喜欢

转载自blog.csdn.net/XG_2013/article/details/107513673