Significato di int (*) (int *) = 5 (o qualsiasi valore intero)


88

Non riesco a capirlo:

int main() {
    int (*) (int *) = 5;
    return 0;
}

L'assegnazione sopra viene compilata con g ++ c ++ 11. So che int (*) (int *)è un puntatore a una funzione che accetta un (int *)argomento come e restituisce un int, ma non capisco come potresti equipararlo a 5. All'inizio ho pensato che fosse una funzione che restituisce costantemente 5 (dal mio recente apprendimento in F #, probabilmente, haha), poi ho pensato, brevemente, che il puntatore a funzione punta alla posizione di memoria 5, ma questo non funziona, chiaramente, e nemmeno i valori esadecimali.

Pensando che potrebbe essere perché la funzione restituisce un int e che l'assegnazione di un int è ok (in qualche modo), ho provato anche questo:

int * (*) (int *) = my_ptr

dove my_ptrè di tipo int *, lo stesso tipo di questo secondo puntatore a funzione, come nel primo caso con tipo int. Questo non si compila. Assegnare 5, o qualsiasi valore int, invece di my_ptr, non viene compilato neanche per questo puntatore a funzione.

Allora cosa significa l'incarico?

Aggiorna 1

Abbiamo la conferma che si tratta di un bug, come mostrato nella migliore risposta. Tuttavia, non è ancora noto cosa accada effettivamente al valore che si assegna al puntatore a funzione o cosa succede con l'assegnazione. Qualsiasi (buona) spiegazione in merito sarebbe molto apprezzata! Fare riferimento alle modifiche di seguito per maggiore chiarezza sul problema.

Modifica 1

Sto usando la versione 4.8.2 di gcc (in Ubuntu 4.8.2)

Modifica 2

In realtà, equipararlo a qualsiasi cosa funziona sul mio compilatore. Anche equipararlo a una variabile std :: string, o un nome di funzione che restituisce un double, funziona.

Modifica 2.1

È interessante notare che, renderlo un puntatore a una funzione che restituisce un tipo di dati che non è un puntatore, consentirà la compilazione, ad esempio

std::string (*) () = 5.6;

Ma non appena il puntatore a una funzione è a una funzione che restituisce un puntatore, non si compila, come con

some_data_type ** (*) () = any_value;

3
Hmm ... non sembra giusto e clang non lo accetta. Potrebbe essere un'estensione (o un bug) di gcc.
Wintermute

4
g ++ viene compilato, ma gcc non funziona:error: expected identifier or '(' before ')' token
tivn

3
@ 0x499602D Notare che il codice non dà un nome al puntatore. Con l' int *x = 5hai chiamato x. Con int * (*x) (int *) = 5esso non verrà compilato. (anche se verrà compilato come codice C).
n.

5
Caso di prova ridotto: int(*) = 5;eint(*);
Johannes Schaub - litb

Risposte:


60

È un bug in g ++.

 int (*) (int *) 

è un nome di tipo.

In C ++ non è possibile avere una dichiarazione con un nome di tipo senza un identificatore.

Quindi questo si compila con g ++.

 int (*) (int *) = 5;

e anche questo compila:

 int (*) (int *);

ma sono entrambe dichiarazioni non valide.

MODIFICA :

TC menziona nei commenti bugzilla bug 60680 con un test case simile ma non è stato ancora approvato . Il bug è stato confermato in bugzilla.

EDIT2 :

Quando le due dichiarazioni precedenti sono nell'ambito del file, g ++ emette correttamente una diagnostica (non riesce a emettere la diagnostica nell'ambito del blocco).

EDIT3 :

Ho controllato e posso riprodurre il problema sull'ultima versione di g ++ versione 4 (4.9.2), l'ultima versione pre-release 5 (5.0.1 20150412) e l'ultima versione sperimentale 6 (6.0.0 20150412).


5
MSVC ha rifiutato il codice pubblicato modificato conerror C2059: syntax error : ')'
Weather Vane

Se è un nome di tipo, perché non "int (*) (int *) int_func;" lavoro?
Konrad Kapp

1
Per il bugzilla di GCC, "NEW" è un bug confermato. (I bug non confermati sono "NON CONFERMATI").
TC

4
@KonradKapp: funziona benissimo se dici int (*int_func)(int *); che dichiara un puntatore a funzione denominato int_func.
Edward

3
@KonradKapp C ++ utilizza la notazione infissa per posizionare l'identificatore; lo stesso motivo è int x[5];e nonint[5] x;
MM

28

Non è C ++ valido. Ricorda che, poiché il tuo particolare compilatore compila, non lo rende valido. I compilatori, come tutti i software complessi, a volte hanno bug e questo sembra essere uno.

Al contrario si clang++lamenta:

funnycast.cpp:3:11: error: expected expression
    int (*) (int *) = 5;
          ^
funnycast.cpp:3:18: error: expected '(' for function-style cast or type construction
    int (*) (int *) = 5;
             ~~~ ^
funnycast.cpp:3:19: error: expected expression
    int (*) (int *) = 5;
                  ^
3 errors generated.

Questo è il comportamento previsto perché la riga incriminata non è C ++ valido. Si pretende di essere un incarico (a causa di =) ma non contiene identificatore.


9

Come altre risposte hanno sottolineato, è un bug quello

int (*) (int *) = 5;

compila. Una ragionevole approssimazione di questa affermazione che dovrebbe avere un significato è:

int (*proc)(int*) = (int (*)(int*))(5);

Ora procè un puntatore a una funzione che si aspetta che l'indirizzo 5sia l'indirizzo di base di una funzione che accetta un int*e restituisce un int.

Su alcuni microcontrollori / microprocessori 5potrebbe esserci un indirizzo di codice valido e potrebbe essere possibile individuare una tale funzione lì.

Sulla maggior parte dei computer di uso generale, la prima pagina di memoria (indirizzi 0-1023per pagine 4K) è volutamente non valida (non mappata) per catturarenull rilevare gli accessi del puntatore.

Pertanto, sebbene il comportamento dipenda dalla piattaforma, ci si può ragionevolmente aspettare che si verifichi un errore di pagina quando *procviene invocato (ad esempio (*proc)(&v)). Prima del momento in cui *procviene invocato, non accade nulla di insolito.

A meno che non stiate scrivendo un linker dinamico, quasi certamente non dovreste calcolare numericamente gli indirizzi e assegnarli a variabili puntatore a funzione.


2
/usr/lib/gcc/x86_64-pc-cygwin/4.9.2/cc1plus.exe -da so.cpp

Questa riga di comando genera molti file intermedi. Il primo di loro so.cpp.170r.expand, dice:

...
int main() ()
{
  int D.2229;
  int _1;

;;   basic block 2, loop depth 0
;;    pred:       ENTRY
  _1 = 0;
;;    succ:       3

;;   basic block 3, loop depth 0
;;    pred:       2
<L0>:
  return _1;
;;    succ:       EXIT

}
...

Questo ancora non risponde esattamente a ciò che accade, ma dovrebbe essere un passo nella giusta direzione.


Interessante. Qual è lo scopo di questi file intermedi?
Konrad Kapp

@KonradKapp Produrre codice macchina dal codice umano è un processo piuttosto complesso (specialmente se vuoi che il tuo compilatore ottimizzi il suo output). Poiché la compilazione è così complessa, non viene eseguita in un unico passaggio, la maggior parte dei compilatori ha una qualche forma di rappresentazione intermedia (IR).
11684

2
Un altro motivo per avere un IR è che se hai un IR ben definito puoi separare front-end e back-end del tuo compilatore. (Ad esempio, il front-end compila C nel tuo IR, il back-end compila IR nel codice macchina Intel. Ora se vuoi aggiungere il supporto ARM hai solo bisogno di un secondo back-end. E se vuoi compilare Go, hai solo bisogno di un secondo front-end, e per di più il compilatore Go supporta immediatamente sia Intel che ARM poiché è possibile riutilizzare entrambi i back-end.
11684

@ 11684 OK, ha senso. Molto interessante. Non sono riuscito a determinare quale linguaggio Roland ha dato in questa risposta però ... sembra una sorta di assemblaggio mescolato con C.
Konrad Kapp

IR non deve essere stampabile; Non ho idea di cosa usi gcc, questa potrebbe essere semplicemente una rappresentazione stampabile @KonradKapp
11684
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.