如何用 cout 打印函数指针?
Posted
技术标签:
【中文标题】如何用 cout 打印函数指针?【英文标题】:How to print function pointers with cout? 【发布时间】:2010-01-14 14:22:26 【问题描述】:我想使用 cout 打印出一个函数指针,发现它不起作用。 但是在我将函数指针转换为(void *)后它起作用了,printf也是如此,带有%p,例如
#include <iostream>
using namespace std;
int foo() return 0;
int main()
int (*pf)();
pf = foo;
cout << "cout << pf is " << pf << endl;
cout << "cout << (void *)pf is " << (void *)pf << endl;
printf("printf(\"%%p\", pf) is %p\n", pf);
return 0;
我用g++编译得到如下结果:
cout cout printf("%p", pf) 为 0x100000b0c
那么 cout 对 int (*)() 类型做了什么?有人告诉我函数指针被视为布尔值,是真的吗? cout 对 type (void *) 做了什么?
提前致谢。
编辑:无论如何,我们可以通过将函数指针转换为 (void *) 并使用 cout 打印出来来观察函数指针的内容。 但它不适用于成员函数指针,编译器抱怨非法转换。我知道成员函数指针不是简单的指针,而是一个比较复杂的结构,但是如何观察成员函数指针的内容呢?
【问题讨论】:
【参考方案1】:实际上有一个
ostream & operator <<( ostream &, const void * );
符合您的预期 - 以十六进制输出。函数指针不可能有这样的标准库重载,因为它们有无数种类型。所以指针被转换为另一种类型,在这种情况下它似乎是一个 bool - 我不能随便记住这个规则。
编辑: C++ 标准规定:
4.12 布尔转换
1 算术右值, 枚举、指针或指向的指针 成员类型可以转换为 bool 类型的右值。
这是为函数指针指定的唯一转换。
【讨论】:
+1。适用于函数指针的唯一标准转换是(除了左值到右值的转换)到bool
的转换。值得注意的是,您必须执行reinterpret_cast
才能将int (*)()
转换为void *
。
@avakar 有没有关于函数指针转换规则的文档?
最终文档将是 C++ 标准,第 4[conv] 和 5.2[expr.post] 部分。该标准不是免费的,但您可以在此处获取最新草案:open-std.org/jtc1/sc22/wg21/docs/papers/2009/n3000.pdf。请注意,如果您不习惯它可能难以阅读。
@Neil:void *
是唯一的转换,但还有一点要记住:操纵器是您将其地址传递给流的函数。流调用它们,而不是转换它们;如果您将指针传递给具有类似操纵器的签名的函数,则流会将其视为操纵器,并调用该函数而不是尝试转换其地址。
@Neil Butterworth @avkar 非常感谢您的帮助。所以 cout 会在发现没有任何其他类型可以转换后将函数指针视为 bool ,对吧?【参考方案2】:
关于您的编辑,您可以通过unsigned char
指针访问它来打印任何内容。指向成员函数的指针示例:
#include <iostream>
#include <iomanip>
struct foo virtual void bar() ;
struct foo2 ;
struct foo3 : foo2, foo virtual void bar() ;
int main()
void (foo3::*p)() = &foo::bar;
unsigned char const * first = reinterpret_cast<unsigned char *>(&p);
unsigned char const * last = reinterpret_cast<unsigned char *>(&p + 1);
for (; first != last; ++first)
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< (int)*first << ' ';
std::cout << std::endl;
【讨论】:
非常感谢!现在我知道如何观察成员函数指针的内容了。【参考方案3】:您可以将函数指针视为该函数机器代码中第一条指令的地址。任何指针都可以被视为bool
:0 为假,其他一切为真。正如您所观察到的,当转换为 void *
并作为流插入运算符 (<<
) 的参数给出时,将打印地址。 (严格来说,将指向函数的指针强制转换为 void *
是未定义的。)
没有演员,故事有点复杂。为了匹配重载函数(“重载解析”),C++ 编译器收集一组候选函数,并从这些候选函数中选择“最佳可行”函数,必要时使用隐式转换。皱纹是匹配规则形成偏序,因此多个最佳可行匹配会导致歧义错误。
按照优先顺序,标准转换(当然还有用户定义和省略号转换,不详述)是
完全匹配(即,无需转换) 促销(例如,int
到 float
)
其他转化
最后一类包括布尔转换,任何指针类型都可以转换为bool
:0(或NULL
)是false
,其他都是true
。后者在传递给流插入运算符时显示为1
。
要获取0
,请将您的初始化更改为
pf = 0;
请记住,使用零值常量表达式初始化指针会产生空指针。
【讨论】:
所以你的意思是编译器倾向于将任何没有已知类型的指针视为 bool? @curiousguy 呃...实际上我的意思是当编译器找不到作为给定参数调用的函数的确切签名时,例如在这个特定示例中的函数指针,编译器将尝试将其转换为 bool,从而匹配带有 bool 类型参数的重载版本。 @ibread 实际上编译器总是会考虑所有可能的候选函数;如果有一个函数采用正确的函数类型,它将是“最佳”候选者。由于没有这样的函数,唯一的候选者是采用bool
的函数。【参考方案4】:
在 C++11 中,可以通过定义 operator<<
的可变参数模板重载来修改此行为(是否推荐是另一个主题):
#include<iostream>
namespace function_display
template<class Ret, class... Args>
std::ostream& operator <<(std::ostream& os, Ret(*p)(Args...) ) // star * is optional
return os << "funptr " << (void*)p;
// example code:
void fun_void_void();
void fun_void_double(double d);
double fun_double_double(double d)return d;
int main()
using namespace function_display;
// ampersands & are optional
std::cout << "1. " << &fun_void_void << std::endl; // prints "1. funptr 0x40cb58"
std::cout << "2. " << &fun_void_double << std::endl; // prints "2. funptr 0x40cb5e"
std::cout << "3. " << &fun_double_double << std::endl; // prints "3. funptr 0x40cb69"
【讨论】:
您还需要一个重载,它将采用 C 风格的可变参数函数(带有尾随省略号)和八个用于指向成员函数的指针的重载,每个都可以是不合格的,const
,@987654324 @ 或 const volatile
并且可能有也可能没有尾随省略号。
@Brian,您的意思是要涵盖指向函数的 const 指针的情况吗?喜欢double(const *ptr)(double) = &fun_double_double;
? (请随时编辑答案以澄清)。
您不能将const
放在那个位置。如果你想要一个指向函数的常量指针,你可能想把它放在ptr
之前。无论如何,您的答案已经涵盖了这种情况,但我的意思是double (T::*ptr)(double) const
。
@Brian,是的,根据我的测试,const
被覆盖(实际上const
与否,它是相同的功能,const
是多余的)。是的,没有涵盖指向成员函数的指针。我解释说这不是问题的一部分。但我现在将其添加到答案中。
@Brian,试图概括解决方案,但我不能 template<class Ret, class T, class... Args> std::ostream& operator <<(std::ostream& os, Ret(T::* const p)(Args...) const) // first const ins optional return os << "memfunptr const " << typeid(T).name() <<"::"<< reinterpret_cast<size_t const*>(&p);
但基本上所有不同的成员函数都打印相同的数字。【参考方案5】:
如果您想查看它们的值,则将指向 (void*)
的指针转换为 cout
是正确的 (TM) 做法。
【讨论】:
这是正确的做法,当然,除非它根本不起作用。不幸的是,不能保证指向代码的指针与指向数据的指针真正兼容。一个经典的例子(现在非常经典)是在 MS-DOS 下的“中等”内存模型中,指向数据的指针只有 16 位,但指向代码的指针是 32 位。 @JerryCoffinunsigned long
, unsigned long long
也可以使用。【参考方案6】:
关于您的具体问题,
我们如何观察a的内容 成员函数指针?
答案是,除了将它们转换为 bool 以表示它指向或不指向某物外,您不能“观察”成员函数指针。至少不是以合规的方式。原因是标准明确不允许这样做:
4.12 脚注 57:
57) 转换规则 指向成员的指针(从指针到 基成员指向指向成员的指针 派生)与 指向对象的指针规则(从 派生的指针指向基的指针) (4.10,第 10 条)。这种反转是 确保类型安全所必需的。笔记 指向成员的指针不是 指向对象的指针或指向 函数和转换规则 此类指针不适用于 指向成员的指针。 特别是, 指向成员的指针无法转换 到一个空洞*。
例如,这里是示例代码:
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
class Gizmo
public:
void DoTheThing()
return;
;
private:
int foo_;
;
int main()
void(Gizmo::*fn)(void) = &Gizmo::DoTheThing;
Gizmo g;
(g.*fn)(); // once you have the function pointer, you can call the function this way
bool b = fn;
// void* v = (void*)fn; // standard explicitly disallows this conversion
cout << hex << fn;
return 0;
我注意到我的调试器 (MSVC9) 能够在运行时告诉我成员函数的实际物理地址,所以我知道必须有某种方法来实际获取该地址。但我确信它是不符合标准的、不可移植的,并且可能涉及机器代码。如果我要走这条路,我会先获取函数指针的地址(例如&fn
),然后将其转换为void*,然后从那里开始。这还需要您知道指针的大小(在不同平台上有所不同)。
但我会问,只要你可以将成员函数指针转换为 bool 并评估指针的存在,为什么在实际代码中你需要地址?
大概最后一个问题的答案是“这样我就可以确定一个函数指针是否指向与另一个相同的函数”。很公平。您可以比较函数指针是否相等:
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
class Gizmo
public:
void DoTheThing()
return;
;
**void DoTheOtherThing()
return;
;**
private:
int foo_;
;
int main()
void(Gizmo::*fn)(void) = &Gizmo::DoTheThing;
Gizmo g;
(g.*fn)(); // once you have the function pointer, you can call the function this way
bool b = fn;
// void* v = (void*)fn; // standard explicitly disallows this conversion
cout << hex << fn;
**void(Gizmo::*fnOther)(void) = &Gizmo::DoTheOtherThing;
bool same = fnOther == fn;
bool sameIsSame = fn == fn;**
return 0;
【讨论】:
@John Dibling 非常感谢您的回答。其实我只是对成员函数指针的内容很好奇,所以我想打印出来看看。 :) 而且,有几个“**”会导致编译器抱怨。 ;) 您引用的部分标准与所要求的答案无关。 指向成员的指针与指向方法的指针(即指向成员函数的指针)完全不同。 @SergeDundich:您的评论毫无意义——指向成员函数的指针是指向成员的指针(另一种指向成员的指针是指向非静态数据成员的指针)。所有指向成员的指针(函数和数据)都以相同的方式形成,使用&Type::member
,并使用相同的方式,使用.*
或->*
。【参考方案7】:
也许(有一次我对函数的地址保持交叉) 决定之一)))
#include <iostream>
#include <stdlib.h>
void alexf();
int main()
int int_output;
printf("printf(\"%%p\", pf) is %p\n", alexf);
asm( "movl %[input], %%eax\n"
"movl %%eax, %[output]\n"
: [output] "+m" (int_output)
: [input] "r" (&alexf)
: "eax", "ebx"
);
std::cout<<"" <<std::hex<<int_output <<""<<std::endl;
return 0;
void alexf()
将指针传递给函数(&alexf)
或使用&
的其他指针使用约束r
。让gcc
使用寄存器作为输入参数))。
【讨论】:
以上是关于如何用 cout 打印函数指针?的主要内容,如果未能解决你的问题,请参考以下文章