我可以通过通用函数指针调用函数吗?

Can I call a function via a generic function pointer?

我目前正在尝试用 C 编写一个有点通用的 FSM。

我运行遇到的问题是,我想通过一个泛型类型的函数指针来调用一个特定状态的函数(例如状态的开始函数)。

我建立了一个简单的例子来说明我的问题:

typedef struct test
{
    int dummy;
} test_t;

void foo(test_t * test)
{
    
}

typedef void (fooFunction)(test_t * test);
typedef void (genericFooFunction)(void * test);

void bar()
{
    test_t test = {45};
    fooFunction * fpFoo = &foo;
    genericFooFunction * fpGenericFoo = (genericFooFunction *) &foo;
    fooFunction * fpGenericFooRecast = (fooFunction *) fpGenericFoo;

    fpFoo(&test); //OK

    fpGenericFooRecast(&test); //OK

    fpGenericFoo(&test); //not OK?

}

我想我知道 C 标准允许前两个函数调用,但不允许最后一个。我想知道为什么会这样。 (此代码适用于我的环境(Ubuntu 20.04 in WSL with gcc))。我知道不同的机器在表示指针或调用函数的方式上可能存在差异,但我想不出最后一个函数调用会失败或弄乱堆栈的情况。

这是因为:

  1. 函数的return类型和泛型指针是相等的,所以这里没有问题。
  2. 参数个数匹配。
  3. 参数的类型不相等,但都是指向对象的指针,应该具有相同的表示形式。

我哪里错了?

假设 3 不正确。来自 C17 6.2.5/28:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.48) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

从一种函数指针类型到另一种类型的转换是可以的,但是使用函数指针调用不兼容的函数是不行的。来自 C17 6.3.2.3/8:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

在您的代码示例中,函数调用 fpGenericFoo(&test); 导致未定义的行为,因为调用的函数 foo 与函数指针 fpGenericFoo 不兼容。

I think I know that the c standard allows the first two function calls, but not the last.

正确。 C17 6.3.2.3/8:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.


I wonder why this is the case.

C 标准通常不强制或假设任何关于指针实现的内容。一旦你进行疯狂的 function/object 指针转换然后 de-reference,你就完全是靠自己了,超出了标准的任何保证范围。

首先,不同的函数原型在给定的 ABI 中可能具有不同的调用约定显然总是很重要。同样,对象指针转换显然需要在重要的情况下具有相同的对齐方式。

除了那些明显的情况,不同的对象指针可能有不同的格式。 C 标准保证指针类型之间的转换本身,但不保证它们具有相同的表示形式。对象指针的转换由C17 6.3.2.3/1:

保证

A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

当然这意味着编译器使用不同对象指针类型的不同内部表示是非常不切实际的,尽管理论上它可以(DeathStation 9000 编译器)。因此,虽然标准不支持您的示例 3,但它很可能在实践中起作用。

确实存在各种不寻常的系统:一些具有 non-linear 地址映射,一些具有负地址和一些不同的地址大小。然而,这样的系统传统上总是通过 non-standard 扩展来处理它们的奇特特性,最常见的是指针的 nearfar 限定符。

None 在这些现实世界中,semi-exotic 我知道的系统在指针格式上有所不同,这取决于所使用的指针类型,而是取决于 pointed-at数据被存储。