【问题标题】:Confusion about C++ function pointer关于 C++ 函数指针的困惑
【发布时间】:2016-08-25 05:29:49
【问题描述】:

我的源文件名以cpp结尾。

以下是 2 个声明:

void (*f1)(void);

void *f2(void);

我认为:

  • f1 是一个有效的函数指针。
  • f2 是一个返回 void * 指针的函数。

那我还有另一个功能:

void f3(void *p_func);

我认为:

  • 尽管有 p_func 参数名称,f3 只是一个接受 void * 指针的函数,即 32 位机器上的 32 位无符号整数。

以下2条语句:

f3(&f1); //<----------It works
f3(&f2); //<----------Compiler error

编译错误是:

no known conversion from 'void *(*)()' to 'void *' for 2nd argument;

我的问题:

  • &amp;f1应该是函数指针的地址,为什么可以把f3当成void *传给void *?
  • 为什么&amp;f2 是void *(*)()?
  • void *(*)() 到底是什么意思?

【问题讨论】:

  • 您是否 100% 确定您不是在编写 c++ 代码而不是 c?
  • &amp;f1 不是函数指针,只是指向对象的指针(它指向的对象是函数指针)...所以它可以转换为void * 就好了。另一方面,&amp;f2 是一个函数指针……它们不一定与void * 兼容。
  • C++ 的类型比 C 强得多。可以在 C 中相互隐式转换的类型可能与 C++ 不同。一个突出的例子是void *:在 C 中,它是一个通用指针,所有其他指针(指向函数的指针除外)都可以转换为它,或从它转换。在 C++ 中,如果没有显式强制转换,这是不可能的。
  • 每个指向 data 的指针都可以隐式转换为 void*。函数不是数据,但指向函数的指针才是。所以f1的地址是指向数据的指针,f2的地址不是。
  • 您正在使用 C++ 编译器进行编译。我重新标记了这个问题。知道你在写什么语言很重要。 C 和 C++ 不一样。

标签: c++ function-pointers


【解决方案1】:

没错,第一个是指向函数的指针,第二个是函数声明。

这意味着编译器对警告也是正确的。

第一次调用 (f3(&amp;f1)) 传递函数指针的地址,该地址可转换为 void *(与我之前的评论相反)。所以,不需要错误。 (它是指向函数指针的指针,因此是数据对象,而不是函数指针。省略 &amp; 会得到与第二次调用相同的错误。)

第二次调用 (f3(&amp;f2)) 传递一个指向函数的指针,函数指针和 void 指针不可相互转换。函数名前面的&amp; 是多余的——在上下文中会产生轻微的误导。您可以从函数名称中添加任意数量的*,或单个&amp;(或将它们全部省略),它们的处理方式相同——标准C 的一个更奇怪的方面。(另见Why do function pointer definitions work with any number of ampersands '&' or asterisks '*'?)

我注意到我必须使用-pedantic 来让 GCC 抱怨它。这是标准在附录 J 中记录通用扩展的结果:

J.5.7 函数指针转换

¶1 指向对象或void 的指针可以转换为指向函数的指针,从而允许数据 作为函数调用 (6.5.4)。

¶2 指向函数的指针可以转换为指向对象的指针或void,允许 要检查或修改的函数(例如,由调试器)(6.5.4)。

你问void *(*)() 是什么意思。它是一个指向函数的指针的“强制转换”形式,它接受一个不确定的参数列表(但不是一个带有省略号 ... 的可变参数列表)并返回一个指向函数的指针。


haccksasked:

能否请您从 C 标准中添加关于函数指针和 void 指针不可相互转换的参考?

是的——这很简单:

6.2.5 类型

¶1 … 类型分为对象类型(描述对象的类型)和函数类型(描述函数的类型)。

6.3 转化

6.3.2.3 指针

¶1 指向void 的指针可以转换为或从指向任何对象类型的指针转​​换。指向任何对象类型的指针可以转换为指向void 的指针并再次返回;结果应与原始指针比较。

¶7 指向对象类型的指针可以转换为指向不同对象类型的指针。如果 结果指针未正确对齐68) 对于引用的类型,行为是 不明确的。否则,当再次转换回来时,结果将等于 原始指针。当指向对象的指针转换为指向字符类型的指针时, 结果指向对象的最低寻址字节。的连续递增 结果,直到对象的大小,产生指向对象剩余字节的指针。

¶8 指向一种函数的指针可以转换为指向另一种函数的指针 键入并再次返回;结果应与原始指针比较。如果一个转换 指针用于调用类型与引用类型不兼容的函数, 行为未定义。

这些是指针类型之间唯一定义的转换。没有关于在指向对象的指针和指向函数的指针之间进行转换的内容,因此不允许这样做(但不一定需要诊断;它不在标准的“约束”部分)。

测试代码

变体 1 (pf19.c):

void (*f1)(void);
void *f2(void);

void f3(void *p_func);

int main(void)
{
    f3(&f1);
    f3(&f2);
}

编译警告(由于-Werror 和-pedantic 导致的错误):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes  -Wold-style-definition -pedantic -c pf19.c
pf19.c: In function ‘main’:
pf19.c:9:12: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
         f3(&f2); //<----------Compiler error
            ^
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void * (*)(void)’
 void f3(void *p_func);
      ^~
cc1: all warnings being treated as errors

变体 2(也是 pf19.c):

void (*f1)(void);
void *f2(void);

void f3(void *p_func);

int main(void)
{
    f3(f1);
    f3(&f2);
}

编译消息:

$ gcc -O3   -g      -std=c11   -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes  -Wold-style-definition      -pedantic -c pf19.c
pf19.c: In function ‘main’:
pf19.c:8:8: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
     f3(f1);
        ^~
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void (*)(void)’
 void f3(void *p_func);
      ^~
pf19.c:9:8: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
     f3(&f2);
        ^
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void * (*)(void)’
 void f3(void *p_func);
      ^~
cc1: all warnings being treated as errors
$

消息的措辞与 C 编译器相比 C++ 编译器不同,但意图相同(pf17.cc 是pf19.c 的简单副本):

$ g++ -O3 -g -I./inc -std=c++11 -Wall -Wextra -Werror    -c pf17.cc
pf17.cc: In function ‘int main()’:
pf17.cc:8:10: error: invalid conversion from ‘void (*)()’ to ‘void*’ [-fpermissive]
     f3(f1);
          ^
pf17.cc:4:6: note:   initializing argument 1 of ‘void f3(void*)’
 void f3(void *p_func);
      ^~
pf17.cc:9:8: error: invalid conversion from ‘void* (*)()’ to ‘void*’ [-fpermissive]
     f3(&f2);
        ^~~
pf17.cc:4:6: note:   initializing argument 1 of ‘void f3(void*)’
 void f3(void *p_func);
      ^~
$

测试:Mac OS X 10.11.6 El Capitan 上的 GCC 6.2.0。


感谢Dmitri noting §6.3.2.3 ¶1 是相关的,以及附件 J.5.7。

【讨论】:

  • 能否请您添加 C 标准中关于函数指针和 void 指针不可相互转换的参考?
  • 感谢您的参考。
  • 在 6.3.2.3 中。 1 似乎也相关:“指向 void 的指针可以转换为指向任何对象类型的指针或从指向任何对象类型的指针。指向任何对象类型的指针可以转换为指向 void 的指针并再次返回;结果应比较相等到原始指针。” 并且 3.15 将“对象”定义为 “执行环境中的数据存储区域,其内容可以表示值”...这表明函数是'不考虑对象。
  • @Dmitri:函数与标准条款中的对象绝对不同——因此关于“指向对象类型的指针”和“指向函数的指针”的单独段落。 §6.2.5 说类型分为对象类型(描述对象的类型)和函数类型(描述函数的类型)。
  • 为了清楚起见,我同意你的回答......我只是认为标准的其他部分也有助于支持它。
【解决方案2】:

如果这是 程序,则错误消息没有意义。因为在 中,每个指针都可以转换为void * 并返回而无需强制转换。您的编译器似乎认为没有办法将指针转换为 void *,这就是 的工作原理。

尽管您的问题中的 cmets 也是正确的,但您的代码应该在两种情况下都可以编译,前提是它被编译为 代码。标准严格禁止这种转换,但错误信息仍然是 c++ 错误而不是 c 编译器错误,而且从/到void */函数指针的转换是一个常见的扩展

J.5.7 函数指针转换

  1. 指向对象或 void 的指针可以转换为指向函数的指针,从而允许数据 作为函数调用 (6.5.4)。
  2. 可以将指向函数的指针转换为指向对象的指针或 void,从而允许 要检查或修改的函数(例如,由调试器)(6.5.4)。

虽然不能保证所有系统都支持这一点,但大多数系统都支持。

编译为 的可能原因是

  1. 你直接调用了c++编译器,比如g++
  2. 您使用 .cpp 扩展名而不是 .c 命名源文件

如果是 源代码,则错误是有道理的,因为 不允许此类转换,但在 c 中这可能会正确编译(并且大部分时间),即使它是严禁。

还要注意 中的函数指针不是 AFAIK 的常用方法。因为当你有一个对象时,你只能创建指向静态成员的指针,这与拥有一个指向普通函数的指针并没有太大区别。在 中,回调并不常见,实际上我在Qt 中广泛使用的库需要(在 c++11 标准之前)一个名为 moc 的工具em> 以使“回调”工作。该工具从头文件生成代码以允许回调或(它们的模拟)在 中工作。

【讨论】:

  • C 仅保证指向 objects 的指针可与void * 相互转换——这不包括函数指针...尽管能够在函数指针之间进行转换和对象指针被标记为通用扩展名。
  • 感谢 J.5.7 参考。
【解决方案3】:
  • &f1应该是函数指针的地址,为什么可以作为void *传给f3?

这是正确的,这就是 正是它成功的原因。因为指向指针的指针可以隐式转换为void *,无论其目标指向什么数据类型。

  • 为什么 &f2 是void *(*)()? void *(*)() 到底是什么意思?

您可能误解了函数指针表示法。 void *(*)() 是函数指针的符号。 void *f2(void) 是一个返回 void 指针的函数。 &amp;f2 是一个函数指针(指向该函数)。编译器告诉您它不能将函数指针隐式转换为 void *(根据 C 标准,这不能保证,而且您的编译器显然无法做到这一点)。

这是独家新闻。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-03
    • 1970-01-01
    • 2012-11-04
    • 2023-04-01
    • 2011-04-24
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    相关资源
    最近更新 更多