函数返回自身

33

是否可以声明一些函数类型func_t,使其返回该类型func_t

换句话说,一个函数是否能够返回自身?

// func_t is declared as some sort of function pointer
func_t foo(void *arg)
{
  return &foo;
}

或者我必须使用 void * 并进行类型转换吗?


2
@Baratong 想象一下实现一个状态机,其中每个状态都由一个函数表示。该函数在处理状态时返回一个带有新状态的函数。这将需要声明一个返回其自身类型的函数,在 C 中这是不可能的(据我所知)。 - user4815162342
@user4815162342 可以的,详情请看我的回答。 - Roland
10个回答

18

使用结构体的可能解决方案:

struct func_wrap
{
    struct func_wrap (*func)(void);
};

struct func_wrap func_test(void)
{
    struct func_wrap self;

    self.func = func_test;
    return self;
}

使用gcc -Wall编译时没有警告,但我不确定是否100%可移植。


2
使用 -pedantic 编译来警告使用非标准特性。 - Chris
1
@Chris使用gcc -Wall -Werror -Wextra -pedantic -c func.c -std=c89编译func.c没有问题。 - panzi
3
太棒了,完全没有任何类型转换!这应该是首选答案,因为它不仅提供了解释,还提供了一个合适的解决方案。 我喜欢 C++ 的函数对象传递方式(有点类似...)! - Ingmar

18

不,您不能在C语言中声明递归函数类型。除了在结构体(或联合体)中,C语言并不支持声明递归类型。

至于使用 void * 的解决方案,void * 仅保证保存对象指针而非函数指针。将函数指针转换为 void * 只作为一种扩展提供。


7
就算价值不高,对于通常的“带有函数指针用于状态机推进”的设置,您可以拥有一个返回结构体 state_machine 对象的函数,其中包含一个指向函数的指针,类型为 struct state_machine (*fp)(args)。然后,函数 foo 可以使用以下代码“返回自身”:struct state_machine ret; ret.fp = foo; ... return ret; - torek
请注意,尽管 C 语言不要求 void* 可转换为函数指针,但 POSIX 要求 - 请参阅 dlsym(3) - Adam Rosenfield
3
如何将所有函数指针类型都转换为双向可转换? - Deduplicator
你如何称呼这个函数 void (*the_most_pointless_function_in_the_world(void))(void){ return the_most_pointless_function_in_the_world; }?这个函数有趣的地方在于它确实返回了自己,调用 the_most_pointless_function_in_the_world(); 会返回这个函数,如果把它赋值给一个变量,那么这个变量也可以被调用,但是 the_most_pointless_function_in_the_world()(); 会导致编译错误。我想我把C语言玩过头了... - Braden Best
@torek 一个非常好的答案:https://gcc.godbolt.org/z/27vDj3。我以为在类型完全定义之前必须始终使用指针或引用。显然不是这样。 - user877329
显示剩余9条评论

4

你不能将函数指针转换为void*(它们可能具有不同的大小),但这并不是问题,因为我们可以将其转换为另一种函数指针类型,然后再次转换以获取原始值。

typedef void (*fun2)();
typedef fun2 (*fun1)();

fun2 rec_fun()
{
    puts("Called a function");
    return (fun2)rec_fun;
}

// later in code...
fun1 fp = (fun1)((fun1)rec_fun())();
fp();

输出:

Called a function
Called a function
Called a function

5
即使标准没有要求,也请注意:如果C标准没有要求某些东西(即没有定义它),则行为是未定义的,因此你无法这样做。(我已经检查过了-C标准没有定义从函数指针到数据指针的转换)。如果在您的代码中进行转换有效,则您不是在编写C代码-而是在编写其超集(这不是C)。 - milleniumbug
J.5.7:对象指针或void指针可以强制转换为函数指针,从而允许以函数的形式调用数据(6.5.4)。函数指针可以强制转换为对象指针或void指针,允许检查或修改函数(例如,通过调试器)(6.5.4)。 C99和C11。 - Alex Celeste
@GradyPlayer:它们不仅属于不同的宇宙,而且不能保证指向一个宇宙中的东西的指针与指向另一个宇宙中的东西的指针大小相同。顺便说一句,我认为如果编译器和链接器为每个函数签名构建一个跳转表,其中包含所有地址被取的具有该签名的函数,则64K代码空间和256字节数据空间的机器在大多数情况下可以使用8位代码指针。我很好奇即使在计算跳转和“全范围”跳转都需要的体系结构上,为什么通常不这样做... - supercat
例如,在PIC18xx上,“movf src,w / call springboardxx / addwf pc / goto xxxx”在2MB地址空间内跳转的总成本为7个周期,而即使是最现实可能的假设,16位计算跳转也至少需要6个周期,在大多数实际实现中,它需要超过十个周期。因此,使用“短跳转”指令比使用“全范围”计算跳转要更加高效。 - supercat
@GradyPlayer:这样做将把8位指针分派的成本降低到六个周期,这与16位分派的绝对最小值相匹配。由于每个加载/存储操作都可以节省两个指令字和两个周期的代码指针,因此除了涉及太多不同地址的情况外,这种方法将是无条件的性能优势。[顺便说一句,所引用的设计利用了“CALL”指令为下一个计算跳转设置上部程序计数器位的事实] - supercat
显示剩余7条评论

3
换句话说,一个函数能否返回自身?
这取决于“自身”的含义;如果你指的是指向自身的指针,那么答案是肯定的!虽然一个函数不可能返回它自己的类型,但是一个函数可以返回指向它自身的指针,并且在调用之前可以将该指针转换为适当的类型。
细节在问题中有解释:comp.lang.c faq: Function that can return a pointer to a function of the same type. 详见我的 答案

1
假设函数定义为:
T f(void)
{
  return &f;
}

f() 返回类型为 T 的值,但表达式 &f 的类型是“返回类型为 T 的函数指针”。无论 T 是什么,表达式 &f 总是不同的、不兼容的类型 T (*)(void)。即使 T 是指向函数的指针类型,例如 Q (*)(void),表达式 &f 最终将成为“返回指向函数的指针的函数指针”,或者 Q (*(*)(void))(void)

如果 T 是足够大以容纳函数指针值的整数类型,并且在您的平台上从 T (*)(void)T 再到 T (*)(void) 的转换有意义,那么您可能可以像下面这样做:

T f(void)
{ 
  return (T) &f;
}

但我至少能想到几种情况,它根本行不通。 而且说实话,与使用查找表相比,它的实用性极为有限。
C语言并没有设计将函数视为任何其他数据项,并且函数指针不能与对象类型的指针互换。

如果你坚持要踩我,至少告诉我为什么,这样我才能修复你认为的错误。 - John Bode

0

函数不可能通过值返回自身。但是,它可以通过指针返回自身。

C语言允许定义接受未定义数量参数的函数类型,并且这些类型与接受已定义参数的函数类型兼容。

例如:

typedef void fun_t();

void foo(int);
fun_t *fun = foo; // types are fine

因此,以下函数将起作用。

void fun(void (**ptr)()) {
    *ptr = &fun;
}

以下是示例用法:

#include <stdio.h>

void fun(void (**ptr)()) {
    puts("fun() called");
    *ptr = &fun;
}

int main() {
    void (*fp)();
    fun(&fp); /* call fun directly */
    fp(&fp);  /* call fun indirectly */
    return 0;
}

该代码在C89标准下以严格模式编译,没有任何警告。

它产生了预期的输出:

fun() called
fun() called

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

typedef void *(*fptr)(int *);
void *start (int *);
void *stop (int *);
void *start (int *a) {
         printf("%s\n", __func__);
         return stop(a);
}
void *stop (int *a) {
         printf("%s\n", __func__);
         return start(a);
}
int main (void) {
         int a = 10;
         fptr f = start;
         f(&a);
         return 0;
}

0

这样怎么样:

typedef void* (*takesDoubleReturnsVoidPtr)(double);

void* functionB(double d)
{
    printf("here is a function %f",d);
    return NULL;
}

takesDoubleReturnsVoidPtr functionA()
{
    return functionB;
}

int main(int argc, const char * argv[])
{
    takesDoubleReturnsVoidPtr func = functionA();
    func(56.7);
    return 0;
}

你可以让某个东西返回函数A...但你已经知道了它的地址,因为你刚刚调用了它。 - Grady Player
能够返回函数A的关键是它可以返回它本身,也可以返回其他内容,例如:if (someCondition) return functionA else return functionB; - Drew McGowen
在 Objective C 中有类似于 typedef id (*IMP)(id, SEL, ...); 的东西,因为 IMP 返回一个抽象对象 id,该对象可以包含其自己的 IMP,因此您可以采用面向对象的方法。 - Grady Player

-1

有一种方法,你可以尝试这个:

typedef void *(*FuncPtr)();

void *f() { return f; }

int main() {
    FuncPtr f1 = f();
    FuncPtr f2 = f1();
    FuncPtr f3 = f2();
    return 0;
}

void * 到函数指针的转换不是标准的,也绝对不是隐式的。 - Antti Haapala -- Слава Україні

-2
如果您正在使用C ++,则可以创建一个State对象类型(假设状态机示例用法),其中声明返回引用或指针的State对象类型的operator()。然后,您可以将每个状态定义为State的派生类,该类从其operator()的实现中返回每个适当的其他派生类型。

15
如果他正在使用OCaml,那么OP可以使用选项-rectypes,以使系统接受定义let rec f () = f;;,其类型将被推断为unit -> 'a as 'a。但是因为他既没有标记问题为C ++也没有标记为OCaml,所以我们并没有真正地提供帮助,是吗? - Pascal Cuoq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接