代码之家  ›  专栏  ›  技术社区  ›  Oliver Schönrock

将泛型函数指针强制转换为具有指向不同类型的void*params的特定签名是否定义良好

  •  1
  • Oliver Schönrock  · 技术社区  · 1 年前

    EDIT:从答案中得出的最相关的结论是……不:你不能,因为void*和struct*的指针在内存中的表示可能会有所不同。这很罕见,但标准并不禁止。这是我找到的最好的标准引用:

    https://stackoverflow.com/a/1241314/1087626

    在下面的模拟示例中,我试图构建一个 generic_processor 接口,调用者将对象指针传递到 void* 处理器中的参数,调用者显式地将“操作”转换为 void(*gfp)(void) (通用函数指针),这是 通用处理器 期待。

    对于跨类型的每个操作,“具体操作”都有几乎相同的签名,因为它们接受一个对象指针,可能还有其他一些参数,并返回一个 int 请注意,具体操作采用对象指针 混凝土类型 .

    以下是我怀疑的关键步骤: 通用处理器 铸造 gfp 函数指针的参数 typedef 具有与具体动作功能匹配的签名 除了对象指针现在是 无效* .

    正如这个答案中引用的:

    https://stackoverflow.com/a/189126/1087626

    • 766指向一种类型的函数的指针可以转换为指向另一种类型函数的指针,然后再转换回来;
    • 767,结果应与原始指针相等。
    • 768如果使用转换后的指针调用类型与所指向类型不兼容的函数,则行为未定义。

    所以,问题的核心是:在这种情况下,“不兼容”是什么意思?

    在the 通用处理器 我正在转换一个与原始具体签名相同的函数指针签名,除了它有一个 无效* 作为第一个参数,而不是 circle* square* .

    这定义得好吗?

    模拟示例:

    #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
    
    
    1 回复  |  直到 1 年前
        1
  •  3
  •   dbush    1 年前

    忽略变分函数和旧的K&R风格声明,如果两个函数指针指向以下函数,则它们是兼容的:

    • 它们的返回类型是兼容的
    • 它们具有相同数量的参数
    • 参数的类型是兼容的

    第6.7.6.3p15节对此进行了详细说明 C standard :

    为了使两种功能类型兼容,两者都应指定兼容 返回类型。此外,参数类型列表(如果两者都是) 目前,应就参数数量和使用达成一致 省略终止符;相应的参数应兼容 类型。

    在您的情况下,您的函数都与函数指针不兼容 open_fpt send_fpt 因为他们采取了单一 void * 参数和所讨论的函数需要一个指向不同结构类型的指针,以及 无效* 与结构指针不兼容。因此,这种转换是无效的,并导致未定义的行为。

    你需要做的就是改变你的功能 无效* 参数,然后将参数转换为正确的类型。例如:

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

    或者,如果您无法更改上述函数定义,请创建一个包装器函数:

    int open_circle_wrap(void *p) {
      return open_circle(p);
    }
    

    generic_processor 您可以删除铸件:

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