为什么函数指针和数据指针在C / C ++中不兼容?

时间:2012-09-10 20:21:40

标签: c++ c pointers function-pointers

我已经读过将函数指针转换为数据指针,反之亦然,但在大多数平台上都可以使用,但不保证能够正常工作。为什么会这样?两者都不应该简单地写入主内存,因此兼容吗?

14 个答案:

答案 0 :(得分:169)

架构不必将代码和数据存储在同一内存中。使用哈佛架构,代码和数据存储在完全不同的内存中。大多数体系结构都是Von Neumann体系结构,代码和数据存储在同一个内存中,但如果可能的话,C并不仅限于某些类型的体系结构。

答案 1 :(得分:37)

某些计算机(代码和数据)具有单独的地址空间。在这样的硬件上它只是不起作用。

该语言不仅适用于当前的桌面应用程序,还允许在大量硬件上实现。


似乎C语言委员会从不打算将void*作为指向函数的指针,他们只是想要一个指向对象的通用指针。

C99理由说:

  

6.3.2.3指针
  C现已在各种架构上实现。其中一些   体系结构具有统一指针,这些指针是最大的某种整数类型的大小   可移植代码不能假设不同指针类型和整数类型之间有任何必要的对应关系。在某些实现中,指针甚至可以比任何整数类型都宽。

     

使用void*(“指向void的指针”)作为通用对象指针类型是C89委员会的发明。指定函数原型参数的愿望刺激了这种类型的采用,这些参数要么悄悄地转换任意指针(如在fread中),要么在参数类型不完全匹配时抱怨(如在strcmp中)。关于函数的指针没有任何说法,这可能与对象指针和/或整数不相称。

注意在最后一段中没有关于函数的指针。它们可能与其他指针不同,委员会也知道这一点。

答案 2 :(得分:30)

对于那些记得MS-DOS,Windows 3.1及更早版本的人来说,答案非常简单。所有这些都用于支持几种不同的内存模型,具有不同的代码和数据指针特征组合。

例如,对于Compact模型(小代码,大数据):

sizeof(void *) > sizeof(void(*)())

相反,在Medium模型中(大代码,小数据):

sizeof(void *) < sizeof(void(*)())

在这种情况下,您没有单独的代码和日期存储空间,但仍然无法在两个指针之间进行转换(缺少使用非标准__near和__far修饰符)。

此外,即使指针大小相同,也不能保证它们指向相同的东西 - 在DOS小内存模型中,指针附近使用的代码和数据,但它们指向不同的段。因此,将函数指针转换为数据指针不会给你一个与函数有任何关系的指针,因此没有用于这样的转换。

答案 3 :(得分:23)

指向void的指针应该能够容纳指向任何类型数据的指针 - 但不一定是指向函数的指针。有些系统对指向函数的指针有不同的要求,而不是指向数据的指针(例如,有数据与代码有不同寻址的DSP,MS-DOS上的介质模型使用32位指针代码,但只有16位数据指针)

答案 4 :(得分:12)

除了这里已经说过的内容之外,看看POSIX dlsym()很有意思:

  

ISO C标准不要求指向函数的指针可以来回转换为指向数据的指针。实际上,ISO C标准不要求void *类型的对象可以保存指向函数的指针。但是,支持XSI扩展的实现确实需要void *类型的对象可以保存指向函数的指针。但是,将指向函数的指针转换为指向另一种数据类型(void *除外)的指针的结果仍未定义。请注意,如果尝试从void *指针到函数指针的转换,则需要符合ISO C标准的编译器生成警告:

 fptr = (int (*)(int))dlsym(handle, "my_function");
     

由于此处提到的问题,未来版本可能会添加一个新函数来返回函数指针,或者可能不赞成使用当前接口以支持两个新函数:一个返回数据指针而另一个返回函数指针

答案 5 :(得分:9)

C ++ 11解决了dlsym()与C / C ++和POSIX之间长期不匹配的问题。只要实现支持此功能,就可以使用reinterpret_cast将函数指针转换为数据指针。

从标准,5.2.10段。 8,“有条件地支持将函数指针转换为对象指针类型,反之亦然”。 1.3.5将“有条件支持”定义为“不需要支持实现的程序构造”。

答案 6 :(得分:7)

根据目标体系结构,代码和数据可能存储在根本不兼容的,物理上不同的内存区域中。

答案 7 :(得分:5)

undefined并不一定意味着不允许,它可能意味着编译器实现者可以更自由地按照自己的意愿去做。

例如,在某些体系结构上可能无法实现 - undefined允许它们仍然具有符合标准的“C”库,即使您不能这样做。

答案 8 :(得分:5)

它们可以是具有不同空间要求的不同类型。分配给一个可以不可逆地切片指针的值,以便分配回来产生不同的东西。

我认为它们可以是不同的类型,因为标准并不想限制可能的实现,以便在不需要时节省空间,或者当大小可能导致CPU必须使用额外的废话时它等...

答案 9 :(得分:4)

另一种解决方案:

假设POSIX保证函数和数据指针具有相同的大小和表示(我找不到相关的文本,但引用的示例OP表明它们至少打算来满足此要求) ,以下应该有效:

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

这可以通过char []表示来避免违反别名规则,允许对所有类型进行别名。

又一种方法:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

但如果你想要100%正确的C,我会推荐memcpy方法。

答案 10 :(得分:2)

唯一真正可移植的解决方案是不要将dlsym用于函数,而是使用dlsym来获取指向包含函数指针的数据的指针。例如,在您的库中:

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

然后在你的申请中:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

顺便提一下,这仍然是一个很好的设计实践,并且可以轻松支持通过dlopen动态加载和静态链接不支持动态链接的系统上的所有模块,或者用户/系统集成商执行的操作不想使用动态链接。

答案 11 :(得分:2)

在大多数体系结构中,指向所有普通数据类型的指针具有相同的表示形式,因此数据指针类型之间的转换是无操作。

然而,可以想象函数指针可能需要不同的表示,也许它们比其他指针更大。如果void *可以保存函数指针,这意味着void *的表示必须是更大的大小。所有来自void *的数据指针的转换都必须执行这个额外的副本。

正如有人提到的,如果你需要这个,你可以使用工会来实现它。但是void *的大多数用法仅用于数据,因此在需要存储函数指针的情况下增加所有内存使用将是繁重的。

答案 12 :(得分:0)

一个现代的例子,其中函数指针的大小可以与数据指针不同: C ++类成员函数指针

直接引自https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };
     

现在有两个可能的this指针。

     

指向Base1成员函数的指针可以用作指向a的指针   Derived的成员函数,因为它们都使用相同的this   指针。但是不能使用指向Base2的成员函数的指针   as-is作为指向Derived的成员函数的指针,因为this   指针需要调整。

     

有很多方法可以解决这个问题。以下是Visual Studio的内容   编译器决定处理它:

     

实际上是指向乘法继承类的成员函数的指针   结构。

[Address of function]
[Adjustor]
     

使用多重继承的类的指向成员函数的大小是指针的大小加上size_t的大小。

tl; dr:当使用多重继承时,指向成员函数的指针可能(取决于编译器,版本,体系结构等)实际存储为

struct { 
    void * func;
    size_t offset;
}

明显大于void *

答案 13 :(得分:-1)

我知道自2012年以来没有对此进行过评论,但我认为添加 知道一个非常不兼容指针的架构会很有用对于数据和功能,因为对该体系结构的调用检查特权并携带额外信息。没有多少铸造会有所帮助。这是The Mill