我读到过将函数指针转换为数据指针,反之亦然,这在大多数平台上都有效,但不保证适用于所有情况。为什么会出现这种情况?两者不都只是主内存中的地址,所以应该兼容吗?
我读到过将函数指针转换为数据指针,反之亦然,这在大多数平台上都有效,但不保证适用于所有情况。为什么会出现这种情况?两者不都只是主内存中的地址,所以应该兼容吗?
架构并不一定要将代码和数据存储在同一块内存中。使用哈佛架构,代码和数据分别存储在完全不同的内存中。大多数架构都是冯·诺依曼架构,其中代码和数据存储在同一块内存中,但 C 语言不局限于特定类型的架构。
CS!=DS
的近指针)。 - cafVirtualProtect
之前,你无法将数据区域标记为可执行。 - Dietrich Epp有些计算机的代码和数据拥有不同的地址空间。在这种硬件上,指针会失效。
C语言的设计不仅适用于当前的桌面应用程序,还允许它在大量硬件上实现。
似乎C语言委员会从未打算将void*
作为函数指针,他们只想要一个通用的对象指针。
C99 Rationale 指出:
6.3.2.3 指针
C现在已经被实现在各种体系结构上。虽然其中一些体系结构具有大小等于某些整数类型的统一指针,但最大程度地可移植的代码不能假定任何不同指针类型和整数类型之间的对应关系。在某些实现中,指针甚至可能比任何整数类型都宽。
void*
(“指向void
”的指针)作为一种通用的对象指针类型是由C89委员会发明的。采用这种类型的目的是为了指定函数原型参数,这些参数可以静默地转换任意指针(如fread
),或者如果参数类型与所需类型不完全匹配,则抛出错误(如strcmp
)。关于函数指针没有任何说明,它们可能与对象指针和/或整数不相容。
请注意,最后一段中“没有任何说明函数指针”的话。他们可能与其他指针不同,委员会已经意识到这一点。
void *
。 - ouahvoid*
可以与函数指针兼容吗?我认为所有非函数指针都与函数指针不兼容,包括void*
。 - gexicidesizeof(void*) == sizeof( void(*)() )
会浪费空间,因为函数指针和数据指针在大小上可能不同。这在80年代是一种常见情况,当时第一个C标准被编写出来。 - Robᵩ对于那些还记得MS-DOS、Windows 3.1及更早版本的人来说,答案相当简单。所有这些操作系统都支持多种不同的内存模型,具有不同组合的代码和数据指针特性。
因此,例如对于Compact模型(小代码,大数据):
sizeof(void *) > sizeof(void(*)())
在 Medium 模型中相反的情况是 (大代码,小数据):
sizeof(void *) < sizeof(void(*)())
在这种情况下,你没有单独的存储代码和数据的空间,但仍然无法在两个指针之间进行转换(除非使用非标准的 __near 和 __far 修饰符)。此外,即使指针大小相同,也不能保证它们指向相同的内容——在DOS Small内存模型中,代码和数据都使用近似指针,但它们指向不同的段。因此,将函数指针转换为数据指针将不会给你一个与函数有任何关系的指针,因此这种转换是没有用处的。int*
转换为void*
会给您一个指针,您实际上无法对其进行任何操作,但仍然有用能够执行此转换。(这是因为void*
可以存储任何对象指针,因此可用于不需要知道其所持有类型的通用算法。如果允许,函数指针也可能非常有用。) - ruakhint*
转换为void*
时,void*
指针至少保证指向与原始int*
相同的对象 - 因此对于访问所指对象的通用算法(如int n; memcpy(&n, src, sizeof n);
)很有用。但将函数指针转换为void*
时,如果它不能产生指向函数的指针,那么它就不适用于这样的算法 - 您唯一能做的是将void*
再次转换回函数指针,因此最好使用包含void*
和函数指针的union
。 - cafvoid*
确实指向函数,我想让人们把它传给memcpy
也是个坏主意。:-P - ruakhvoid
的指针类型相同的表示形式。将函数指针转换为 void *
不应更改其表示形式。由此转换产生的 void *
值可以使用显式转换回原始函数指针类型,而不会丢失信息。注意:ISO C 标准不要求这样做,但 POSIX 符合性要求这样做。 - Jonathan Leffler指向void的指针应该能够容纳任何类型数据的指针 - 但不一定是函数指针。一些系统对函数指针和数据指针具有不同的要求(例如,某些DSP对数据和代码使用不同的寻址方式,在MS-DOS上使用中模型时,代码使用32位指针,而数据只使用16位指针)。
dlsym_function()
用于返回函数符号,或者 dlsym()
将返回一个包含 void *
和 void (*)()
的 union {}
。 - caf除了已经在这里提到的内容外,值得一提的是看一下POSIX dlsym()
:
ISO C标准不要求函数指针可以相互转换为数据指针。事实上,ISO C标准不要求void*类型的对象能够保存函数指针。然而,支持XSI扩展的实现确实要求void*类型的对象可保存函数指针。但是,将函数指针转换为另一种数据类型(除了void*)的结果仍然未定义。请注意,符合ISO C标准的编译器在尝试将void*指针转换为函数指针时必须生成警告,如下所示:
fptr = (int (*)(int))dlsym(handle, "my_function");
由于这里记录的问题,未来版本可能会添加一个新函数来返回函数指针,或者当前接口可能会废弃,而使用两个新函数: 一个返回数据指针,另一个返回函数指针。
void*
并且回转。 - Maxim Egorushkinvoid*
与函数指针兼容,而POSIX则要求。 - Maxim Egorushkin-Werror
)。更好(且非UB)的解决方案是检索由dlsym
返回的对象的指针(即void **
),并将其转换为函数指针的指针。仍然是实现定义,但不再引起警告/错误。 - Konrad Rudolphdlsym
和GetProcAddress
能够在编译时没有警告而编写的。 - MSalters-pedantic
)确实*会发出警告。再次强调,没有任何推测的可能性。 - Konrad Rudolph根据目标架构,代码和数据可能存储在基本上不兼容的物理不同的内存区域中。
另一种解决方案:
假设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
方法。
未定义并不一定意味着不允许,它可以意味着编译器实现者有更多的自由来决定如何处理。
例如,在某些架构上可能不可能 - 未定义使得它们仍然可以拥有一个符合标准的'C'库,即使你无法执行此操作。
它们可以是不同类型,有不同的空间要求。将其分配给一个可能会不可逆地切片指针的值,以至于重新赋值会得到不同的结果。
我认为它们可以是不同类型,因为标准不想限制可能的实现,在不需要节省空间或当大小可能导致CPU执行额外操作时,可以节省空间等等。
void
类型的指针相同的表示形式。将函数指针转换为void *
不应改变该表示形式。由此转换产生的void *
值可以通过显式强制转换回原始函数指针类型,而不会丢失信息。 注意:ISO C标准不要求这样做,但它是符合POSIX标准所必需的。 - Jonathan Lefflervoid*
转换的保证现在仅适用于由dlsym()
返回的值。 - Keith Thompsondlsym()
相关联——请注意“应用程序使用”部分的末尾,其中说:“请注意,将void *
指针转换为函数指针,如:fptr = (int (*)(int))dlsym(handle, "my_function");
在ISO C标准中未定义。该标准要求此转换在符合规范的实现上正常工作。” - Jonathan Leffler