2010-01-14 13 views
40

Voglio stampare un puntatore a funzione usando cout, e ho trovato che non ha funzionato. ma ha funzionato dopo che ho convertire il puntatore a funzione di (void *), in modo da non printf con% p, come ad esempioCome stampare i puntatori di funzione con cout?

#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; 
} 

ho compilato con g ++ ed ho ottenuto risultati come questo:

cout < < pf è 1
cout < < (void *) pf è 0x100000b0c
printf ("% p", pf) è 0x100000b0c

Quindi cosa fa cout con tipo int (*)()? Mi è stato detto che il puntatore della funzione è trattato come bool, è vero? E cosa fa il cout con type (void *)?

Grazie in anticipo.

MODIFICA: In ogni caso, possiamo osservare il contenuto di un puntatore di funzione convertendolo in (void *) e stamparlo usando cout. Ma non funziona per i puntatori di funzioni dei membri e il compilatore si lamenta della conversione illegale. So che i puntatori a funzioni dei membri sono piuttosto una struttura complicata diversa dai semplici puntatori, ma come possiamo osservare il contenuto di un puntatore di funzioni membro?

risposta

38

In realtà c'è un sovraccarico del < < all'operatore che sembra qualcosa di simile:

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

che fa quello che ci si aspetta - uscite in esadecimale. Non ci può essere un tale sovraccarico di libreria standard per i puntatori di funzione, perché sono un numero infinito di tipi di essi. Quindi il puntatore viene convertito in un altro tipo, che in questo caso sembra essere un bool - Non posso ricordare a caso le regole per questo.

Edit: Visual C++ specifica:

4.12 conversioni booleane

1 Un rvalue dell'aritmetica, enumerazione, puntatore o puntatore a Tipo di utente può essere convertito in un rvalue di tipo bool.

Questa è l'unica conversione specificata per i puntatori di funzione.

+4

+1. L'unica conversione standard applicabile a un puntatore alla funzione è (salva la conversione da lvalue a rvalue) la conversione in 'bool'. In particolare, devi eseguire 'reinterpret_cast' per convertire' int (*)() 'in' void * '. – avakar

+0

@avakar Esiste un documento che copre le regole di conversione dei puntatori di funzione? – ibread

+3

Il documento finale sarebbe lo standard C++, sezioni 4 [conv] e 5.2 [expr.post]. Lo standard non è gratuito, ma puoi ottenere l'ultima bozza qui: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n3000.pdf. Fai attenzione che può essere difficile da leggere se non ci sei abituato. – avakar

4

Si può pensare a un puntatore a funzione come l'indirizzo della prima istruzione nel codice macchina di quella funzione. Qualsiasi puntatore può essere trattato come bool: 0 è falso e tutto il resto è vero. Come è stato osservato, quando si esegue il cast su void * e fornito come argomento all'operatore di inserimento dello stream (<<), l'indirizzo viene stampato. (Visto rigorosamente, il lancio di un puntatore a funzione su void * non è definito.)

Senza il cast, la storia è un po 'complessa. Per la corrispondenza delle funzioni sovraccaricate ("overload resolution"), un compilatore C++ raccoglie una serie di funzioni candidate e da questi candidati seleziona la "migliore possibile", utilizzando le conversioni implicite, se necessario. La ruga è che le regole di corrispondenza formano un ordine parziale, quindi più corrispondenze più valide causano un errore di ambiguità.

In ordine di preferenza, le conversioni standard (e, naturalmente, ci sono anche definiti dall'utente e ellissi conversioni, non dettagliati) sono

  • corrispondenza esatta (cioè, senza conversione necessaria)
  • promozione (es , int a float)
  • altre conversioni

L'ultima categoria include le conversioni booleane e qualsiasi tipo di puntatore può essere convertito in bool: 0 (o NULL) è false e tutto il resto è true. Quest'ultimo si presenta come 1 quando viene passato all'operatore di inserimento del flusso.

Per ottenere 0 invece, cambiare la vostra inizializzazione

pf = 0; 

Ricordate che l'inizializzazione di un puntatore con un'espressione costante zero valore cede il puntatore nullo.

+0

Quindi vuoi dire che il compilatore tende a trattare qualsiasi puntatore senza un tipo noto come bool? – ibread

+0

@ibread "_known type_" cos'è un tipo noto? – curiousguy

+0

@curiousguy Uh ... In realtà intendo quando il compilatore non riesce a trovare la firma esatta della funzione chiamata come argomento dato, ad esempio un puntatore di funzione in questo specifico esempio, il compilatore proverà quindi a convertirlo in bool e quindi corrisponderà una versione sovraccaricata con argomento di tipo bool. – ibread

0

I puntatori di lancio su (void*) per stamparli su cout sono la cosa giusta (TM) da fare in C++ se si desidera vedere i loro valori.

+0

È la cosa giusta da fare, tranne ovviamente dove non funziona affatto. Sfortunatamente, non c'è alcuna garanzia che un puntatore al codice sia realmente compatibile con un puntatore ai dati. Un esempio classico (veramente classico, al giorno d'oggi) era nel modello di memoria "medio" sotto MS-DOS, dove un puntatore ai dati era solo di 16 bit, ma un puntatore al codice era di 32 bit. –

+0

@JerryCoffin 'unsigned long', 'unsigned long long' può essere usato anche. – curiousguy

8

Per quanto riguarda la modifica, è possibile stampare il contenuto di qualsiasi cosa accedendovi tramite il puntatore unsigned char. Un esempio per i puntatori a funzioni membro:

#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; 
} 
+0

Grazie mille! Ora so come osservare il contenuto del puntatore della funzione membro. – ibread

0

quanto riguarda la tua domanda specifica,

come possiamo osservare il contenuto di un puntatori a funzione membro?

La risposta è, oltre a convertirli in bool per esprimere che punta a qualcosa o non lo fa, non si possono 'puntatori di funzioni membro' osservatore '. Almeno non in modo conforme. La ragione è che la norma non ammette esplicitamente:

4.12 nota 57:

57) La regola di conversione di puntatori a membri (da puntatore a membro di base per puntatore a membro della derivato) appare invertita rispetto a la regola per i puntatori agli oggetti (dal puntatore derivato al puntatore alla base) (4.10, clausola 10). Questa inversione è necessaria per garantire la sicurezza del tipo. Nota che un puntatore al membro non è un puntatore a oggetto o un puntatore alla funzione e le regole per le conversioni di tali puntatori non si applicano ai puntatori ai membri.In particolare, un puntatore del membro non può essere convertito in un void *.

Per esempio, qui è il codice di esempio:

#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; 
} 

constato che il mio debugger (MSVC9) è in grado di dirmi l'indirizzo fisico reale della funzione di membro a tempo di esecuzione, quindi so che c'è deve essere un modo per ottenere effettivamente quell'indirizzo Ma sono sicuro che non è conforme, non portatile e probabilmente coinvolge codice macchina. Se dovessi percorrere questa strada, inizierei prendendo l'indirizzo del puntatore della funzione (es. &fn), lanciando quello per annullare *, e andare da lì. Ciò richiederebbe anche conoscere la dimensione dei puntatori (diversi su piattaforme diverse).

Ma vorrei chiedere, fintanto che è possibile convertire il puntatore della funzione membro in bool e valutare l'esistenza del puntatore, perché nel codice reale avresti bisogno dell'indirizzo?

Presumibilmente la risposta all'ultima domanda è "così posso determinare se un puntatore a funzione punta alla stessa funzione di un altro." Giusto. È possibile confrontare i puntatori a funzione per l'uguaglianza:

#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; 
} 
+0

@John Dibling Grazie mille per la risposta. In realtà, sono solo curioso del contenuto del puntatore della funzione membro ed è per questo che voglio stamparlo per il check-out. :) E ci sono diversi "**" che potrebbero causare lamentele al compilatore. ;) – ibread

+0

Parte dello standard che stai citando non ha nulla a che fare con la risposta richiesta. _Pointer to member_ è totalmente diverso da _pointer a method_ (ovvero _pointer to member function_). –

0

In C++ 11 si potrebbe modificare questo comportamento definendo un sovraccarico di modello variadic di operator<< (se questo sia raccomandabile o non è un altro argomento):

#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" 
} 
+0

È inoltre necessario un sovraccarico che utilizzi una funzione variadica in stile C (con i suoi ellissi finali) e otto overload per i puntatori alle funzioni membro, ognuna delle quali può essere non qualificata, 'const',' volatile' o 'const volatile' e potrebbe o meno avere un'ellissi finale. – Brian

+0

@Brian, intendi coprire il caso di un puntatore const per funzionare? come 'double (const * ptr) (double) = & fun_double_double;'? (sentiti libero di modificare la risposta per chiarire). – alfC

+0

Non puoi mettere 'const' in quella posizione. Dovresti metterlo appena prima di 'ptr', se vuoi che un puntatore costante funzioni. Comunque, quel caso è già coperto dalla tua risposta, ma intendo qualcosa come 'double (T :: * ptr) (double) const'. – Brian

Problemi correlati