Как напечатать указатели на функции cout?
Я хочу распечатать указатель на функцию, используя 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 << pf is 1
cout << (void *) pf равно 0x100000b0c
printf ("% p", pf) - 0x100000b0c
Так что же делать cout с типом int (*)()? Мне сказали, что указатель на функцию обрабатывается как bool, это правда? И что cout делает с типом (void *)?
Заранее спасибо.
РЕДАКТИРОВАТЬ: Во всяком случае, мы можем наблюдать содержимое указателя функции, преобразовав его в (void *) и распечатать его с помощью cout. Но это не работает для указателей на функции-члены, и компилятор жалуется на незаконное преобразование. Я знаю, что указатели на функции-члены представляют собой довольно сложную структуру, отличную от простых указателей, но как мы можем наблюдать за содержимым указателей на функции-члены?
6 ответов
На самом деле перегружен оператор <<, который выглядит примерно так:
ostream & operator <<( ostream &, const void * );
который делает то, что вы ожидаете - выводит в шестнадцатеричном виде. Не может быть такой стандартной перегрузки библиотеки для указателей на функции, потому что их бесконечное количество типов. Таким образом, указатель преобразуется в другой тип, который в данном случае выглядит как бул - я не могу запоминать правила для этого.
Изменить: Стандарт C++ определяет:
4.12 Булевы преобразования
1 Значение арифметики, перечисления, указателя или указателя на тип члена может быть преобразовано в значение типа bool.
Это единственное преобразование, указанное для указателей на функции.
Что касается вашего редактирования, вы можете распечатать содержимое чего-либо, открыв доступ к нему через 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;
}
Вы можете рассматривать указатель на функцию как адрес первой инструкции в машинном коде этой функции. Любой указатель может рассматриваться как bool
: 0 ложно, а все остальное верно. Как вы заметили, когда бросили void *
и передается в качестве аргумента оператору вставки потока (<<
), адрес печатается. (При строгом рассмотрении приведение указателя на функцию к void *
не определено.)
Без актерского состава история немного сложна. Для сопоставления перегруженных функций ("разрешение перегрузки") компилятор C++ собирает набор функций-кандидатов и из этих кандидатов выбирает "наиболее жизнеспособную", используя при необходимости неявные преобразования. Морщина заключается в том, что правила сопоставления образуют частичный порядок, поэтому множественные наилучшие жизненные совпадения вызывают ошибку неоднозначности.
В порядке предпочтения стандартные преобразования (и, конечно, также определенные пользователем преобразования и преобразования эллипса, не детализированные)
- точное совпадение (т. е. преобразование не требуется)
- продвижение (например,
int
вfloat
) - другие преобразования
Последняя категория включает в себя логические преобразования, и любой тип указателя может быть преобразован в bool
: 0 (или NULL
) является false
а все остальное есть true
, Последний проявляется как 1
при передаче оператору вставки потока.
Получить 0
вместо этого измените свою инициализацию на
pf = 0;
Помните, что инициализация указателя с константным выражением с нулевым значением дает нулевой указатель.
В 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"
}
Приведение указателей к (void*)
напечатать их cout
это правильно (ТМ) делать в С ++, если вы хотите увидеть их значения.
Что касается вашего конкретного вопроса,
Как мы можем наблюдать содержимое указателей на функции-члены?
Ответ заключается в том, что кроме преобразования их в bool для выражения того, что оно указывает на что-то или нет, вы не можете указатели на функции-члены "наблюдателя". По крайней мере, не в соответствии с требованиями. Причина в том, что стандарт явно запрещает это:
4.12 сноска 57:
57) Правило для преобразования указателей на элементы (от указателя на элемент базы к указателю на член производного) выглядит инвертированным по сравнению с правилом для указателей на объекты (от указателя на производное к указателю на базу) (4.10, пункт 10), Эта инверсия необходима для обеспечения безопасности типов. Обратите внимание, что указатель на член не является указателем на объект или указателем на функцию, и правила преобразования таких указателей не применяются к указателям на члены. В частности, указатель на член не может быть преобразован в void*.
Например, вот пример кода:
#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;
}
Может быть (однажды я пересекаюсь с адресом функции) одно из решений)))
#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
использовать регистр для ввода аргумента)).