2010-05-04 6 views
5
int valid (int x, int y) { 
    return x + y; 
} 

int invalid (int x) { 
    return x; 
} 

int func (int *f (int, int), int x, int y) { 
    //f is a pointer to a function taking 2 ints and returning an int 
    return f(x, y); 
} 

int main() { 
    int val = func(valid, 1, 2), 
     inval = func(invalid, 1, 2); // <- 'invalid' does not match the contract 

    printf("Valid: %d\n", val); 
    printf("Invalid: %d\n", inval); 

    /* Output: 
    * Valid: 3 
    * Invalid: 1 
    */ 
} 

A la ligne inval = func(invalid, 1, 2);, pourquoi je ne reçois pas une erreur de compilation? Si func attend un pointeur vers une fonction prenant 2 ints et que je passe un pointeur sur une fonction qui prend un seul int, pourquoi le compilateur ne se plaint-il pas?Le passage d'un pointeur vers une fonction qui ne correspond pas aux exigences du paramètre formel

De plus, puisque cela se produit, qu'advient-il du deuxième paramètre y dans la fonction invalid?

+0

juste par curiosité ... le programme ci-dessus produit-il correctement? – SysAdmin

+0

Oui, c'est le cas ... c'est pourquoi j'ai demandé en fait. –

+1

compilez-vous avec des avertissements? –

Répondre

1

Vous voulez:

int func (int (*f) (int, int), int x, int y) { 

Qu'est-ce que vous avez dans votre code est le type d'une fonction qui retourne un int * - vous voulez un pointeur vers une fonction qui retourne un entier. Avec ce changement, cette ligne:

inval = func(invalid, 1, 2); 

me donne:

fp.c:16: warning: passing argument 1 of 'func' from incompatible pointer type 
fp.c:9: note: expected 'int (*)(int, int)' but argument is of type 'int (*)(int)' 

avec gcc. Votre code original m'a aussi donné plusieurs avertissements, BTW - quel compilateur utilisez-vous? Et si votre question est vraiment "Pourquoi ce code semble-t-il fonctionner?", C'est bien l'une des joies du comportement indéfini.

+0

Neil, pouvez-vous nous en dire un peu plus à ce sujet? –

+0

Ce que vous avez dit est vrai dans le sens où dans mon cas, 'f' est une fonction renvoyant un pointeur plutôt qu'un pointeur vers une fonction. Mais, même quand je l'ai changé en '(* f)', il n'y a toujours pas de problèmes en ce qui concerne l'exécution du code. –

2

Pourquoi le compilateur ne se plaint-il pas?

Peut-être avez-vous besoin d'un meilleur compilateur? gcc dit warning: passing argument 1 of ‘func’ from incompatible pointer type sur ce code.

De plus, puisque cela se produit, qu'advient-il du deuxième paramètre y dans la fonction invalide?

Probablement ce qui se passe est que le compilateur fait ce qu'il devrait normalement faire pour passer un paramètre (pousser sur la pile, mettez-le dans un registre désigné, etc.). Cependant, appeler une fonction avec le mauvais nombre de paramètres est un comportement non défini, donc il n'y a aucune garantie - le programme pourrait tomber en panne, ou le compilateur pourrait faire monkeys fly out of your nose.

0

ce arrive au second paramètre y dans la fonction non valide?

Le compilateur génère toujours le code qui pousse deux arguments à l'intérieur func pour la ligne

return f(x, y); 

car il ne sait pas mieux. Vous voulez appeler une fonction avec deux arguments, il en pousse deux. (à condition que le prototype le permette, ce qu'il fait). Si vous inspectiez la pile, vous les verriez, mais comme invalid en prend seulement un, vous n'avez aucun moyen direct de voir le second argument en C (sans supercherie).

2

En supposant que vous ne tenez pas compte de tous les avertissements du compilateur que cela devrait vous donner, vous pouvez penser à ce qui se passe comme ceci:

Votre code tente d'appeler une fonction qui prend deux ints, et retourne un.En fonction de la convention d'appel, les paramètres peuvent être passés dans des registres sur le CPU ou sur la pile, la sortie va probablement dans un registre. L'appel valid fonctionne bien, tout est là où il est prévu. Pour l'appel invalid, la même pile est configurée, avec les deux paramètres car c'est ce que le programme pense appeler, puis la fonction est appelée.

Apparemment avec votre plate-forme, il arrive que l'argument solitaire pour invalid est dans le même endroit que le premier paramètre pour valid, de sorte que invalid ce que vous ne fait par hasard auriez prévu d'appeler correctement. La façon dont les paramètres sont nettoyés est non spécifiée - si la fonction appelée est supposée nettoyer l'espace pour ses paramètres, votre pile sera frit, si la fonction d'appel se nettoie, votre programme continuera probablement à fonctionner.

Indépendamment, vous invoquez un comportement non défini ici. Essayez de changer func à la forme de paramètre unique

int func(int(*f)(int),x){return f(x);} 

et voir si les appels fonctionnent toujours.

1

Voici un exemple où un comportement non défini fonctionne,
et probablement quelque chose de similaire se passe dans votre exemple.

typedef int (*p)(int, int); 
typedef int (*p2)(int); 

int invalid (int x) { 
    return x; 
} 

int func() { 
    p2 f = invalid; 
    return ((p)f)(1, 2); 
} 

// IA32 asm, "func" 
... 
216:  p2 f = invalid; 
00402148 mov   dword ptr [ebp-4],offset @ILT+1380(invalid) (00401569) 
0040214F mov   eax,dword ptr [ebp-4] 
00402152 mov   dword ptr [ebp-4],eax 
217:  return ((p)f)(1, 2); 
00402155 mov   esi,esp 
00402157 push  2 ; <-- 
00402159 push  1 ; <-- 
0040215B call  dword ptr [ebp-4] ; "invalid" will use only "1" 
0040215E add   esp,8 ; <-- `pop` the arguments 
... 
0

En C, ce n'est pas une erreur de lancer un pointeur d'un type à un autre. Cependant, un bon compilateur générera un avertissement lors du passage du mauvais type de pointeur à une fonction sans un cast explicite. Si vous n'obtenez pas d'avertissement, je vous recommande vivement de vérifier les paramètres de votre compilateur pour vous assurer que les avertissements sont activés. Ou pensez à utiliser un compilateur différent. ;-)

Pour comprendre pourquoi cela fonctionne, vous devez comprendre un peu le langage d'assemblage et comment C utilise the stack pour transmettre des paramètres. Vous pouvez visualiser la pile comme une grande pile de plaques, où chaque plaque contient une variable simple. Sur de nombreuses plateformes, tous les paramètres sont transmis sur la pile. func va appuyer sur y et x, appeler f, puis retirer les variables. En valid charge x et y en regardant les deux entrées supérieures de la pile. invalid trouve x en regardant l'entrée du haut de la pile.

Voici ce que la pile pourrait ressembler à l'intérieur invalide:

main:  3 
      uninitialized 
f:  2 
      1 
      invalid 
invalid: 2 
      1 

invalid() prend un paramètre, il semble juste en haut de la pile (le 1) et la charge comme paramètre.

C'est aussi le fonctionnement des fonctions printf. Ils peuvent accepter un nombre variable de paramètres. Le premier paramètre est en haut de la pile, et ils peuvent continuer à regarder dans la pile pour tous les paramètres dont ils ont besoin. Certains systèmes passent des paramètres dans des registres au lieu d'utiliser la pile, mais cela fonctionne de manière analogue.

Dans les tout premiers jours de C, les déclarations de fonction n'incluaient aucun paramètre. En fait, si vous déclarez une fonction sans rien entre parenthèses, vous pouvez toujours définir une fonction de cette façon.Par exemple, cette compile très bien:

void foo(); 
void bar(void) { 
     foo(5); /* foo's parameters are implicit */ 
} 

Voilà pourquoi il est important d'inclure la void lors de la déclaration d'une fonction sans paramètres. Il indique au compilateur que la fonction prend vraiment aucun paramètre. Avec rien entre les parenthèses, c'est une fonction avec des paramètres implicites.

+0

"inclure le vide lors de la déclaration d'une fonction avec des paramètres." - Je pense que tu veux dire "sans paramètres". –

+1

@Andreas Correction, merci! –

Questions connexes