为什么函数指针和数据指针在C / C ++中不兼容?
我已经读过,将函数指针转换为数据指针,反之亦然,在大多数平台上工作,但不能保证工作。 这是为什么? 不应该都只是简单的地址到主内存,因此是兼容的?
架构不必将代码和数据存储在相同的存储器中。 采用哈佛架构,代码和数据存储在完全不同的内存中。 大多数体系结构都是代码和数据在同一内存中的冯·诺依曼体系结构,但如果可能的话,C并不仅限于某些types的体系结构。
一些计算机具有用于代码和数据的单独的地址空间。 在这样的硬件上,它只是不起作用。
该语言不仅适用于当前的桌面应用程序,而且允许在大量的硬件上实现。
似乎C语言委员会从来没有打算将void*
作为函数的指针,他们只是想要一个指向对象的通用指针。
C99理由说:
6.3.2.3指针
C已经被广泛的架构实现。 虽然其中一些体系结构具有统一指针,这些指针是某种整数types的大小,但最大可移植代码不能承担不同指针types和整数types之间的任何必要对应关系。 在一些实现中,指针甚至可以比任何整数types更宽。使用
void*
(“void
指针”)作为通用对象指针types是C89委员会的发明。 这种types的采用受到指定函数原型参数的渴望的刺激,要么静静地转换任意指针(如fread
),要么抱怨参数types不完全匹配(如在strcmp
)。 没有什么关于函数的指针,可能与对象指针和/或整数不相称。
注意在最后一段中没有关于函数指针的说明。 他们可能与其他指针不同,委员会也知道这一点。
对于那些记得MS-DOS,Windows 3.1及更高版本的人来说,答案相当简单。 所有这些都用来支持几种不同的内存模型,对于代码和数据指针具有不同的特征组合。
所以例如对于Compact模型(小码,大数据):
sizeof(void *) > sizeof(void(*)())
相反,在中型号(大码,小号码)中:
sizeof(void *) < sizeof(void(*)())
在这种情况下,您没有单独的代码和date存储,但仍然无法在两个指针之间转换(使用非标准的__near和__far修饰符)。
另外,即使指针的大小相同,也不能保证它们指向相同的东西 – 在DOS小内存模型中,在指针附近使用代码和数据,但是它们指向不同的段。 所以将函数指针转换为数据指针不会给你一个与函数有任何关系的指针,因此没有用于这种转换。
指针无效应该能够容纳任何types数据的指针 – 但不一定是指向函数的指针。 有些系统对指向函数的指针的要求比指向数据的指针的要求不同(例如,数据与代码有不同的寻址的DSP,MS-DOS的中等模式使用32位指针代码,但是只有16位指针用于数据) 。
除了这里已经说过的内容,看看POSIX dlsym()
是很有趣的:
ISO C标准不要求指向函数的指针可以来回转换成指向数据的指针。 事实上,ISO C标准并不要求void *types的对象可以持有指向函数的指针。 但是,支持XSI扩展的实现要求void *types的对象可以包含指向函数的指针。 然而,将一个指针转换为一个指向另一个数据types(void *除外)的指针的结果仍然是未定义的。 请注意,如果尝试从void *指针到函数指针的转换,则需要符合ISO C标准的编译器生成警告,如下所示:
fptr = (int (*)(int))dlsym(handle, "my_function");
由于这里提到的问题,将来的版本可能会添加一个新的函数来返回函数指针,或者当前的接口可能会被弃用,以支持两个新的函数:一个返回数据指针,另一个返回函数指针。
根据目标体系结构的不同,代码和数据可能存储在基本上不兼容的,物理上不同的内存区域中。
C ++ 11有解决C / C ++和POSIX关于dlsym()
的长期不匹配问题。 只要实现支持这个特性,就可以使用reinterpret_cast
将函数指针转换为数据指针。
根据标准,5.2.10段。 8,“将函数指针转换为对象指针types或反之亦然是有条件支持的”。 1.3.5将“有条件支持”定义为“不需要实现支持的程序构造”。
未定义不一定意味着不允许,这可能意味着编译器实现者有更多的自由去做它想要的。
例如,在某些体系结构中可能无法实现 – 即使不能这样做,未定义也允许它们仍然具有符合的“C”库。
它们可以是不同的空间需求types。 分配给一个人可以不可逆地切片指针的值,以便分配返回的结果不同的东西。
我相信它们可以是不同的types,因为标准不希望限制在不需要时节省空间的可能的实现,或者当尺寸可能导致CPU必须做额外的废话来使用它时等等。
另一个scheme
假设POSIX保证函数和数据指针具有相同的大小和表示(我找不到这个文本,但引用的示例OP表明他们至less打算做出这个要求),下面的应该工作:
double (*cosine)(double); void *tmp; handle = dlopen("libm.so", RTLD_LAZY); tmp = dlsym(handle, "cos"); memcpy(&cosine, &tmp, sizeof cosine);
这样可以避免通过char []
表示来违反别名规则,该expression式允许别名所有types。
又一种方法:
union { double (*fptr)(double); void *dptr; } u; u.dptr = dlsym(handle, "cos"); cosine = u.fptr;
但是我会推荐memcpy
方法,如果你想绝对100%正确的C.
唯一真正的便携式解决scheme不是使用dlsym
函数,而是使用dlsym
来获取指向包含函数指针的数据的指针。 例如,在你的图书馆里:
struct module foo_module = { .create = create_func, .destroy = destroy_func, .write = write_func, /* ... */ };
然后在你的应用程序中:
struct module *foo = dlsym(handle, "foo_module"); foo->create(/*...*/); /* ... */
顺便说一下,这是一个很好的devise实践,并且可以很容易地支持通过dlopen
dynamic加载,并且静态链接不支持dynamic链接的系统上的所有模块,或者用户/系统集成商不想使用dynamic链接。
在大多数体系结构中,指向所有常规数据types的指针具有相同的表示forms,因此在数据指针types之间进行转换是无操作的。
但是,可以想象,函数指针可能需要不同的表示,也许它们比其他指针大。 如果void *可以包含函数指针,那么这意味着void *的表示必须是更大的大小。 所有从void *转换的数据指针都必须执行这个额外的拷贝。
正如有人提到,如果你需要这个,你可以使用工会来实现它。 但是void *的大部分用途仅仅是为了数据,所以在需要存储函数指针的情况下增加所有的内存使用是非常麻烦的。
函数指针的大小可以与数据指针不同的现代示例: 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(); };
现在有两个可能的指针。
指向
Base1
成员函数的指针可以用作指向Derived
成员函数的指针,因为它们都使用相同的指针。 但是指向Base2
成员函数的指针不能被当作指向Derived
成员函数的指针,因为this
指针需要被调整。有很多方法可以解决这个问题。 以下是Visual Studio编译器决定如何处理它的方法:
指向多inheritance类的成员函数的指针实际上是一个结构。
[Address of function] [Adjustor]
使用多inheritance的类的指向成员函数的大小是指针的大小加上
size_t
的大小。
tl; dr:当使用多重inheritance时,指向成员函数的指针可能(取决于编译器,版本,体系结构等)实际上被存储为
struct { void * func; size_t offset; }
这显然比void *
。
我知道自从2012年以来,这个问题一直没有被评论过,但是我认为应该补充一点,我知道一个架构的数据和函数指针非常不兼容,因为这个架构的调用会检查权限并携带额外的信息。 没有数量的铸造将有所帮助。 这是磨房 。