我已经读过,在大多数平台上都可以将函数指针转换为数据指针,反之亦然,但不能保证能正常工作。为什么会这样呢?难道两个都不都是简单地寻址到主存储器并因此兼容吗?
我已经读过,在大多数平台上都可以将函数指针转换为数据指针,反之亦然,但不能保证能正常工作。为什么会这样呢?难道两个都不都是简单地寻址到主存储器并因此兼容吗?
Answers:
架构不必将代码和数据存储在同一内存中。使用哈佛架构,代码和数据存储在完全不同的内存中。大多数体系结构都是冯·诺依曼体系结构,在同一内存中具有代码和数据,但是C绝不将自身限制为仅某些类型的体系结构。
CS != DS
)。
VirtualProtect
,这才允许您将数据区域标记为可执行文件。
某些计算机具有(具有)用于代码和数据的单独地址空间。在这样的硬件上,它根本不起作用。
该语言不仅为当前的桌面应用程序设计,而且允许在大量硬件上实现。
似乎C语言委员会从未打算过void*
要成为函数的指针,他们只是想要一个指向对象的通用指针。
C99基本原理说:
6.3.2.3指针
C现在已在多种体系结构上实现。尽管其中一些体系结构具有统一的指针,这些指针的大小是某些整数类型的大小,但最大的可移植代码无法假定不同指针类型和整数类型之间的任何必要的对应关系。在某些实现中,指针甚至可以比任何整数类型都宽。使用
void*
(“指向的指针void
”)作为通用对象指针类型是C89委员会的一项发明。指定函数原型参数的愿望刺激了采用这种类型,该函数要么悄悄地转换任意指针(如fread
),要么抱怨参数类型不完全匹配(如strcmp
)。关于函数的指针,它什么也没说,这可能与对象指针和/或整数不符。
注意在上一段中没有提到任何有关函数的指针。它们可能与其他指标有所不同,委员会对此有所了解。
void *
。
sizeof(void*) == sizeof( void(*)() )
在函数指针和数据指针大小不同的情况下,要求将浪费空间。这是在80年代写第一个C标准时的一种常见情况。
对于那些记得MS-DOS,Windows 3.1和更早版本的人,答案很简单。所有这些用于支持几种不同的内存模型,并具有代码和数据指针的特征的不同组合。
因此,例如对于紧凑模型(小代码,大数据):
sizeof(void *) > sizeof(void(*)())
相反,在中型模型中(大代码,小数据):
sizeof(void *) < sizeof(void(*)())
在这种情况下,您没有代码和日期的单独存储,但是仍然无法在两个指针之间进行转换(缺少使用非标准的__near和__far修饰符)。
另外,不能保证即使指针大小相同,它们也指向同一事物-在DOS小型内存模型中,代码和数据都在指针附近使用,但它们指向的是不同的段。因此,将函数指针转换为数据指针根本不会给您一个与该函数有任何关系的指针,因此这种转换毫无用处。
int*
为void*
会给您一个指针,您实际上无法做任何事情,但是能够执行转换仍然很有用。(这是因为void*
可以存储任何对象指针,因此可以用于不需要知道它们所持有的类型的通用算法。如果允许的话,同样的事情也可以用于函数指针。)
int *
,以void *
中,void *
是保证至少指向同一个对象作为原始int *
没有-所以这是通用算法有用的访问被指向的对象,等int n; memcpy(&n, src, sizeof n);
。如果将函数指针转换为a void *
不会产生指向该函数的指针,则对于此类算法没有用-您唯一可以做的就是void *
再次将其转换回函数指针,因此您可能会只需使用union
包含void *
和函数指针。
void*
确实指向了该函数,我认为将其传递给也是一个坏主意memcpy
。:-P
指向void的指针应该能够容纳指向任何类型数据的指针-但不一定是指向函数的指针。某些系统对功能的指针的要求与对数据的指针的要求不同(例如,DSP的数据和代码的寻址方式不同,MS-DOS上的中等模型对代码使用32位指针,而对数据仅使用16位指针) 。
除了这里已经说过的内容外,看看POSIX还很有趣dlsym()
:
ISO C标准不要求可以将指向函数的指针前后转换为指向数据的指针。确实,ISO C标准并不要求void *类型的对象可以持有指向函数的指针。但是,支持XSI扩展的实现确实要求void *类型的对象可以容纳指向函数的指针。但是,将指向函数的指针转换为指向另一种数据类型(void *除外)的指针的结果仍然不确定。请注意,如果尝试从void *指针转换为函数指针,则需要遵循ISO C标准的编译器生成警告,如下所示:
fptr = (int (*)(int))dlsym(handle, "my_function");
由于此处提到的问题,将来的版本可能添加新函数以返回函数指针,或者不建议使用当前接口,而推荐使用两个新函数:一个返回数据指针,另一个返回函数指针。
void*
前后转换。
void*
与函数指针兼容,而POSIX则需要。
C ++ 11解决了C / C ++与POSIX之间长期存在的不匹配问题dlsym()
。reinterpret_cast
只要实现支持此功能,就可以用于将函数指针与数据指针进行转换。
根据标准,第5.2.10段。在图8中,“有条件地支持将函数指针转换为对象指针类型,反之亦然。” 1.3.5将“有条件支持”定义为“不需要实现支持的程序构造”。
-Werror
)。更好的(非UB)解决方案是检索指向dlsym
(即void**
)返回的对象的指针,并将其转换为指向函数指针的指针。仍由实现定义,但不再导致警告/错误。
dlsym
和GetProcAddress
编译而不会发出警告。
-pedantic
)确实会发出警告。再次,没有猜测可能。
根据目标体系结构,代码和数据可能存储在根本不兼容的,物理上不同的内存区域中。
void *
足够大,可以容纳任何数据指针,但不一定包含任何函数指针。
另一个解决方案:
假设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;
但是memcpy
如果您绝对希望100%正确的C语言,我会推荐这种方法。
唯一真正可移植的解决方案是不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
不支持动态链接或用户/系统集成商不希望使用动态链接的系统上的所有模块通过静态链接来支持动态加载。
foo_module
结构(具有唯一的名称),则可以简单地创建一个带有数组struct { const char *module_name; const struct module *module_funcs; }
和一个简单函数的额外文件,以在该表中搜索您要“加载”的模块并返回正确的指针,然后使用此指针代替dlopen
和dlsym
。
一个现代的示例,其中函数指针的大小可以与数据指针的大小不同: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
指针可用作指向的成员函数的指针Derived
,因为它们都使用相同的this
指针。但是指向的成员函数的指针Base2
不能原样用作指向的成员函数Derived
的this
指针,因为该指针需要调整。有许多解决方法。这是Visual Studio编译器决定处理它的方式:
指向多重继承类的成员函数的指针实际上是一个结构。
[Address of function] [Adjustor]
使用多重继承的类的指向成员函数的指针的大小是指针的大小加上a的大小
size_t
。
tl; dr:使用多重继承时,指向成员函数的指针(取决于编译器,版本,体系结构等)实际上可以存储为
struct {
void * func;
size_t offset;
}
显然比void *
。