web-dev-qa-db-fra.com

Appel d'isalpha provoquant une erreur de segmentation

J'ai le programme suivant qui provoque une erreur de segmentation.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
    printf("TEST");

    for (int k=0; k<(strlen(argv[1])); k++)
    {
        if (!isalpha(argv[1])) {
            printf("Enter only alphabets!");
            return 1;
        }
    }

    return 0;
}

J'ai compris que c'est cette ligne qui pose problème

if (!isalpha(argv[1])) {

et le remplacement de argv[1] par argv[1][k] résout le problème.

Cependant, je trouve plutôt curieux que le programme aboutisse à une erreur de segmentation sans même imprimer TEST. Je m'attends également à ce que la fonction isalpha vérifie de manière incorrecte si l'octet inférieur du pointeur char* à argv[1], mais cela ne semble pas être le cas. J'ai du code pour vérifier le nombre d'arguments mais ce n'est pas indiqué ici par souci de concision.

Qu'est-ce qu'il se passe ici?

10

En général, il est plutôt inutile de discuter de la raison pour laquelle un comportement non défini conduit à ce résultat ou à un autre.

Mais peut-être que tenter de comprendre pourquoi quelque chose se produit même si cela n’est pas conforme aux spécifications ne fait pas de mal.

Il existe des implémentations de isalpha qui utilisent un tableau simple pour rechercher toutes les valeurs possibles de unsigned char. Dans ce cas, la valeur transmise en tant que paramètre est utilisée comme index dans le tableau . Alors qu'un caractère réel est limité à 8 bits, un entier n'est pas . La fonction prend une variable int en paramètre. Ceci permet de saisir EOF également, ce qui ne rentre pas dans unsigned char.

Si vous transmettez une adresse comme 0x7239482342 à votre fonction, cela va bien au-delà de la fin dudit tableau et lorsque le CPU essaie de lire l'entrée avec cet index, il tombe du bord du monde. ;)

L'appel de isalpha avec une telle adresse est l'endroit où le compilateur doit émettre un avertissement concernant la conversion d'un pointeur en entier. Ce que vous ignorez probablement ...

La bibliothèque pourrait contient un code qui recherche les paramètres valides, mais elle peut aussi simplement compter sur l’utilisateur pour ne pas transmettre de choses à ne pas transmettre.

19
Gerhardh
  1. printf n'a pas été vidé
  2. la conversion implicite de pointeur en entier qui aurait dû générer au moins des diagnostics au moment de la compilation pour la violation de contrainte a généré un nombre qui était hors limite pour isalpha. isalpha étant implémenté en tant que table de recherche, votre code a accédé à la table en dehors des limites, donc un comportement indéfini.
  3. Pourquoi vous n'avez pas obtenu des diagnostics peut faire partie d'une partie à cause du fait que commentisalpha est implémenté sous forme de macro. Sur mon ordinateur avec Glibc 2.27-3ubuntu1, isalpha est défini comme 

    # define isalpha(c)     __isctype((c), _ISalpha)
    # define __isctype(c, type) \
        ((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) type)
    

    la macro contient une distribution malheureuse sur int, ce qui fera taire votre erreur!


Une des raisons pour lesquelles je publie cette réponse après tant d’autres est que vous n’avez pas corrigé le code, il a toujours un comportement indéfini étant donné les caractères étendus et char étant signé (ce qui est généralement le cas sur x86- 32 et x86 à 64).

L'argument correct à donner à isalpha est (unsigned char)argv[1][k]! C11 7.4 :

Dans tous les cas, l'argument est un int, dont la valeur doit être représentable sous la forme d'un unsigned char ou doit être égale à la valeur de la macro EOF. Si l'argument a une autre valeur, le comportement n'est pas défini. 

6
Antti Haapala

Je trouve plutôt curieux que le programme aboutisse à une erreur de segmentation sans même imprimer TEST

printf n'imprime pas instantanément, mais écrit dans un tampon temporel. Terminez votre chaîne avec \n si vous voulez la vider à la sortie réelle.

le remplacement de argv [1] par argv [1] [k] résout le problème.

isalpha est conçu pour fonctionner avec des caractères uniques.

3
malarres

Tout d’abord, un compilateur conforme doit vous donner un message de diagnostic ici. Il n'est pas autorisé de convertir implicitement un pointeur vers le paramètre int attendu par isalpha. (C'est une violation des règles d'assignation simple, 6.5.16.1.)

Quant à savoir pourquoi "TEST" n’est pas imprimé, c’est peut-être tout simplement parce que stdout n’est pas vidé. Vous pouvez essayer d'ajouter fflush(stdout); après printf et voir si cela résout le problème. Vous pouvez également ajouter un saut de ligne \n à la fin de la chaîne.

Sinon, le compilateur est libre de réordonner l'exécution du code tant qu'il n'y a pas d'effets secondaires. C'est-à-dire qu'il est autorisé à exécuter la totalité de la boucle avant la printf("TEST");, tant qu'elle imprime TEST avant d'imprimer potentiellement "Enter only alphabets!". De telles optimisations ne sont probablement pas susceptibles de se produire ici, mais dans d'autres situations, elles peuvent se produire.

1
Lundin