关于c++函数指针的混淆

Confusion about C++ function pointer

本文关键字:指针 c++ 函数 关于      更新时间:2023-10-16

我的源文件名以cpp结尾

下面是两个声明:

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;

我的问题:

  • &f1应该是一个函数指针的地址,为什么它可以传递给f3作为void * ?
  • 为什么&f2void *(*)() ?
  • void *(*)()到底是什么意思?

你是对的,第一个是指向函数的指针,第二个是函数声明。

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

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

第二个调用(f3(&f2))传递一个指向函数的指针,函数指针和void指针不能相互转换。函数名前面的&是多余的——在上下文中会有轻微的误导。您可以在函数名中添加任意数量的*或单个&(或全部省略它们),并且它们被视为相同的-这是标准c的一个更奇怪的方面(参见为什么函数指针定义可以使用任意数量的& &或星号* ?)

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

J.5.7函数指针类型转换

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

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


haccks问道:

你能从C标准中添加一个关于函数指针和void指针不能相互转换的参考吗?

是的,这很简单:

6.2.5类型类型分为对象类型(描述对象的类型)和函数类型(描述函数的类型)。

6.3转换

6.3.2.3指针指向void的指针可以转换为指向任何对象类型的指针或从指针转换为指向任何对象类型的指针。指向任何对象类型的指针都可以转换为指向void的指针,然后再转换回来;结果应与原指针相等。 指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针没有正确对齐68)对于引用类型,行为是未定义的。否则,当再次转换回来时,结果将比较等于原来的指针。当对象指针转换为字符类型指针时,结果指向对象的最低寻址字节。的连续增量结果,不超过对象的大小,生成指向该对象剩余字节的指针。 指向一种类型函数的指针可以转换为指向另一种类型函数的指针再打一遍;结果应该与原始指针相等。如果被转换指针用于调用类型与引用类型不兼容的函数,行为未定义。

是指针类型之间唯一定义的转换。这里没有关于对象指针和函数指针之间的转换,所以它是不允许的(但不一定需要诊断;它不在标准的"约束"部分中)。

测试代码

变型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.ccpf19.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注意到§6.3.2.3¶1是相关的,以及附录J.5.7.

如果这是一个c程序,则错误消息没有意义。因为在c语言中,每个指针都可以转换为void *并返回,而不需要强制转换。你的编译器似乎认为没有办法从指针转换为void *,这就是c++的工作方式。

虽然对你的问题的评论是正确的,你的代码应该编译在这两种情况下提供它被编译为c代码。标准严格禁止这种转换,但错误信息仍然是c++错误而不是c编译器错误,并且从/到void */函数指针的转换是一个常见的扩展

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

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

作为c++编译的可能原因是

    直接调用c++编译器,例如:g++ 你用.cpp扩展名命名源文件,而不是.c

如果是c++源代码,这个错误是有意义的,因为c++不允许这样的转换,但在c中,这可能会正确编译(并且大多数时候),尽管这是严格禁止的。

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

  • &f1应该是一个函数指针的地址,为什么它可以传递给f3作为void *?

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

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

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

这就是独家新闻