C 接口是否关心指向的类型?

Do C interfaces care about the pointed-to type?

我有两段代码:第一段,在 C++ 程序中,是我从外部加载和调用函数的地方 test_lib.so:

typedef void *(*init_t)(); // init_t is ptr to fcn returning a void*
typedef void (*work_t)(void *); // work_t is ptr to fcn taking a void*

void *lib = dlopen("test_lib.so", RTLD_NOW);

init_t init_fcn = dlsym(lib, "test_fcn");
work_t work_fcn = dlsym(lib, "work_fcn");

void *data = init_fcn();
work_fcn(data);

第二段代码编译为 test_lib.so:

struct Data {
    // ...
};

extern "C" {
void *init_fcn() {
    Data *data = new Data; // generate a new Data*...
    return data; // ...and return it as void*
}

void work_fcn(void *data) { // take a void*...
    static_cast<Data *>(data)->blabla(); // ...and treat it as Data*
    static_cast<Data *>(data)->bleble();
}
}

现在,第一段代码不需要知道 Data 是什么,它只是传递指针,所以它是 void*。但是直接使用 data 的方法和成员的库需要知道,因此它必须将 void* 转换为 Data*

但是这两段代码之间的接口只是一些指针参数为and/or return类型的函数。我可以只保留客户端中的 void*,并将库中 void* 的每个实例更改为 Data*。我这样做了,一切正常(我的系统是 Linux/GCC 6.2.1)。

我的问题是:我是幸运的,还是这保证在任何地方都有效?如果我没记错的话,用 void* 参数调用一些 f(Data*) 的结果就好像在 void* 上调用 reinterpret_cast<Data*> 一样——那不可能可能是危险的。对吗?

编辑: 不,简单地使 Data 类型对客户端代码透明是行不通的。客户端代码通过相同的 API 调用许多库,但每个库可能有自己的实现。对于客户,Data 可以是任何东西。

虽然这在实践中可能有效,但 C 不保证这种行为。

有两个问题:

  1. 不同的指针类型可以有不同的大小和表示。在这样的实现中,void * 和返回涉及运行时的实际转换,而不仅仅是为了让编译器满意而进行的转换。有关示例列表,请参阅 http://c-faq.com/null/machexamp.html,例如“旧的 HP 3000 系列对字节地址使用的寻址方案与字地址不同;像上面的几台机器一样,它因此对 char *void * 指针使用不同的表示法,而不是其他指针。"

  2. 不同的指针类型可以使用不同的调用约定。例如,一个实现可能在堆栈上传递 void * 而在寄存器中传递其他指针。 C 没有定义 ABI,所以这是合法的。

就是说,您正在使用 dlsym,这是一个 POSIX 函数。我不知道 POSIX 是否强加了使此代码可移植(对所有 POSIX 系统)的附加要求。


另一方面,你为什么不到处使用 Data *?在客户端你可以做

struct Data;

使类型不透明。这满足了你最初的要求(客户端不能弄乱 Data 的内部,因为它不知道它是什么,它只能传递指针),但也使接口更安全:你可以' 不小心将错误的指针类型传递给它,它会被采用 void *.

的东西默默接受

通过错误的函数类型调用任何函数自动成为未定义行为。来自 C++ 标准草案 n4604(大约 C++17)[expr.reinterpret.cast]:

A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling a function through a pointer to a function type that is not the same as the type used in the definition of the function is undefined. Except that converting a prvalue of type "pointer to T1" to the type "pointer to T2" (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.

通过带有错误链接的函数指针类型调用任何函数也是未定义的行为。您的 typedef 不使用 "C" 链接,因此 UB。来自 n4604 草案第 [expr.call] 部分:

Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined.

除此之外,不同的指针类型不需要具有相同的表示。 (cv-qualified) void* 可以容纳任何对象指针,但它的对齐限制与 char* 相同(即没有限制)因此,它不一定表示与其他对象指针兼容类型和 may not even be the same size。 (而且最肯定的是,对象指针、函数指针和指向成员的指针的变体在现实世界的系统中通常大小不同。)

您可以使用不透明的结构定义使其更清晰。在此处查看已接受答案的后半部分:

Why should we typedef a struct so often in C?

因此调用者正在处理指向已定义类型的指针,但看不到所指向的内容。该实现具有实际的结构定义,并且可以使用它。不再需要铸造。