You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将泛型函数指针转换为含void*参数的特定签名是否合规?

原问题

我尝试构建一个generic_processor接口:调用者将对象指针传入处理器的void*参数,并把具体操作函数显式转换为void(*gfp)(void)(泛型函数指针)供generic_processor使用。

各类型的具体操作函数签名几乎一致:接收对应类型的对象指针、可能的其他参数,返回int。

核心疑问点:generic_processor会把gfp参数转换为一种函数指针类型,其签名与具体操作函数几乎一致,仅第一个参数从具体类型指针改为void*。

根据C标准的相关条款:

  • 一种函数类型的指针可转换为另一种函数类型的指针,再转换回原类型;
  • 转换结果应与原指针相等;
  • 若使用转换后的指针调用类型不兼容的函数,行为未定义。

因此核心问题是:此处的“不兼容”具体指什么?将具体类型指针参数替换为void*的函数指针转换是否合规?

示例代码

#include <stdio.h>

// types
typedef struct {
  int a;
  double d;
} circle;

typedef struct {
  int b;
  float f;
} square;

// ... 10 types,.. different sizeof()

// concrete API
int open_circle(circle* c) {
  printf("opening circle: %d: %f\n", c->a, c->d);
  return c->a;
}

int open_square(square* s) {
  printf("opening square: %d: %f\n", s->b, s->f);
  return s->b;
}

int send_circle(circle* c, const char* msg) {
  printf("sending circle: %d: %f: %s\n", c->a, c->d, msg);
  return -c->a;
}

int send_square(square* s, const char* msg) {
  printf("sending square: %d: %f: %s\n", s->b, s->f, msg);
  return -s->b;
}

// ten more operations for each type

// "genericised" function pointer types (note the void* params!!)
typedef int (*open_fpt)(void* o);
typedef int (*send_fpt)(void* o, const char*);

typedef void (*gfp)(void); // generic function pointer

int generic_processor(void* obj, gfp open, gfp send) {
  int sum = 0;
  sum += ((open_fpt)open)(obj);
  sum += ((send_fpt)send)(obj, "generically sent");
  return sum;
}

int main() {
  circle c = {2, 22.2};
  square s = {3, 33.3F};

  int net = 0;
  net += generic_processor(&c, (gfp)open_circle, (gfp)send_circle);
  net += generic_processor(&s, (gfp)open_square, (gfp)send_square);
  printf("net %d\n", net);
  return 0;
}

编译运行结果

使用GCC 13.2编译无警告且运行正常:

gcc -std=c99 -g -o gfp tests/gfp.c -Wall -Wextra -pedantic -fsanitize=address,leak,undefined && ./gfp 
opening circle: 2: 22.200000
sending circle: 2: 22.200000: generically sent
opening square: 3: 33.299999
sending square: 3: 33.299999: generically sent
net 0

EDIT1

我从讨论中学到了很多,原示例代码并不合规,因为void*与square*不兼容——结构体指针与非结构体指针的表示形式可能不同。

不过标准保证所有结构体指针的大小和表示形式一致,因此只需将void*改为虚拟结构体类型obj_*,代码即可符合标准,完整修正代码见我的补充回答。


EDIT2

关于函数指针转换时void*或struct*的使用,C标准定义并不完全清晰,甚至不同部分存在一定矛盾。

这种模糊性导致像OpenSSL这类大型安全项目使用了与本文类似的技术,却在LLVM发布的Clang 17(2023版)的UBSAN中被判定为未定义行为(UB)并抛出运行时警告。

鉴于这类基础问题缺乏明确标准,我决定采用“宏thunking黑魔法”方案,详见我的另一篇补充回答。


内容的提问来源于Stack Exchange,提问作者Oliver Schönrock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:04:56