在GCC中实现编译时只读函数指针表

时间:2013-11-07 20:07:21

标签: c gcc function-pointers avr

我想实现一种简单的方法来声明/定义应该在编译时添加到R / O存储器中的函数指针表(特别是使用GCC的AVR设备上的程序闪存)的函数,同时还具有默认值放在所有未使用的条目中的函数指针。例如,如果我有32个可能的条目,则以下内容:

DEFAULTFUNC
void default_handler(...)
{
   ...
}

FUNC(28)
void handle_foo(...)
{
   ...
}

会将handle_foo的指针放在函数表的元素28中,同时将default_handler放入另一个31中。

我已经看过avr-libc如何为中断向量实现ISR(),但它似乎依赖于我尚未找到的将函数指针放在{{1}内的一些内部GCC行为}} 分割。如何在代码中模仿它,以便在.vectors段中创建适当的函数指针表?

3 个答案:

答案 0 :(得分:2)

http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html讨论将变量放入特定的链接器部分。他们给出了一个示例,用于将uart结构放置在可能已配置为硬件地址(名为DUART_A)的部分:

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };

在您描述的情况下,我相信您只需将现有的只读部分命名为将表格放置在那里找到空间的位置。

答案 1 :(得分:1)

您可能正在构建嵌入式软件,例如:一些Linux PC(否则你应该)。然后你可能使用make或其他一些建设者。

然后你可以生成一个C文件(使用旧的m4GPP或你最喜欢的Python脚本,awk或其他......)。然后将生成的文件(可能由#include - 在某处)提供给C编译器。

生成的C代码(可能是C宏调用的序列)可以静态构建(在C编译时)您的只读函数表。

否则,请使用例如扩展您的GCC一些用MELT编码的自定义,以实现神奇的效果。在你的特定情况下,我认为这不值得(但我可能是错的),因为在你的情况下生成一些C代码的某些部分是如此简单....

答案 2 :(得分:1)

我可能会迟到几年,但这个问题激起了我的兴趣,所以我用一些C代码,一些__attribute__和链接器对弱符号的支持解决了它( 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_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();
  }
}

Testcase 2定义了自己的处理程序:

/* testcase-2.c */
void handler_default(void)
{ ... }
void handler_foo(void)
{ ... }

链接两个测试用例:

  1. testcase-1: testcase-main.o handler-table.o

    仅使用默认处理程序。

  2. testcase-2: testcase-main.o handler-table.o testcase-2.o

    仅使用testcase-2.c

  3. 提供的处理程序

    一些评论:

    • 这是一个链接时实现,而不是编译时实现。

    • 这可以通过定义具有弱符号的所有处理程序来实现 其他目标文件可以实现处理程序符号,从而覆盖 弱势的象征。对于默认处理程序,我们使用虚拟处理程序 它只调用默认处理程序的弱符号。

      虚拟处理程序的堆栈框架和代码显然被浪费了, 如果需要,可以通过内联汇编轻松替换 避免周期和空间。

    • 我还没有在实际的AVR MCU上测试过这种情况,只能在我的电脑上进行测试。 我非常有信心我正确使用PROGMEMpgm_read_word_near,但是。{/ p>

    • 如果您错误输入了已覆盖的处理函数的名称, 没有任何编译,它将不会进入处理程序表 时间或链接时间警告或错误。

      我建议在预处理器中添加一个间接层 用于处理函数名称的宏,以便获得 在拼写错误的情况下编译时错误。

      这个宏层可能也会为代码派上用场 需要从表中调用特定的处理程序,因此 需要知道表中的索引。

      另一方面......在某些情况下,你可能不需要 首先是函数表,只是一些弱函数 可能被非弱函数覆盖可以解决你的问题 没有桌子的问题。