Embedded 嵌入式系统中的函数指针有用吗?

Embedded 嵌入式系统中的函数指针有用吗?,embedded,functor,Embedded,Functor,在一次采访中,他们问我在为嵌入式系统编写代码时,使用函数指针是否有益(就速度而言)?我对嵌入式系统一无所知,所以无法回答这个问题。只是含糊不清的回答。 那么真正的好处是什么呢?速度、可读性、维护、成本?我会说,它们在任何环境中都是有益的(就速度而言),而不仅仅是嵌入式环境。其思想是,一旦指针指向正确的函数,就不需要进一步的决策逻辑来调用该函数。让我们看看 速度(假设我们在手臂上):然后(理论上): (正常功能调用ARM指令大小)

在一次采访中,他们问我在为嵌入式系统编写代码时,使用函数指针是否有益(就速度而言)?我对嵌入式系统一无所知,所以无法回答这个问题。只是含糊不清的回答。
那么真正的好处是什么呢?速度、可读性、维护、成本?

我会说,它们在任何环境中都是有益的(就速度而言),而不仅仅是嵌入式环境。其思想是,一旦指针指向正确的函数,就不需要进一步的决策逻辑来调用该函数。

让我们看看

速度(假设我们在手臂上):然后(理论上):

(正常功能调用ARM指令大小)<(功能指针调用设置指令大小)

由于它们是设置函数指针调用的附加间接层,因此将涉及附加的ARM指令

PS:普通函数调用:使用BL设置的函数调用


PSS:不知道它们的实际尺寸,但应该很容易验证

函数指针的一个消极方面是它们永远不会在调用站点内联。这可能有益,也可能有害,这取决于编译速度或大小。如果是后者,则它们应该与普通函数调用没有区别。

函数指针的另一个缺点(对于虚拟函数,因为它们只是核心级别的函数指针):

使函数内联&虚拟将强制编译器创建同一函数的脱机副本。这将增加最终二进制文件的大小(假设大量使用它)


经验法则:1:不要内联进行虚拟呼叫

你在速度上有所提高,但在可读性和维护上有所损失。而不是一个if-then-else树,if-a-then-fun\u a(),else-if-b-then-fun\u b(),else-if-c-then-fun\u-default(),每次都要这样做,如果a-then-fun=fun\u-a,else-if-b-then-fun=fun\u-b,等等,然后你做一次,从那时起,只要调用fun()。快得多。正如前面指出的,不能内联,这是另一个速度技巧,但是if-then-else树上的内联并不一定比没有内联的树快,而且通常没有函数指针快

你会失去一点可读性和可维护性,因为你必须弄清楚fun()的设置位置,它的变化频率(如果有),确保在设置之前不调用它,但它仍然是一个可搜索的名称,你可以使用它来查找和维护所有使用它的地方

这基本上是一种速度技巧,可以在每次执行函数时避免if-then-else树。如果性能不重要,如果没有其他功能,则fun()可以是静态的,并在其中包含If-then-else树

编辑并添加一些示例来解释我所说的内容

extern unsigned int fun1 ( unsigned int a, unsigned int b ); unsigned int (*funptr)(unsigned int, unsigned int); void have_fun ( unsigned int x, unsigned int y, unsigned int z ) { unsigned int j; funptr=fun1; j=fun1(z,5); j=funptr(y,6); } 外部无符号整数fun1(无符号整数a,无符号整数b); 无符号整数(*funptr)(无符号整数,无符号整数); void have__fun(无符号整数x、无符号整数y、无符号整数z) { 无符号整数j; funptr=fun1; j=fun1(z,5); j=funptr(y,6); } 编译给出了以下结果:

have_fun: stmfd sp!, {r3, r4, r5, lr} .save {r3, r4, r5, lr} ldr r4, .L2 mov r5, r1 mov r0, r2 mov r1, #5 ldr r2, .L2+4 str r2, [r4, #0] bl fun1 ldr r3, [r4, #0] mov r0, r5 mov r1, #6 blx r3 ldmfd sp!, {r3, r4, r5, pc} 玩得开心: stmfd sp!,{r3,r4,r5,lr} .save{r3,r4,r5,lr} ldr r4,.L2 mov r5,r1 mov r0,r2 第1节,第5节 ldr r2、.L2+4 str r2[r4,#0] bl fun1 ldr r3,[r4,#0] mov r0,r5 第1节,第6节 blx r3 ldmfd sp!,{r3,r4,r5,pc} 我想Clifford所说的是一个直接的电话 一条指令就足够了(取决于架构)

bl fun1 bl fun1 其中一个函数指针至少要花费两个

ldr r3, [r4, #0] blx r3 ldr r3,[r4,#0] blx r3 我还提到了直接和间接的区别 你承受的额外负担

在继续之前,值得一提的是内联的优点和缺点。 对于这些示例所使用的ARM,调用 约定使用r0-r3作为函数的传入参数,并使用r0 返回。因此,使用三个参数进入have_fun()表示r0-r3 有内容。对于ARM,还假设函数可以破坏 r0-r3,have_fun()需要保留输入,然后放置 r0和r1中fun1()的两个输入,因此发生了一点寄存器舞蹈

mov r5, r1 mov r0, r2 mov r1, #5 ldr r2, .L2+4 str r2, [r4, #0] bl fun1 mov r5,r1 mov r0,r2 第1节,第5节 ldr r2、.L2+4 str r2[r4,#0] bl fun1 编译器非常聪明,可以看出我们从不需要第一个 输入have_fun()函数,因此r0被丢弃并允许 马上换。而且,编译器也足够聪明,知道 在发送之后,我们永远不需要第三个参数z(r2) 它在第一次调用时被转换为fun1(),因此它不需要将其保存在高位 登记R1但是,第二个参数需要 被保留,以便将其放入一个不会被fun1()破坏的regsiter中

您可以看到第二个函数调用也会发生同样的情况

假设fun1()是一个简单的函数:

inline unsigned int fun1 ( unsigned int a, unsigned int b ) { return(a+b); } 内联无符号整数fun1(无符号整数a、无符号整数b) { 返回(a+b); } 当您内联fun1()时,会得到如下结果:

stmfd sp!, {r4, lr} mov r0, r1 mov r1, #6 add r4, r2, #5
typedef struct
{
  uint16_t counter;
  void (*callback)(void);

} Timer_t;
stmfd sp!,{r4,lr} mov r0,r1 第1节,第6节 加上r4、r2和#5 编译器不需要洗牌即将开始的较低寄存器 准备打电话。同样,您可能已经注意到r4和 当我们输入hello_fun()时,lr将保留在堆栈上。用这个 ARM调用约定函数可以破坏r0-r3,但必须保留 所有其他寄存器,因为本例中的have_fun()需要更多 四个寄存器完成它的工作,它将r4的内容保存在 堆叠以便它可以使用它。同样,这个函数也是我编译的 它确实调用了另一个函数,bl/blx指令使用/销毁 lr寄存器(r14),所以为了让have_fun()返回,我们还需要 在堆栈上保留lr。fun1()的简化示例没有 没有显示这一点,但是从内联中得到的另一个节省是在en上 if(fputype==FPU_FPA) fdivide=fdivide_fpa; else if(fputype==FPU_VFP) fdivide=fdivide_vfp; else fdivide=fdivide_soft; if(fputype==FPU_FPA) fdivide=fdivide_fpa; else if(fputype==FPU_VFP) fdivide=fdivide_vfp; else fdivide=fdivide_soft; a=fdivide(b,c); if(fputype==FPU_FPA) a=fdivide_fpa(b,c); else if(fputype==FPU_VFP) a=fdivide_vfp(b,c); else a=fdivide_soft(b,c); unsigned int fun1 ( unsigned int a, unsigned int b ); unsigned int fun2 ( unsigned int a, unsigned int b ); unsigned int fun3 ( unsigned int a, unsigned int b ); unsigned int (*funptr)(unsigned int, unsigned int); unsigned int have_fun ( unsigned int x, unsigned int y, unsigned int z ) { unsigned int j; switch(x) { default: case 1: j=fun1(y,z); break; case 2: j=fun2(y,z); break; case 3: j=fun3(y,z); break; } return(j); } unsigned int more_fun ( unsigned int x, unsigned int y, unsigned int z ) { unsigned int j; j=funptr(y,z); return(j); } cmp r0, #2 beq .L3 cmp r0, #3 beq .L4 mov r0, r1 mov r1, r2 b fun1 .L3: mov r0, r1 mov r1, r2 b fun2 .L4: mov r0, r1 mov r1, r2 b fun3 mov r0, r1 ldr r3, .L7 mov r1, r2 blx r3 unsigned int fun1 ( unsigned int a, unsigned int b ) { return(a+b); } unsigned int fun2 ( unsigned int a, unsigned int b ) { return(a-b); } unsigned int fun3 ( unsigned int a, unsigned int b ) { return(a&b); } unsigned int have_fun ( unsigned int x, unsigned int y, unsigned int z ) { unsigned int j; switch(x) { default: case 1: j=fun1(y,z); break; case 2: j=fun2(y,z); break; case 3: j=fun3(y,z); break; } return(j); } have_fun: cmp r0, #2 rsbeq r0, r2, r1 bxeq lr cmp r0, #3 addne r0, r2, r1 andeq r0, r2, r1 bx lr cmp r0, #2 beq .L3 cmp r0, #3 beq .L4 and r0,r1,r2 bx lr .L3: sub r0,r1,r2 bx lr .L4: add r0,r1,r2 bx lr mov r0, r1 ldr r1, .L7 ldr r3,[r1] mov r1, r2 blx r3
typedef struct
{
  uint16_t counter;
  void (*callback)(void);

} Timer_t;