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

Другие вопросы по тегам