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

gex*_*ide 128 c c++ pointers function-pointers

我已经读过将函数指针转换为数据指针,反之亦然,但在大多数平台上都可以工作,但不能保证工作.为什么会这样?两者都不应该只是简单地址到主存储器中,因此兼容吗?

Dir*_*ple 169

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

  • 此外,即使代码和数据存储在物理硬件中的相同位置,软件和内存访问通常也会阻止将数据作为代码运行而无需操作系统"批准".DEP等. (15认同)
  • 至少与具有不同地址空间(可能更重要)一样重要的是,函数指针可以具有与数据指针不同的表示. (15认同)
  • 你甚至不需要哈佛架构来使用不同的地址空间来获得代码和数据指针 - 旧的DOS"小"内存模型就是这样做的(靠近指针`CS!= DS`). (14认同)
  • @EricJ.直到你调用`VirtualProtect`,它允许你将数据区域标记为可执行. (3认同)

Bo *_*son 37

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

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


似乎C语言委员会从未打算void*成为函数的指针,他们只是想要一个指向对象的泛型指针.

C99理由说:

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

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

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

  • @CrazyEddie你不能将函数指针赋给`void*`. (15认同)
  • @RichardChambers:不同的地址空间也可能有不同的地址*宽度*,例如[Atmel AVR](http://en.wikipedia.org/wiki/Atmel_AVR),它使用16位作为指令,8位作为数据; 在这种情况下,很难从数据(8位)转换为函数(16位)指针并再次返回.C应该很容易实现; 这种轻松的一部分来自于使数据和指令指针彼此不兼容. (8认同)
  • 但是在函数指针和数据指针大小不同的情况下,要求`sizeof(void*)== sizeof(void(*)())`会浪费空间.这是80年代的常见案例,当时编写了第一个C标准. (5认同)
  • 在void*接受函数指针时我可能错了,但重点仍然存在.比特是比特.该标准可能要求不同类型的大小能够容纳彼此的数据,并且即使它们被用在不同的存储器段中,也可以保证分配工作.这种不兼容性存在的原因是标准不能保证这一点,因此数据可能会在分配中丢失. (4认同)

Tom*_*mek 30

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

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

sizeof(void *) > sizeof(void(*)())
Run Code Online (Sandbox Code Playgroud)

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

sizeof(void *) < sizeof(void(*)())
Run Code Online (Sandbox Code Playgroud)

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

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

  • @ruakh:在将`int*`转换为`void*`的情况下,`void*'保证至少指向与原始`int*`相同的对象 - 所以这对于泛型算法很有用访问指向对象,如`int n; memcpy(&n,src,sizeof n);`.在将函数指针转换为`void*`不会产生指向函数的指针的情况下,它对这样的算法没有用处 - 你唯一能做的就是将`void*`转换回a再次使用函数指针,所以你不妨只使用包含`void*`和函数指针的`union`. (4认同)

Jer*_*fin 23

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

  • [+1回答问题*有人问之前*](http://meta.stackoverflow.com/questions/262662/time-paradox-on-the-stack-overflow-tour-page?cb=1) (31认同)
  • @LegoStormtroopr:有趣的是21人同意*投票的*想法*,但只有3人实际上已经这样做了.:-) (2认同)

Max*_*kin 13

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

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

 fptr = (int (*)(int))dlsym(handle, "my_function");
Run Code Online (Sandbox Code Playgroud)

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

  • 这意味着当前POSIX需要从平台ABI中将函数和数据指针都安全地转换为"void*"并返回. (4认同)

Dav*_*men 9

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

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

  • @KonradRudolph:不同意."条件支持"的措辞是专门编写的,允许`dlsym`和`GetProcAddress`在没有警告的情况下编译. (2认同)
  • @KonradRudolph:我不同意你的"不应该",这是一种意见.答案特别提到了C++ 11,在问题得到解决时我是C++ CWG的成员.C99确实有不同的措辞,有条件支持的是C++发明. (2认同)

Gra*_*and 7

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


Mar*_*ett 5

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

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


R..*_*R.. 5

另一个解决方案:

假设POSIX保证函数和数据指针具有相同的大小和表示形式(我找不到用于此的文本,但是引用的示例OP建议它们至少旨在满足此要求),以下方法应该起作用:

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

这样可以避免通过遍历char []表示形式而违反别名规则,该表示形式允许别名所有类型。

另一种方法:

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

但是memcpy如果您绝对希望100%正确的C语言,我会推荐这种方法。


Edw*_*nge 5

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

我相信它们可以是不同的类型,因为标准不希望限制在不需要时节省空间的可能实现,或者当大小可能导致CPU必须使用额外的垃圾来使用它等时...