2010-01-14 13 views
40

Chcę wydrukować wskaźnik funkcji za pomocą cout i okazało się, że nie działa. Ale to działało po I konwersji wskaźnik funkcyjny (void *), więc nie printf z% p, takie jakJak wydrukować wskaźniki funkcji z 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; 
} 

Skompilowałem go z g ++ i mam wyniki tak:

cout < < PF 1
cout < < (void *) PF 0x100000b0c
printf ("% s", PF) jest 0x100000b0c

Co więc robi cout z typem int (*)()? Powiedziano mi, że wskaźnik funkcji jest traktowany jako bool, czy to prawda? A co robi cout z typem (void *)?

Z góry dziękuję.

EDYCJA: W każdym razie możemy obserwować zawartość wskaźnika funkcji, przekształcając go w (void *) i drukować za pomocą cout. Ale to nie działa dla wskaźników funkcji członkowskich, a kompilator narzeka na nielegalną konwersję. Wiem, że wskaźniki funkcji członków są raczej skomplikowaną strukturą niż proste wskaźniki, ale jak możemy obserwować zawartość wskaźników funkcji członka?

Odpowiedz

38

Tam faktycznie jest przeciążenie operatora < <, który wygląda mniej więcej tak:

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

który robi to, czego oczekują - wyjść w hex. Nie może być przeciążenia standardowej biblioteki dla wskaźników funkcji, ponieważ są one nieskończoną liczbą typów. Tak więc wskaźnik zostaje przekonwertowany na inny typ, który w tym przypadku wydaje się być boolem - nie mogę z góry zapamiętać reguł dla tego.

Edycja: C++ Określono:

4.12 logiczne konwersji

1 rvalue arytmetyczne, wyliczenia wskaźnika lub wskaźnik do typu człon może być przekształcony w rvalue typu bool.

Jest to jedyna konwersja określona dla wskaźników funkcji.

+4

+1. Jedyną standardową konwersją stosowaną do wskaźnika do funkcji jest (z wyjątkiem konwersji l-wartość do-r) konwersja do 'bool'. W szczególności musisz wykonać 'reinterpret_cast', aby przekształcić' int (*)() 'na' void * '. – avakar

+0

@avakar Czy istnieje jakiś dokument obejmujący zasady konwersji wskaźników funkcji? – ibread

+3

Ostatecznym dokumentem byłby standard C++, sekcje 4 [konw.] I 5.2 [expr.post]. Standard nie jest bezpłatny, ale najnowszą wersję można pobrać tutaj: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n3000.pdf. Pamiętaj, że czytanie może być trudne, jeśli nie jesteś do tego przyzwyczajony. – avakar

4

Możesz myśleć o wskaźniku funkcji jako o adresie pierwszej instrukcji w kodzie maszynowym tej funkcji. Każdy wskaźnik może być traktowany jako bool: 0 jest fałszywe, a wszystko inne jest prawdziwe. Jak zaobserwowałeś, po odlaniu do void * i podany jako argument do operatora wstawiania strumienia (<<), adres jest drukowany. (Odczytanie ściśle, rzutowanie wskaźnika na funkcję na void * jest niezdefiniowane.)

Bez obsady historia jest nieco skomplikowana. W celu dopasowania przeciążonych funkcji ("rozdzielczość przeciążania") kompilator C++ gromadzi zestaw funkcji kandydujących i od tych kandydatów wybiera "najlepszy", w razie potrzeby, stosując niejawne konwersje. Zmarszczka to pasujące reguły z częściowego porządku, więc wiele najlepiej wykonalnych dopasowań powoduje błąd niejednoznaczności.

w kolejności preferencji, standardowe konwersje (i oczywiście także zdefiniowane przez użytkownika i Wielokropek konwersje, a nie szczegółowy) są

  • dokładne dopasowanie (tj, konwersja nie jest to konieczne)
  • promocja (np, int do float)
  • że konwersja

Ostatnia kategoria obejmuje konwersję typu Boolean, a każdy typ wskaźnika może zostać przekonwertowany na bool: 0 (lub NULL) to false, a wszystko inne to true. Ten ostatni pojawia się jako 1 po przekazaniu do operatora wstawiania strumienia.

Aby uzyskać 0 zamiast zmienić inicjalizacji do

pf = 0; 

Pamiętaj, że inicjowanie wskaźnik o zerowej wartości stałej ekspresji daje pustego wskaźnika.

+0

Więc masz na myśli kompilator ma tendencję do traktowania dowolnego wskaźnika bez znanego typu jako bool? – ibread

+0

@ibread "_known type_" co to jest znany typ? – curiousguy

+0

@cualnyguy Uh ... Właściwie mam na myśli to, że gdy kompilator nie może znaleźć dokładnego podpisu funkcji zwanej jako podane argumenty, powiedzmy wskaźnik funkcji w tym konkretnym przykładzie, kompilator spróbuje następnie przekonwertować go na wartość bool i tym samym dopasować wersja przeciążona z argumentem typu bool. – ibread

0

Wskaźniki odlewnicze do (void*), aby wydrukować je do cout, to właściwa rzecz (TM) do zrobienia w C++, jeśli chcesz zobaczyć ich wartości.

+0

Jest to słuszne, z wyjątkiem oczywiście tam, gdzie to w ogóle nie działa. Niestety, nie ma gwarancji, że wskaźnik do kodu jest rzeczywiście zgodny ze wskaźnikiem do danych. Klasyczny przykład (naprawdę klasyczny, obecnie) był w "średniej" pamięci modelu pod MS-DOS, gdzie wskaźnik do danych był tylko 16 bitów, ale wskaźnik do kodu wynosił 32 bity. –

+0

@JerryCoffin 'unsigned long',' unsigned long long' może być również użyty. – curiousguy

8

Jeśli chodzi o edycję, można wydrukować zawartość czegokolwiek, uzyskując do niej dostęp za pomocą wskaźnika unsigned char. Przykładem wskaźniki do funkcji składowych:

#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

Wielkie dzięki! Teraz wiem, jak obserwować zawartość wskaźnika funkcji członka. – ibread

0

Odnośnie konkretnej kwestii,

jak możemy obserwować treść wskaźników funkcji członkiem?

Odpowiedź brzmi, inne niż konwersja ich na wartość bool, aby wyrazić, że wskazuje na coś lub nie, nie można "obserwować" wskaźników funkcji członków. Przynajmniej nie w zgodny sposób. Powodem tego jest fakt, że standardowy wyraźnie uniemożliwia to:

4,12 Przypis 57:

57) Zasada do konwersji odnośniki do członkowie (wskaźnik do członka podstawy do wskaźnika członkowi pochodzącego) jest odwrócony w porównaniu z regułą dla wskaźników do obiektów (od wskaźnik do wyprowadzenia na wskaźnik do bazy) (4.10, punkt 10). Ta inwersja to niezbędna do zapewnienia bezpieczeństwa typu. Uwaga: , że wskaźnik do elementu nie jest wskaźnikiem obiektu lub wskaźnikiem do funkcji , a zasady dotyczące konwersji takich wskaźników nie mają zastosowania do wskaźników dla członków.W szczególności, wskaźnik dla członka nie może zostać przekształcony w pustkę *.

Na przykład, oto przykładowy kod:

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

Pragnę zauważyć, że moja debugger (MSVC9) jest w stanie powiedzieć mi rzeczywisty adres fizyczny funkcji Członka w czasie wykonywania, więc wiem, że musi być jakimś sposobem na uzyskanie tego adresu. Ale jestem pewien, że jest niezgodny, nieprzenośny i prawdopodobnie zawiera kod maszynowy. Gdybym szedł tą drogą, zacznę od wpisania adresu wskaźnika funkcji (np. &fn), odrzucając go, aby anulować * i odejdę stamtąd. Wymagałoby to również znajomości wielkości wskaźników (różnych na różnych platformach).

Ale chciałbym zapytać, o ile można zamienić wskaźnik funkcji elementu na wartość bool i ocenić istnienie wskaźnika, dlaczego w prawdziwym kodzie potrzebny byłby adres?

Prawdopodobnie odpowiedź na ostatnie pytanie brzmi "tak, że mogę określić, czy jeden wskaźnik funkcji wskazuje na tę samą funkcję, co druga." Słusznie. Można porównać wskaźniki funkcyjne dla równości:

#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 Bardzo dziękuję za odpowiedź. Właściwie to jestem po prostu ciekawy zawartości wskaźnika funkcji członka i dlatego chcę go wydrukować, aby sprawdzić. :) I jest kilka "**", które spowodowałyby skargę kompilatora. ;) – ibread

+0

Część standardu, który cytujesz, nie ma nic wspólnego z odpowiedzią. _Pointer to member_ to zupełnie inna sprawa niż _pointer do method_ (to jest _pointer do member function_). –

0

w C++ 11 można było zmodyfikować ten problem poprzez zdefiniowanie zmiennej liczbie argumentów szablonu przeciążenie operator<< (czy to jest godne polecenia lub nie jest inny temat):

#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

Potrzebne jest również przeciążenie, które będzie wymagać funkcji variadic w stylu C (z końcową wielokropkiem) i ośmiu przeciążeń dla wskaźników do funkcji składowych, z których każda może być niekwalifikowana, 'const',' volatile' lub 'const volatile' i może mieć lub nie mieć ruchomą wielokropkę. – Brian

+0

@ Brian, masz na myśli pokrycie przypadku wskaźnika const do funkcjonowania? jak 'double (const * ptr) (double) = & fun_double_double;'? (prosimy o edycję odpowiedzi w celu wyjaśnienia). – alfC

+0

Nie możesz umieścić 'const' w tej pozycji. Chciałbyś umieścić go tuż przed 'ptr', jeśli chcesz, aby stały wskaźnik działał. W każdym razie, ta sprawa jest już objęta twoją odpowiedzią, ale mam na myśli coś w stylu 'double (T :: * ptr) (double) const'. – Brian

Powiązane problemy