【问题标题】:Is it possible to swap C functions?是否可以交换 C 函数?
【发布时间】:2013-05-16 05:36:56
【问题描述】:

想看看是否有人知道是否可以交换 C 函数...?

 void swap2(int(*a)(int), int(*b)(int)) {
   int(*temp)(int) = a;
   *a = *b;
   *b = temp;
   // Gives 'Non-object type 'int (int)' is not assignable
 }

 swap2(&funcA, &funcB);

编辑

这里有更多关于意图的数据——下面提供了一些答案工作,例如使用typedef创建函数ptr,将它们指向函数并切换它们,这让你成功调用新交换的ptrs。

BUT 在交换后按其原始名称调用函数显示没有变化。本质上,我正在寻找 objc “swizzle”的 c 等价物。

我开始认为这是不可能的,因为 c 完全没有反射,并且需要实际修改二进制文件本身(显然不可行)。 D:

欢迎评论。

【问题讨论】:

  • *b in *b=temp 完全是荒谬。你可以取消引用一个函数指针来调用它。
  • *b 不是一个有效的左值开始。
  • @Thokchom -- 虽然该评论似乎主要是批评性的而不是建设性的,但重点仍然是您是正确的。该函数是对用于其他数据类型的通用指针交换的改编,显然是不正确的,因此我的问题首先在这里**见编辑**
  • 不,不,你误会我了。我为什么要批评你?我只是在谈论那个作业。我用粗体写了“荒谬”以引起你的注意。干杯! !
  • @MilesAlden 关于编辑部分:能够在运行时交换函数需要运行时决定调用哪个函数。有些语言可能有关于它的语法糖,但它总是归结为对调用什么的某种查找(或在调用函数的每个地方修改二进制文件,这通常甚至被操作系统禁止)。在 C 中,如果你想拥有这个,你需要明确地做到这一点,而函数指针是最直接的方式(但不是唯一的方式)。 IOW,在 C 中,您必须在需要的地方自己实现反射功能。

标签: c pointers function-pointers


【解决方案1】:

如果你使用下面这样的函数指针,是的

typedef int (*func_pt)(int);

func_pt a, b;

void swap(func_pt * a, func_pt * b)
{
    func_pt tmp = *b;
    *b = *a;
    *a = tmp;
}

swap(&a, &b);

或者你这样用,我觉得不是:

int test1(int a)
{
    return a;
}

int test2(int b)
{
    return b;
}

swap(&test1, &test2);

完整的编译工作程序

#include <stdio.h>
#include <stdlib.h>

typedef int (* func_pt)(int);

func_pt a, b;

int test1(int a)
{
    printf("test1\n");
    return 1;
}

int test2(int a)
{
    printf("test2\n");
    return 2;
}

void swap(func_pt * a, func_pt * b)
{
    func_pt tmp = *b;

    *b = *a;
    *a = tmp;
}

int main(void)
{
    a = &test1;
    b = &test2;

    printf("before\n");
    a(1);
    b(1);

    swap(&a, &b);

    printf("after\n");
    a(1);
    b(2);

    return 0;

}

输出:

before
test1
test2
after
test2
test1

有些人自己不去尝试,就说很荒唐。所以我给你举个例子。

【讨论】:

  • 这确实像你描述的那样工作,可以交换 typedef'd ptrs,如果你调用它们,它们就会被翻转;但是我对第二个例子更感兴趣。我本质上是在为常规 c 函数寻找在 Objective-c 中进行调配的等价物。这里我们调用这些指针,但如果我调用 test1(1),它仍然调用 test1() 而不是 test2()。有什么想法吗?
  • @MilesAlden:函数名称是“地址常量”。标准 C 不允许您更改它们,并且如果您设法做到了,则可以让您的计算机着火。如果你想随意切换实现,你必须有代表它们中的每一个的函数指针,并且总是通过这些指针调用函数。不过,这实际上扼杀了许多形式的优化。
  • "...允许让您的计算机着火..." 只是对那个大声笑了起来,谢谢。 :-) 是的,这就是我在这里收集的内容。
  • @cHao 说 UB 如何让编译器着火你的编译器很有趣,但它太不准确了,它已经过时了。实际上它只是不禁止编译器这样做,但 C 标准没有权限允许,即使它想要。标准并不是编译器必须遵守的唯一标准。此外,该标准并没有禁止标准代码产生过多热量并使您的计算机着火,这仅仅是因为编译器无法控制热量产生。
  • @hyde:你忘记了 标签。 :P 关键是,C 本身对结果行为没有任何限制。您的计算机制造商和美国消费品安全委员会是防止火灾发生的原因。
【解决方案2】:

我很确定您需要指向函数指针的指针来交换指针,不是吗?这种类型的交换函数交换值;你真的想处理地址。示例函数调用不会真正起作用,因为 C 不将函数视为一等变量,因此您实际上不能直接交换函数;您需要使用指向函数地址的指针,因为地址可以交换:

void swap2(int(**a)(int), int(**b)(int)) {
   int(*temp)(int) = *a;
   *a = *b;
   *b = *temp;
}

int(*func1)(int) = &foo;
int(*func2)(int) = &bar;

swap2(&func1, &func2);

【讨论】:

  • 这在函数范围内有效,但不会改变实际的函数指针地址。打印前后的函数指针就知道了。
  • 就像您可以调用 func1() 和 func2(),它们现在已交换,但调用 foo() 和 bar() 显示它们不受影响。
  • 函数地址不变;在正确的条件下,如果代码段实际上是可写的,或者做了很多汇编级别的魔法,并且如果星星对齐,你就不会在正确的条件下在 C 中移动代码而不需要更多的努力。电脑:D。
【解决方案3】:

您的代码在赋值时会出现“invalid lvalue”之类的错误。正如我在您的代码中看到的那样,您正在尝试在不更改其值的情况下交换指针,因此请查看以下解决方案。

void swap2(int(**a)(int), int(**b)(int)) {   
   int(*temp)(int) = *a;
   *a = *b;
   *b = temp;
}

int main(){
    int(*temp1)(int) = &funcA;
    int(*temp2)(int) = &funcB;
    swap2(&temp1,&temp2);
}

【讨论】:

    【解决方案4】:

    是的,你可以。认为函数指针只是一个内存地址,唯一的要求是:保存此类地址的位置必须是可变的。说,int (*foo)() 并不是真正指向 foo 指向的位置。可能是printf()fopen()

    【讨论】:

      【解决方案5】:

      虽然主题询问交换功能,但您实际上想模仿swizzle 所做的事情。这只是意味着您希望能够调用相同的函数名称,但让它做一些不同的事情。

      只有指针的解决方案不会给你这种行为。如果这对您来说不重要,那么您应该采用仅提供的函数指针解决方案之一。如果它对您很重要,那么您将需要引入一个抽象层。抽象可以在底层使用函数指针(尽管还有其他解决方案)。

      此接口用户的 API 将是:

      /* API to initialize */
      void abstract_func_init ();
      
      /* API to manipulate abstract functions */
      typedef int abstract_func_type ();
      abstract_func_type * abstract_func_get (abstract_func_type *key);
      int abstract_func_set (abstract_func_type *key, abstract_func_type *behavior);
      
      /* the abstract functions */
      extern int foo ();
      extern int bar ();
      

      这样一个接口的实现可能如下所示:

      static void insert (abstract_func_type *key, abstract_func_type **behavior)
      { /* associate key to behavior */ }
      static abstract_func_type ** lookup (abstract_func_type *key)
      { /* return behavior from key */ }
      
      abstract_func_type * abstract_func_get (abstract_func_type *k) {
          abstract_func_type **f = lookup(k);
          if (f) return *f;
          return 0;
      }
      
      int abstract_func_set (abstract_func_type *k, abstract_func_type *p) {
          abstract_func_type **f = lookup(k);
          if (f) {
              *f = p;
              return 0;
          }
          return -ENOENT;
      }
      
      #define DEFINE_ABSTRACT_FUNC(func) \
          static int static_##func (); \
          static abstract_func_type *func##_ptr = static_##func; \
          int func () { return func##_ptr(); } \
          static int static_##func ()
      
      DEFINE_ABSTRACT_FUNC(foo) { return puts("foo"); }
      DEFINE_ABSTRACT_FUNC(bar) { return puts("bar"); }
      
      void abstract_func_init () {
          insert(foo, &foo_ptr);
          insert(bar, &bar_ptr);
      }
      

      那么,您最初在帖子中提出的swap() 可以这样实现:

      void swap (abstract_func_type *a, abstract_func_type *b) {
          abstract_func_type *ap = abstract_func_get(a);
          abstract_func_type *bp = abstract_func_get(b);
          abstract_func_set(a, bp);
          abstract_func_set(b, ap);
      }
      

      这是一个调用swap()的程序:

          puts("before swap");
          foo();
          bar();
          swap(foo, bar);
          puts("after swap");
          foo();
          bar();
      

      它的输出是:

      before swap
      foo
      bar
      after swap
      bar
      foo
      

      要自动将抽象函数添加到查找表中,您可以在构建系统中引入一个额外的步骤,该步骤调用一个脚本,该脚本将 grepDEFINE_ABSTRACT_FUNC 行中取出,并生成一个新的源文件,该文件具有对每个这样的行调用insert() 的函数。

      完整版的模型可以在here找到。

      【讨论】:

      • 是的,另一个非建设性的反对票。作为一种先发制人的响应,该解决方案在 API 和 API 的委托操作之间使用了一个抽象层。 swizzle() API 用于交换委托的操作。它给出了发布者要求的行为(现在按名称调用相同的 API 会产生不同的行为)。
      • 问题是,它没用。它需要更改呼叫的一个(或两个!)端点。这破坏了想要交换名称而不是显式使用函数指针的所有合理理由。
      • @cHao:由于 C++ 中不存在这样的特性,但行为是需要的,那么提供行为的唯一方法是在代码中引入抽象。你可以做一些事情,比如引入宏来更容易定义一个“swizzle-able”函数,但是你怎么能在不编写新代码的情况下引入一个新行为呢?
      • 不过,您似乎没有引入所需的行为。你当然已经建立了一个可爱的外观,但是(1)它在几乎所有期望的行为实际上看起来很可取的情况下都是不可用的; (2) 它没有提供函数指针提供的功能(甚至是抽象!); (3) 实施工作更多。这样做有什么好处,而不是简单地假装一个函数指针是一个函数?
      • 实际上这非常有用,感谢您编写它。本质上,您已经拼凑了 objc 运行时的一部分,它使用类似的查找操作,然后启用 swizzling。
      猜你喜欢
      • 2013-02-07
      • 2010-10-06
      • 2015-11-08
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多