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

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

Спасибо заранее.

EDIT: Во всяком случае, мы можем наблюдать содержимое указателя функции, преобразовывая его в (void *) и распечатывая его с помощью cout. Но он не работает для указателей функций-членов, и компилятор жалуется на незаконное преобразование. Я знаю, что указатели на функции-члены представляют собой сложную структуру, отличную от простых указателей, но как мы можем наблюдать содержимое указателей на функции-члены?

Ответ 1

На самом деле существует перегрузка < оператор выглядит примерно так:

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

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

Изменить: Стандарт С++ указывает:

4.12 Логические преобразования

1 Значение арифметики, перечисление, указатель или указатель на тип члена может быть преобразован в rvalue типа 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 - false, а все остальное - true. Как вы заметили, при нажатии на void * и заданной в качестве аргумента для оператора вставки потока (<<), адрес печатается. (Строго просматривается, приведение указателя к функции в void * составляет undefined.)

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

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

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

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

Чтобы вместо 0 изменить свою инициализацию на

pf = 0;

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

Ответ 4

Указатели на (void*) для печати на cout - это правильная вещь (TM), которая должна выполняться на С++, если вы хотите увидеть их значения.

Ответ 5

В С++ 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"
}

Ответ 6

Что касается вашего конкретного вопроса,

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

Ответ: кроме преобразования их в 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;
}

Ответ 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 использовать регистр для входного аргумента)).