314 votes

Cette fonction de C doit toujours retourner faux, mais il n'a pas

Je suis tombé sur une question intéressante dans un forum il y a longtemps et je veux savoir la réponse.

Considérons la fonction C suivante:

f1.c

#include <stdbool.h>

bool f1()
{
    int var1 = 1000;
    int var2 = 2000;
    int var3 = var1 + var2;
    return (var3 == 0) ? true : false;
}

Ce doit toujours renvoyer false depuis var3 == 3000. L' main fonction ressemble à ceci:

principal.c

#include <stdio.h>
#include <stdbool.h>

int main()
{
    printf( f1() == true ? "true\n" : "false\n");
    if( f1() )
    {
        printf("executed\n");
    }
    return 0;
}

Depuis f1() doit toujours renvoyer false, on pourrait s'attendre à ce programme pour imprimer un seul faux à l'écran. Mais après la compilation et l'exécution, exécuté est également affiché:

$ gcc main.c f1.c -o test
$ ./test
false
executed

Pourquoi est-ce? Le code de avoir ce genre de comportement indéfini?

Note: j'ai compilé avec gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2.

394voto

Lundin Points 21616

Comme indiqué dans d'autres réponses, le problème est que vous utilisez gcc avec aucune des options du compilateur ensemble. Si vous faites cela, la valeur par défaut de ce qui est appelé "gnu90", ce qui est un non-standard de mise en œuvre de la vieille, retiré C90 standard à partir de 1990.

Dans l'ancienne norme C90 il y avait une faille majeure dans le langage C: si vous ne déclarez pas un prototype avant d'utiliser une fonction, il est par défaut à int func () (où ( ) signifie "accepter n'importe quel paramètre"). Cela modifie la convention d'appel de la fonction func, mais on ne change pas la définition d'une fonction réelle. Puisque la taille de l' bool et int sont différentes, votre code invoque un comportement indéfini lorsque la fonction est appelée.

Cette dangereuse absurdité problème a été résolu dans l'année 1999, avec la sortie de la norme C99. Implicite de la fonction déclarations ont été interdits.

Malheureusement, GCC jusqu'à la version 5.x.x utilise encore l'ancien C standard par défaut. Il n'y a probablement aucune raison pourquoi vous devriez vous voulez compiler votre code comme quelque chose, mais la norme C. Donc, vous devez indiquer explicitement GCC qu'il doit compiler votre code moderne de code C, au lieu de plus de 25 ans, non-standard GNU de la merde.

Résoudre le problème par toujours de la compilation de votre programme:

gcc -std=c11 -pedantic-errors -Wall -Wextra
  • -std=c11 le raconte à faire une timide tentative de compilation selon l' (en cours) C standard (officieusement connu comme C11).
  • -pedantic-errors le raconte à tout son cœur à faire la-dessus, et de donner des erreurs de compilation lorsque vous écrivez du code incorrect qui viole le C standard.
  • -Wall moyen de me donner des avertissements supplémentaires qui pourrait être bon d'avoir.
  • -Wextra moyen de me donner quelques autres avertissements supplémentaires qui pourrait être bon d'avoir.

140voto

dbush Points 8590

Vous n'avez pas un prototype déclarés pour l' f1() dans la principale.c, de sorte qu'il est implicitement définie comme int f1(), c'est à dire une fonction qui prend un nombre indéterminé d'arguments et retourne un int.

Si int et bool sont de tailles différentes, cela se traduira par un comportement indéfini. Par exemple, sur ma machine, int est de 4 octets, et bool est un octet. Puisque la fonction est définie pour revenir bool, il met un octet sur la pile quand il retourne. Cependant, depuis qu'il est implicitement déclarée pour revenir int de main.c, la fonction d'appel va essayer de lire les 4 octets de la pile.

La valeur par défaut options des compilateurs gcc ne vous dis pas qu'il fait. Mais si vous compilez avec -Wall -Wextra, vous obtiendrez ceci:

main.c: In function ‘main':
main.c:6: warning: implicit declaration of function ‘f1'

Pour résoudre ce problème, ajouter une déclaration pour f1 dans la principale.c, avant d' main:

bool f1(void);

Notez que la liste d'arguments est définie explicitement void, ce qui indique au compilateur que la fonction ne prend pas d'arguments, par opposition à un vide de la liste des paramètres qui signifie un nombre indéterminé d'arguments. La définition f1 de la f1.c doit également être modifiée pour tenir compte de cela.

36voto

Owen Points 14439

Je pense que c'est intéressant de voir où la taille d'incompatibilité mentionnés dans Lundin excellente réponse qui se passe réellement.

Si vous compilez avec --save-temps, vous obtiendrez de l'assemblée des fichiers que vous pouvez regarder. Voici la partie où f1() le == 0 comparaison et retourne sa valeur:

cmpl    $0, -4(%rbp)
sete    %al

Le retour de la partie est - sete %al. En C x86 conventions d'appel, les valeurs de retour de 4 octets ou plus petit (ce qui inclut int et bool) sont retournées via le registre %eax. %al est le plus faible de l'octet de %eax. Ainsi, la partie supérieure de 3 octets d' %eax sont laissés dans une traversée de l'etat.

Maintenant, en main():

call    f1
testl   %eax, %eax
je  .L2

Ceci permet de vérifier si l' ensemble de l' %eax est égal à zéro, parce qu'il pense que c'est le test d'un int.

Ajout d'une fonction explicite de la déclaration de change main() :

call    f1
testb   %al, %al
je  .L2

qui est ce que nous voulons.

27voto

jdarthenay Points 2590

Veuillez compiler avec une commande telle que celle-ci:

gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c

Sortie:

main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
     printf( f1() == true ? "true\n" : "false\n");
     ^
cc1.exe: all warnings being treated as errors

Avec un tel message, vous devez savoir quoi faire pour la corriger.

Edit: Après lecture d'un (maintenant supprimé) commentaire, j'ai essayé de compiler votre code sans les drapeaux. Eh bien, Cela m'a conduit à des erreurs d'édition de liens avec aucun des avertissements du compilateur au lieu de les erreurs du compilateur. Et ces erreurs d'édition de liens sont plus difficiles à comprendre, de sorte que même si -std-gnu99 n'est pas nécessaire, s'il vous plaît essayez de toujours utiliser au moins -Wall -Werror il va vous faire économiser beaucoup de douleur dans le cul.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X