我刚刚在一个小型嵌入式产品的引导加载程序中看到了一个非常有趣的C代码。
代码由两部分组成:引导加载程序和主代码。主代码地址的开头存储在函数指针中。以下是我正在谈论的代码
typedef struct {
uint32_t myOtherField;
void (*mainCodeStartAddress)(void);
} MyStruct;
MyStruct myStruct = {0x89ABCDEF, 0x12345678};
void main(void) {
// Some code
...
myStruct.mainCodeStartAddress(); // Jump to the start of the application
}
我无法弄清楚为什么会这样?为什么机器会转到应用程序代码?任何帮助表示赞赏。
答案 0 :(得分:6)
构造void (*mainCodeStartAddress)(void)
声明一个名为mainCodeStartAddress
的函数指针。它将指向一个不带参数的函数,并且不会返回任何内容。
您可以将任何相同类型的函数分配给函数指针,例如:
void some_function(void)
{
// Do something useful
}
mystruct.mainCodeStartAddress = some_function;
然后你可以这样称呼它:
mystruct.mainCodeStartAddress(); // Same as calling some_function();
在您的示例中,为函数指针分配了一个固定地址,因此当您调用该函数时,它指向它跳转到该地址并执行它。
答案 1 :(得分:1)
如果应用程序代码是使用起始地址0x12345678
构建的(我认为你已经做了一个不太可能的地址用于说明?),那么引导加载程序只需要跳转到该起始地址即可启动应用程序。这可以通过在C代码中实现,通过将特定于该地址的函数指针设置为先验已知的起始地址值并通过该函数指针发出调用。任何类型的指针只是一个内存地址,函数指针是某些代码的地址,对void函数的调用很简单,只需调用某个地址即可。
这是否由跳转,调用或分支指令实现,这取决于体系结构和指令集;但在这种情况下,这无关紧要。起始地址不是"功能"也不重要。因此,由于此函数永远不会返回 - 单独编译和链接的应用程序代码将运行它自己的C运行时启动并建立新的堆栈和静态初始化,从而破坏引导加载程序的运行时环境。所需要的只是处理器的程序计数器被设置为特定地址,这是通过指针调用该函数来实现的。例如,它可以通过以下方式实现:
typedef void(*tStartAddress)(void);
((tStartAddress)(0x12345678)() ; // Set PC to start address