keil编译C51代码出现“error C212: indirect call: parameters do not fit within registers”错误的原因及解决方法
原文链接:https://www.cnblogs.com/jqdy/p/14476285.html
代码中用到了函数指针,该函数指针有一个参数,参数类型也是一个指针,见下面代码片段的第5行红色部分。使用Keil编译时出现了“error C212: indirect call: parameters do not fit within registers”错误。
1 //代码片段 2 typedef struct thisDisplay{ 3 unsigned char *buffer; 4 //其他属性和方法。。。。 5 void(* Updata)(struct thisDisplay*, unsigned char *);//一次性更新缓冲区所有内容 6 }Display_c; 7 8 unsigned char buffer[10]; 9 10 Display_c xdata g_Display = 11 { 12 buffer, 13 //其他属性方法的初始化。。。。 14 DisplayClass_Updata 15 }; 16 17 static void DisplayClass_Updata(Display_c* _this, unsigned char* p) 18 { 19 unsigned char i = 0; 20 do{ 21 _this->buffer[i] = *p; 22 }while (++i < DISPLAY_BUFFER_SIZE); 23 } 24 25 //以上为类定义部分,下面为该类的一个实例,执行updata方法 26 g_Display.Updata(&g_Display, pStr); 27 28 //编译时出现的错误提示,指向第26行的代码 29 //error C212: indirect call: parameters do not fit within registers
Keil官网中有一篇文章详细解释了该问题。解决方法有两种,一种是将这个函数指针说明为可重入函数(reentrant);另一种是根据参数指针指向的存储空间类型,给该参数指针加上相应的类型限制,以便编译器正确翻译成相应的汇编代码。
经过尝试,这两种方法都可解决该问题。
但是,方法一会导致另一种错误,并且错误的位置不固定,去掉几条代码编译时指示某个函数错误,加上几条代码又指示另一个函数错误,反复检查代码又不存在问题。错误提示为:error l121: improper fixup。检索后大概原因是将函数标记位 reentrant 后,会导致调用函数时将更多的信息压入栈区,而8051的栈空间过小,从而导致该错误。
方法二是个不错的选择,如果该参数指针指向 xdata 区,只需要在函数声明时,给该指针参数前加上 xdata 修饰即可。上面代码片段第5行相应位置添加 xdata 即可:
void(* Updata)(struct thisDisplay xdata *, unsigned char xdata *);//一次性更新缓冲区所有内容
Mark:又发现当函数指针的参数是 bit 类型时也会出现这种错误提示,将 bit 类型改成 unsigned char 类型即可解决该问题。有兴趣可以读一读 《C51/Cx51编译器用户指南》第162、163页的 Parameter Passing in Registers 和 Parameter Passing in Fixed Memory Locations。
下面是该文章的原文,对应的翻译标记为黄色。
C51: Passing Parameters to Indirectly Called Functions
C51:向间接调用函数传递参数
Information in this article applies to:本文信息适用于C51和Cx51所有版本
- C51 All Versions
- Cx51 All Versions
SYMPTOMS 现象
I'm using function pointers and object-oriented programming techniques in my application. Most of the time my program works as expected. But when I try to pass several parameters to functions that are called via pointers, I get the following compiler error message:
我使用面向对象技术使用了函数指针。大多数时间程序运行正常。但是,当试图向函数传递几个指针类型的参数时,编译出现以下错误信息:
Error 212: Indirect call: Parameters do not fit within registers.
The program example below demonstrates this:举例说明:
1 void (*CallBack1) (void *, unsigned char); 2 void (*CallBack2) (void *, void *); 3 void (*CallBack3) (char, char, char); 4 void (*CallBack4) (char, char, char, char); 5 6 unsigned char c, d, e, f; 7 char *ptr; 8 9 void test (void) { 10 CallBack1 (ptr, c); // works 11 CallBack2 (ptr, ptr); // fails - C51 generates an error message 12 // indirect call: parameters do not fit within 13 registers */ 14 CallBack3 (c, d, e); // works 15 CallBack4 (c, d, e, f); // fails - C51 generates an error message 16 // indirect call: parameters do not fit within registers */ 17 }
CAUSE 原因
Unlike most 16-bit and 32-bit microcontrollers, the 8051 is not a stack based architecture. When parameters do not fit into the CPU registers, the Keil Cx51 Compiler by default uses direct memory locations for parameter passing. This technique generates very efficient code but limits the parameters that can be passed to indirectly called functions. When parameters passed to a function via a function pointer will not fit into registers, the compiler cannot determine where in memory to place the parameters since the function is not known at call-time.
与大多数16位和32位微控制器不同,8051不是基于堆栈的体系结构。当参数不适合CPU寄存器时,Keil Cx51编译器默认使用直接内存定位的方式传递参数。这种技术生成高效的代码,但限制了传递给间接调用函数的参数。当通过函数指针传递给函数的参数无法装入寄存器时,编译器无法确定在内存中放置参数的位置,因为函数在调用时是未知的。
RESOLUTION 解决方法
There are two ways to solve your programming problem.
有两种解决办法。
-
Create reentrant functions using the reentrant function attribute. The compiler simulates a stack-based architecture which makes it possible to pass a virtually unlimited number of parameters to indirectly called functions. For example:
使用可重入函数属性 reentrant 创建可重入函数。编译器模拟了一种基于堆栈的体系结构,它可以将几乎无限数量的参数传递给间接调用的函数。例如:
1 void (*CallBack1) (void *, unsigned char); 2 void (*CallBack2) (void *, void *) reentrant; 3 void (*CallBack3) (char, char, char); 4 void (*CallBack4) (char, char, char, char) reentrant; 5 6 unsigned char c, d, e, f; 7 char *ptr; 8 void test (void) { 9 CallBack1 (ptr, c); // works 10 CallBack2 (ptr, ptr); // works, but now the function that gets called 11 // need to have the reentrant attribute 12 CallBack3 (c, d, e); // works 13 CallBack4 (c, d, e, f); // works, but now the function that gets called 14 // need to have the reentrant attribute 15 }
2. Limit the number and types of parameters so that they all fit into CPU registers. Do this when you need utmost performance or when program size is critical. For example:
限制参数的数量和类型,使它们都适合CPU寄存器。当您需要最佳性能或程序大小非常重要时,请执行此操作。例如:
1 void (*CallBack1) (void *, unsigned char); 2 void (*CallBack2) (void xdata *, void xdata *); 3 void (*CallBack3) (char, char, char); 4 void (*CallBack4) (char, char, int); 5 6 unsigned char c, d, e, f; 7 char xdata *ptr; 8 9 void test (void) { 10 CallBack1 (ptr, c); // works 11 CallBack2 (ptr, ptr); // works, but pointers are memory typed now 12 CallBack3 (c, d, e); // works 13 CallBack4 (c, d, e | (f<<8)); // works, but two chars are packed into 14 // one int parameter 15 }
The parameter passing method is described in the C51/Cx51 Compiler User's Guide. Refer to this to determine how to change your function parameters to fit into registers.
《C51/Cx51编译器用户指南》中描述了参数传递方法。请参阅此项以确定如何更改函数参数以适应寄存器。