1

我正在使用 XC8 编译器。为此,您必须定义自己的void putch(char data)函数才能使函数正常工作,如此printf()所述。基本上,是用于将字符写入.putch()stdout

我现在想即时更改此功能。我有两个不同的功能,putch_a()并且putch_b()希望能够putch()即时更改哪个用于自己。

我想到了这个:

unsigned use_a_not_b;
void putch(char data) {
   if (use_a_not_b) {
      putch_a(data);
   } else {
      putch_b(data);
   }
}

但是,这会降低执行速度。有没有办法为此使用指针?我已阅读此答案,并编写了以下代码:

void putch_a(char data);
void putch_b(char data);

void (*putch)(char) = putch_a; // to switch to putch_a
void (*putch)(char) = putch_b; // to switch to putch_b

那行得通吗?有没有更快或更实用的方法?

4

3 回答 3

2

不,为什么不

回答你的问题:不,你不能,按照你的想法(即函数指针)。函数指针是一个具有另一个变量地址的变量。为了说明这一点,请考虑当您有一个foo指向 function的函数指针时它是如何工作的bar

int bar() {
}

void baz(int (*foo)()) {
   int x = foo();     // Calls the function pointed to bar foo
}

int main() {
   int (*foo)();
   foo = &bar;
   baz(foo);    // Cal baz() passing it foo, which points to bar()
}

foo持有的是 的地址bar。当您传递foo给某个需要函数指针参数的函数(在本例中baz())时,该函数取消引用指针,即查看与 关联的内存地址,获取其中存储的地址,foo在我们的示例中为在该地址bar调用一个函数(在我们的例子中, )。bar对此要非常小心:在上面的例子中baz()

  • 让我看看与之相关的内存foo,它里面还有另一个地址
  • 从内存中加载该地址,并在该地址调用一个函数。该函数返回一个int并且不带参数。

让我们将其与直接调用的函数进行对比bar()

void qux() {
   int x = bar();     // Call bar()
}

在这种情况下,没有函数指针。有一个地址,由链接器提供。链接器列出程序中的所有函数,并且它知道,例如bar()在 address 0xDEADBEEF。所以qux()里面只有一个jump 0xDEADBEEF电话。相比之下,在baz() 中有类似(伪加法)的东西:

pop bar off the stack into register A
read memory address pointed to by register A into register B
jump to memory location pointed to by register B

例如,putch()调用 from的方式与calls完全一样,而不像这样做的方式:静态链接到您的程序中,因此其中的地址是硬编码的,很简单,因为不需要函数指针来调用 a范围。printf()qux()bar()baz()putchputch()fprintf()

为什么#define没有答案

#define是一个预处理器指令,也就是说,#define在编译器看到你的代码之前,用它们定义的“符号”被替换为它们的值。这意味着#define使您的程序的动态修改更少而不是更多。在某些情况下这是可取的,但在您的情况下,它对您没有帮助。为了说明您是否定义了这样的符号:

#define Pi 3.14

然后,无论您在哪里使用Pi它,就好像您键入了3.14. 因为Pi不存在,就编译器而言,您甚至无法获取它的地址来指向它。

最接近动态的putch

就像其他人所说的那样,您可以有某种 case 语句、条件语句或全局指针,但putch函数本身必须以相同的形式存在。

全局函数指针解决方案:

   void (*myPutch)(char);

   putch(char ch) {
       myPutch(ch);
   }

   int main() {
       myPutch = putch_Type_A();

       ...

       myPutch = putch_Type_B();

   }

If/then/else 解决方案已在其他答案中提供

goto 解决方案:这将是一个丑陋的(但很有趣!)hack,并且只能在冯诺依曼型机器上实现,但在这些情况下,你可以putch看起来像这样:

   putch(char ch) {
       goto PutchTypeB
   PutchTypeA:
       // Code goes here
       return; 
   PutchTypeB:
       // Code goes here
       return; 

   }

然后,您将goto使用 goto 将指令覆盖到其他内存地址。您必须弄清楚执行此操作的操作码(可能来自反汇编),而这在哈佛架构机器上是不可能的,所以它在 AVR 处理器上可用,但如果笨拙的话,它会很有趣。

于 2013-05-07T06:21:01.327 回答
1

在优化它之前,请确保您真正拥有的东西确实会显着降低执行速度。在 i/o 函数中,通常会发生很多其他事情(检查缓冲区空间是否空闲、计算缓冲区偏移量、通知硬件数据可用、在数据实际传输到硬件时被中断等),这会使单个额外的 if/else 无关紧要。

在大多数情况下,您的第一个块应该没问题。

在评论中,您提到可能需要将此结构扩展到多个 putch() 函数。

也许试试

enum PUTCH { sel_putch_a, sel_putch_b, ... };

enum PUTCH putch_select;

void putch(char c) {
    switch(putch_select) {
        case sel_putch_a : putch_a(c); break;
        case sel_putch_b : putch_b(c); break;
        /* ... */
    }
}

编译器应该能够将 switch 语句优化为简单的计算和 goto。如果putch_<n>函数是可内联的,这甚至不需要额外的调用/返回。

在另一个答案中使用指向函数的解决方案在能够动态更改可用的 putch 函数或在其他文件中定义它们(例如,如果您正在编写一个库或框架来被其他人使用),但它确实需要额外的调用/返回开销(与仅定义单个 putch 函数的简单情况相比)。

于 2013-05-05T14:52:23.040 回答
1

不。由于代码的生成和链接方式,这不能保证有效。然而...

void (*output_function)(char) = putch_a;

void putch(char c) {
    output_function(c);
}

现在您可以随时更改 output_function...

C中没有“速度”的概念。这是实现引入的属性。有快速实现(或者更确切地说,在“编译器”的情况下产生快速代码的实现)和慢速实现(或产生慢代码的实现)。

无论哪种方式,这都不太可能成为重大瓶颈。生成一个解决有用程序的程序,对其进行分析以确定最重要的瓶颈并努力优化这些瓶颈。

于 2013-05-05T08:07:48.720 回答