Как напечатать указатели на функции cout? - PullRequest
48 голосов
/ 14 января 2010

Я хочу напечатать указатель на функцию, используя 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 <br> cout << (void *) pf равно 0x100000b0c <br> printf ("% p", pf) - 0x100000b0c

Так что же делает cout с типом int (*) ()? Мне сказали, что указатель на функцию обрабатывается как bool, это правда? И что cout делает с типом (void *)?

Заранее спасибо.

РЕДАКТИРОВАТЬ: Во всяком случае, мы можем наблюдать содержимое указателя функции путем преобразования его в (void *) и распечатать его с помощью cout. Но это не работает для указателей на функции-члены, и компилятор жалуется на нелегальное преобразование. Я знаю, что указатели на функции-члены представляют собой довольно сложную структуру, отличную от простых указателей, но как мы можем наблюдать содержимое указателей на функции-члены?

Ответы [ 6 ]

41 голосов
/ 14 января 2010

На самом деле перегружен оператор <<, который выглядит примерно так: </p>

ostream & operator <<( ostream &, const void * );

, который делает то, что вы ожидаете - выводит в шестнадцатеричном виде. Не может быть такой стандартной перегрузки библиотеки для указателей на функции, потому что их бесконечное количество типов. Таким образом, указатель преобразуется в другой тип, который в данном случае выглядит как бул - я не могу запоминать правила для этого.

Редактировать: Стандарт C ++ указывает:

4.12 Булевы преобразования

1 Значение арифметики, перечисление, указатель или указатель на тип члена может быть преобразован в Значение типа bool.

Это единственное преобразование, указанное для указателей функций.

11 голосов
/ 14 января 2010

Относительно ваших правок вы можете распечатать содержимое чего угодно, открыв к нему указатель 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;
}
6 голосов
/ 14 января 2010

Вы можете рассматривать указатель функции как адрес первой инструкции в машинном коде этой функции. Любой указатель может быть обработан как bool: 0 ложно, а все остальное верно. Как вы заметили, при приведении к void * и задании в качестве аргумента оператора вставки потока (<<) адрес печатается. (При строгом рассмотрении приведение указателя на функцию к void * не определено.)

Без актеров история немного сложна. Для сопоставления перегруженных функций («разрешение перегрузки») компилятор C ++ собирает набор функций-кандидатов и из этих кандидатов выбирает «наиболее жизнеспособную», используя при необходимости неявные преобразования. Проблема заключается в том, что правила сопоставления образуют частичный порядок, поэтому множественные сопоставления с наилучшими показателями вызывают ошибку неоднозначности.

В порядке предпочтения, стандартные преобразования (и, конечно, есть также пользовательские и многоточечные преобразования, не детализированные):

  • точное совпадение (, т.е. , преобразование не требуется)
  • повышение ( например, , int до float)
  • другие преобразования

Последняя категория включает в себя логические преобразования, и любой тип указателя может быть преобразован в bool: 0 (или NULL) равен false, а все остальное - true. Последний отображается как 1 при передаче оператору вставки потока.

Чтобы получить 0, измените инициализацию на

pf = 0;

Помните, что инициализация указателя с константным выражением с нулевым значением дает нулевой указатель.

2 голосов
/ 18 ноября 2013

В 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"
}
2 голосов
/ 14 января 2010

Приведение указателей к (void*) для их печати на cout - правильная вещь (ТМ), которую нужно делать в C ++, если вы хотите увидеть их значения.

1 голос
/ 14 января 2010

Относительно вашего конкретного вопроса,

как мы можем наблюдать за содержанием указатели на функции-члены?

Ответ таков: кроме преобразования их в 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...