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

时间:2017-05-04 05:00:45

标签: c++ pointers calling-convention dlopen abi

我有两段代码:第一部分是在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* s。

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

我的问题是:我是幸运的,还是保证可以在任何地方工作?如果我没有弄错,那么使用f(Data*)参数调用某些void*的结果就像在reinterpret_cast<Data*>上调用void*一样 - 并且#39;可能很危险。正确?

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

3 个答案:

答案 0 :(得分:3)

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

  

函数指针可以显式转换为不同类型的函数指针。 通过指向与函数定义中使用的类型不同的函数类型的函数调用函数的效果是未定义的。除了转换类型&#34;指针的prvalue之外到T1&#34;指向T2&#34;的类型&#34;指针(其中T1T2是函数类型)并返回其原始类型会产生原始指针值,   这种指针转换的结果是未指定的。

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

  

通过表达式调用函数,该表达式的函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接,这是未定义的。

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

答案 1 :(得分:2)

虽然这可能在实践中起作用,但C不保证这种行为。

有两个问题:

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

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

  3. 那就是说,你正在使用dlsym,它是一个POSIX函数。我不知道POSIX是否有额外的要求使这段代码可移植(对所有POSIX系统)。

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

    struct Data;
    

    留下不透明的类型。这满足了你原来的要求(客户端不会弄乱Data的内部,因为它不知道它是什么,它只能传递指针),但也使界面更安全:你可以不小心将错误的指针类型传递给它,这会被void *的内容默默接受。

答案 2 :(得分:0)

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

Why should we typedef a struct so often in C?

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