我可能晚了几年,但是这个问题引起了我的兴趣,所以我用一点 C 代码、几个__attribute__
s 和链接器对弱符号的支持来解决它(avr-libc
对于向量表基本上是一样的,但是在汇编代码中)。
简单的最小示例在此答案中,但我已在https://github.com/ndim/handler-function-table提供了稍微扩展的版本。
处理程序表接口:
/* handler_table.h */
#ifndef HANDLER_TABLE_H
#define HANDLER_TABLE_H
#include <avr/pgmspace.h>
#define HANDLER_MAX 2
typedef void (*handler_func)(void);
extern const handler_func handler_table_P[HANDLER_MAX] PROGMEM;
#endif /* HANDLER_TABLE_H */
handler表的实际实现:
/* handler_table.c */
#include "handler_table.h"
void handler_foo(void) __attribute__((weak, alias("__handler_default")));
void handler_bar(void) __attribute__((weak, alias("__handler_default")));
const handler_func handler_talbe_P[HANDLER_MAX] PROGMEM = {
handler_foo,
handler_bar
};
__attribute__((weak))
void handler_default(void)
{
}
void __handler_default(void)
{
handler_default();
}
测试用例主程序:
/* testcase-main.c */
#include "handler-table.h"
int main()
{
for (unsigned int i=0; i<HANDLER_MAX; ++i) {
const uint16_t func_addr = pgm_read_word_near(handler_table_P[i]);
const handler_func func = (handler_func) addr;
func();
}
}
测试用例 2 定义了自己的处理程序:
/* testcase-2.c */
void handler_default(void)
{ ... }
void handler_foo(void)
{ ... }
链接两个测试用例:
testcase-1: testcase-main.o handler-table.o
仅使用默认处理程序。
testcase-2: testcase-main.o handler-table.o testcase-2.o
仅使用 testcase-2.c 提供的处理程序
几点说明:
这是链接时实现,而不是编译时实现。
这通过使用弱符号定义所有处理程序来工作,以便任何其他目标文件可以实现处理程序符号并因此覆盖弱符号。对于默认处理程序,我们使用一个虚拟处理程序,它只为默认处理程序调用弱符号。
虚拟处理程序的堆栈帧和代码显然是浪费的,如果您需要避免循环和占用的空间,可以很容易地通过内联汇编中的跳转来替换。
我没有在实际的 AVR MCU 上测试过这个,只是在我的 PC 上进行了测试。不过,我非常有信心正确使用PROGMEM
and
pgm_read_word_near
。
如果您错误键入了已覆盖的处理程序函数的名称,则不会在没有任何编译时间或链接时间警告或错误的情况下将其输入处理程序表。
我建议在预处理器宏中为处理函数的名称添加一层间接层,以便在出现拼写错误时得到编译时错误。
对于需要从表中调用特定处理程序并因此需要知道其在表中的索引的代码,这个宏层可能也会派上用场。
另一方面......在某些情况下,您可能一开始就不需要函数表,只有一些弱函数可能被非弱函数覆盖,就可以在没有表的情况下解决您的问题。