Как печатать указатели функций с помощью cout?
Я хочу распечатать указатель на функцию cout и обнаружил, что он не работает. Но он работал после преобразования указателя на (void *), так что printf с% p, например
#include 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 << pf равно 1
cout << (void *) pf равно 0x100000b0c
printf (“% p”, pf) равен 0x100000b0c
Итак, что делает cout с типом int (*) ()? Мне сказали, что указатель функции рассматривается как bool, правда ли это? И что делает cout с типом (void *)?
Заранее спасибо.
EDIT: Во всяком случае, мы можем наблюдать содержимое указателя функции, преобразовывая его в (void *) и распечатывая его с помощью cout. Но он не работает для указателей функций-членов, и компилятор жалуется на незаконное преобразование. Я знаю, что указатели на функции-члены представляют собой сложную структуру, отличную от простых указателей, но как мы можем наблюдать содержимое указателей на функцию-член?
- Преимущества Cache vs Session
- Лучше ли в C ++ проходить по значению или передавать постоянную ссылку?
- Ожидание против Задачи. Подождите - Тупик?
- HttpWebRequest и проверка подлинности форм в C #
- Проверка Recaptcha 2 (нет CAPTCHA reCAPTCHA) на стороне сервера ASP.NET
- Как определить платформу / компилятор из макросов препроцессора?
- Как, собственно, работает трюк с двойной строкой?
- Почему разделение двух int не дает правильного значения при присвоении двойной?
На самом деле существует перегрузка оператора <<, который выглядит примерно так:
ostream & operator <<( ostream &, const void * );
который делает то, что вы ожидаете, - выводит в шестнадцатеричном формате. Не может быть такой стандартной перегрузки библиотек для указателей на функции, поскольку они представляют собой бесконечное количество типов. Таким образом, указатель преобразуется в другой тип, который в этом случае выглядит как bool - я не могу забыть правила для этого.
Изменить: стандарт C ++ указывает:
4.12 Логические преобразования
1 Значение арифметики, enums, указателя или указателя на тип члена может быть преобразовано в rvalue типа bool.
Это единственное преобразование, указанное для указателей функций.
Что касается вашего редактирования, вы можете распечатать содержимое чего-либо, обратившись к нему с помощью указателя unsigned char
. Пример указателей на функции-члены:
#include #include 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(&p); unsigned char const * last = reinterpret_cast (&p + 1); for (; first != last; ++first) { std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)*first << ' '; } std::cout << std::endl; }
Вы можете думать о указателе функции как о адресе первой инструкции в машинный код этой функции. Любой указатель можно рассматривать как bool
: 0 – false, а все остальное – true. Как вы заметили, при передаче в void *
и задании в качестве аргумента для оператора вставки streamа ( <<
), адрес печатается. (Строго просматривается, отбрасывание указателя на функцию void *
не определено).
Без броска история немного сложна. Для сопоставления перегруженных функций («разрешение перегрузки») компилятор C ++ собирает набор функций-кандидатов, и из этих кандидатов выбирает «наилучшую жизнеспособную», используя при необходимости неявные преобразования. Морщина - это правила сопоставления с частичным порядком, поэтому множественные наиболее эффективные совпадения вызывают ошибку двусмысленности.
В порядке предпочтения стандартные конверсии (и, конечно, там также определенные пользователем и многоточие преобразования, а не подробные) являются
- точное соответствие ( т. е. никакого преобразования не требуется)
- продвижение ( например ,
int
tofloat
) - другие конверсии
Последняя категория включает в себя логические преобразования, и любой тип указателя может быть преобразован в bool
: 0 (или NULL
) - false
а все остальное - true
. Последний отображается как 1
при передаче оператору вставки streamа.
Чтобы вместо этого получить 0
, измените вашу инициализацию на
pf = 0;
Помните, что инициализация указателя нулевым значением константы дает нулевой указатель.
Указатели каста на (void*)
для печати их в cout
– это правильная вещь (TM), чтобы делать на C ++, если вы хотите увидеть их значения.
Что касается вашего конкретного вопроса,
как мы можем наблюдать содержимое указателей на функцию-член?
Ответ заключается в том, что вместо того, чтобы преобразовывать их в bool, чтобы выразить, что он указывает на что-то или нет, вы не можете указывать функции-наблюдатели. По крайней мере, не соответствующим образом. Причина в том, что стандарт явно запрещает это:
4.12 сноска 57:
57) Правило преобразования указателей в члены (от указателя к члену базы до указателя на член производного) представляется инвертированным по сравнению с правилом для указателей на объекты (от указателя к производному от указателя к базе) (4.10, п. 10) , Эта инверсия необходима для обеспечения безопасности типа. Обратите внимание, что указатель на элемент не является указателем на объект или указателем на функцию, а правила для преобразования таких указателей не применяются к указателям на члены. В частности, указатель на член не может быть преобразован в void *.
Например, вот пример кода:
#include #include #include #include #include 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
), отбрасывая это на пустоту * и оттуда. Это также потребует, чтобы вы знали размер указателей (разные на разных платформах).
Но я бы спросил, до тех пор, пока вы можете преобразовать указатель функции-члена в bool и оценить существование указателя, почему в реальном коде вам нужен адрес?
Предположительно, ответ на последний вопрос «так что я могу определить, указывает ли один указатель на функцию на ту же функцию, что и на другой». Справедливо. Вы можете сравнить указатели на функции для равенства:
#include #include #include #include #include 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; }
В C ++ 11 можно было бы изменить это поведение, указав переменную шаблонную перегрузку operator<<
(то, что это рекомендуется или нет, является другой темой):
#include namespace function_display{ template 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" }