Signification de int (*) (int *) = 5 (ou toute valeur entière)

87

Je ne peux pas comprendre ceci:

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

L'affectation ci-dessus se compile avec g ++ c ++ 11. Je sais que int (*) (int *)c'est un pointeur vers une fonction qui accepte un (int *)comme argument et renvoie un int, mais je ne comprends pas comment vous pourriez l'assimiler à 5. Au début, je pensais que c'était une fonction qui renvoie constamment 5 (d'après mon récent apprentissage en F #, probablement, haha), alors j'ai pensé, brièvement, que le pointeur de fonction pointe vers l'emplacement mémoire 5, mais cela ne fonctionne pas, clairement, et les valeurs hexadécimales non plus.

Pensant que cela pourrait être dû au fait que la fonction renvoie un int, et que l'attribution d'un int est ok (en quelque sorte), j'ai également essayé ceci:

int * (*) (int *) = my_ptr

my_ptrest de type int *, du même type que ce deuxième pointeur de fonction, comme dans le premier cas avec le type int. Cela ne compile pas. L'affectation de 5, ou de toute valeur int, au lieu de my_ptr, ne compile pas non plus pour ce pointeur de fonction.

Alors, que signifie la mission?

Mise à jour 1

Nous avons la confirmation qu'il s'agit d'un bug, comme indiqué dans la meilleure réponse. Cependant, on ne sait toujours pas ce qui arrive réellement à la valeur que vous affectez au pointeur de fonction, ou ce qui se passe avec l'affectation. Toute (bonne) explication à ce sujet serait très appréciée! Veuillez vous référer aux modifications ci-dessous pour plus de clarté sur le problème.

Modifier 1

J'utilise la version 4.8.2 de gcc (dans Ubuntu 4.8.2)

Modifier 2

En fait, l'assimiler à n'importe quoi fonctionne sur mon compilateur. Même l'assimiler à une variable std :: string, ou à un nom de fonction qui renvoie un double, fonctionne.

Modifier 2.1

Fait intéressant, en faire un pointeur de fonction vers n'importe quelle fonction qui renvoie un type de données qui n'est pas un pointeur, le laissera compiler, tel que

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

Mais dès que le pointeur de fonction est sur une fonction qui renvoie un pointeur, il ne se compile pas, comme avec

some_data_type ** (*) () = any_value;
Konrad Kapp
la source
3
Hmm ... ça n'a pas l'air bien, et clang ne l'accepte pas. Cela pourrait être une extension (ou un bogue) gcc.
Wintermute
4
g ++ compile, mais gcc ne fonctionne pas:error: expected identifier or '(' before ')' token
tivn
3
@ 0x499602D Notez que le code ne donne pas de nom au pointeur. Avec int *x = 5vous l'avez nommé x. Avec int * (*x) (int *) = 5elle ne compilera pas. (bien que cela compilera en code C).
nos
5
Testcase réduit: int(*) = 5;etint(*);
Johannes Schaub - litb

Réponses:

59

C'est un bogue dans g ++.

 int (*) (int *) 

est un nom de type.

En C ++, vous ne pouvez pas avoir de déclaration avec un nom de type sans identificateur.

Donc, cela compile avec g ++.

 int (*) (int *) = 5;

et cela compile aussi:

 int (*) (int *);

mais ce sont toutes deux des déclarations invalides.

MODIFIER :

TC mentionne dans les commentaires bugzilla bug 60680 avec un cas de test similaire mais il n'a pas encore été approuvé . Le bogue est confirmé dans bugzilla.

EDIT2 :

Lorsque les deux déclarations ci-dessus sont à portée de fichier, g ++ émet correctement un diagnostic (il ne parvient pas à émettre le diagnostic à portée de bloc).

EDIT3 :

J'ai vérifié et je peux reproduire le problème sur la dernière version de g ++ version 4 (4.9.2), la dernière pré-version 5 (5.0.1 20150412) et la dernière version expérimentale 6 (6.0.0 20150412).

ouah
la source
5
MSVC a rejeté le code publié modifié avecerror C2059: syntax error : ')'
Weather Vane
S'il s'agit d'un nom de type, pourquoi 'int (*) (int *) int_func;' travail?
Konrad Kapp
1
Pour le bugzilla GCC, "NEW" est un bogue confirmé. (Les bogues non confirmés sont "NON CONFIRMÉS").
TC
4
@KonradKapp: cela fonctionne très bien si vous dites int (*int_func)(int *); qui déclare un pointeur de fonction nommé int_func.
Edward le
3
@KonradKapp C ++ utilise la notation infixe pour placer l'identificateur; même raison que c'est int x[5];et pasint[5] x;
MM
27

Ce n'est pas du C ++ valide. N'oubliez pas que parce que votre compilateur particulier compile, il ne le rend pas valide. Les compilateurs, comme tous les logiciels complexes, ont parfois des bogues et cela semble en être un.

En revanche se clang++plaint:

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.

C'est le comportement attendu car la ligne incriminée n'est pas C ++ valide. Il prétend être une affectation (à cause de la =) mais ne contient aucun identifiant.

Edward
la source
9

Comme d'autres réponses l'ont souligné, c'est un bug qui

int (*) (int *) = 5;

compile. Une approximation raisonnable de cette affirmation qui devrait avoir un sens est:

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

Voici maintenant procun pointeur vers une fonction qui s'attend à ce que l'adresse 5soit l'adresse de base d'une fonction qui prend un int*et retourne un int.

Sur certains microcontrôleurs / microprocesseurs, il 5peut y avoir une adresse de code valide et il peut être possible d'y localiser une telle fonction.

Sur la plupart des ordinateurs à usage général, la première page de mémoire (adresses 0-1023pour les pages 4K) est volontairement invalide (non mappée) afin de capturernull accès du pointeur.

Ainsi, alors que le comportement dépend de la plate-forme, on peut raisonnablement s'attendre à ce qu'une erreur de page se produise lorsqu'elle *procest invoquée (par exemple, (*proc)(&v)). Avant l'heure à laquelle *procest invoqué, rien d'inhabituel ne se produit.

À moins que vous n'écriviez un éditeur de liens dynamique, vous ne devriez certainement pas calculer numériquement les adresses et les affecter à des variables de pointeur vers une fonction.

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

Cette ligne de commande génère de nombreux fichiers intermédiaires. Le premier d'entre eux so.cpp.170r.expand, dit:

...
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

}
...

Cela ne répond toujours pas à ce qui se passe exactement, mais cela devrait être un pas dans la bonne direction.

Roland Illig
la source
Intéressant. À quoi servent ces fichiers intermédiaires?
Konrad Kapp
@KonradKapp Produire du code machine à partir de code humain est un processus assez complexe (surtout si vous voulez que votre compilateur optimise sa sortie). Comme la compilation est si complexe, elle ne se fait pas en une seule étape, la plupart des compilateurs ont une forme de représentation intermédiaire (IR).
11684
2
Une autre raison d'avoir un IR est que si vous avez un IR bien défini, vous pouvez séparer le front-end et le back-end de votre compilateur. (Par exemple, le front-end compile C sur votre IR, le back-end compile le code machine IR vers Intel. Désormais, si vous souhaitez ajouter le support ARM, vous n'avez besoin que d'un second back-end. Et si vous voulez compiler Go, vous n'avez besoin que d'un deuxième extrémité avant et au - dessus de ce que le compilateur Go prend en charge immédiatement Intel et ARM puisque vous pouvez réutiliser les deux back-ends.
11684
@ 11684 OK, c'est logique. Très intéressant. Je n'ai pas pu déterminer le langage que Roland a donné dans cette réponse ... cela ressemble à une sorte d'assemblage mélangé avec C.
Konrad Kapp
IR n'a pas besoin d'être imprimable; Je n'ai aucune idée de ce qu'utilise gcc, cela peut simplement être une représentation imprimable @KonradKapp
11684