Pourquoi ce code C d’inversion de chaîne provoque-t-il une erreur de segmentation? [dupliquer]

StackOverflow https://stackoverflow.com/questions/1614723

  •  06-07-2019
  •  | 
  •  

Question

    

Cette question a déjà une réponse ici:

         

J'essaie d'écrire du code pour inverser une chaîne (j'essaie simplement de m'améliorer en programmation C et en manipulation de pointeur), mais je ne peux pas comprendre pourquoi je reçois un erreur de segmentation :

#include <string.h>

void reverse(char *s);

int main() {
    char* s = "teststring";
    reverse(s);

    return 0;
}

void reverse(char *s) {
    int i, j;
    char temp;

    for (i=0,j = (strlen(s)-1); i < j; i++, j--) {
        temp = *(s+i);     //line 1
        *(s+i) = *(s+j);   //line 2
        *(s+j) = temp;     //line 3
    }
}

Ce sont les lignes 2 et 3 qui sont à l'origine de l'erreur de segmentation. Je comprends qu’il existe peut-être de meilleures façons de procéder, mais j’aimerais savoir ce qui dans mon code est précisément à l’origine de l’erreur de segmentation.

Mettre à jour : j'ai ajouté la fonction d'appel à la demande.

Était-ce utile?

La solution

Il n'y a aucun moyen de dire à partir de ce code. Très probablement, vous passez un pointeur pointant vers une mémoire non valide, non modifiable ou un autre type de mémoire qui ne peut tout simplement pas être traitée de la façon dont vous la traitez ici.

Comment appelez-vous votre fonction?

Ajouté: Vous passez un pointeur sur un littéral de chaîne. Les littéraux de chaîne ne sont pas modifiables. Vous ne pouvez pas inverser un littéral de chaîne.

Passez un pointeur sur une chaîne modifiable à la place

char s[] = "teststring";
reverse(s); 

Cela a déjà été expliqué à mort. "teststring" est un littéral de chaîne. Le littéral chaîne lui-même est un objet non modifiable. En pratique, les compilateurs peuvent (et vont) le mettre en mémoire en lecture seule. Lorsque vous initialisez un pointeur comme celui-ci

char *s = "teststring";

le pointeur pointe directement au début du littéral de chaîne. Toute tentative de modification de ce que s désigne est considérée comme un échec dans le cas général. Vous pouvez le lire, mais vous ne pouvez pas y écrire. Pour cette raison, il est vivement recommandé de pointer sur les littéraux de chaîne avec des variables pointeur sur const uniquement

const char *s = "teststring";

Mais lorsque vous déclarez vos s comme

char s[] = "teststring";

vous obtenez un tableau complètement indépendant s situé dans une mémoire modifiable ordinaire, qui est simplement initialisé avec un littéral de chaîne. Cela signifie que ce tableau modifiable indépendant s aura sa valeur initiale copiée à partir du littéral de chaîne. Après cela, votre tableau s et le littéral chaîne continuent d'exister en tant qu'objets complètement indépendants. Le littéral n'est toujours pas modifiable, alors que votre tableau s est modifiable.

En gros, cette dernière déclaration est fonctionnellement équivalente à

char s[11];
strcpy(s, "teststring");

Autres conseils

Votre code peut être défectueux pour plusieurs raisons. Voici ceux qui me viennent à l'esprit

  1. s est NULL
  2. s pointe vers une chaîne const conservée en mémoire en lecture seule
  3. s n'est pas terminé avec NULL

Je pense que le n ° 2 est le plus probable. Pouvez-vous nous montrer le site d’appel de reverse?

MODIFIER

Sur la base de votre échantillon n ° 2, la réponse est sans aucun doute. Un littéral de chaîne en C / C ++ n'est pas modifiable. Le type approprié est en fait const char * et non char * . Ce que vous devez faire est de passer une chaîne modifiable dans ce tampon.

Exemple rapide:

char* pStr = strdup("foobar");
reverse(pStr);
free(pStr);

Est-ce que vous testez quelque chose comme ça?

int main() {
    char * str = "foobar";
    reverse(str);
    printf("%s\n", str);
}

Ceci fait de str un littéral et vous ne pourrez probablement pas l’éditer (segfaults pour moi). Si vous définissez char * str = strdup (foobar) , cela devrait fonctionner correctement (convient pour moi).

Votre déclaration est complètement fausse:

char* s = "teststring";

" teststring " est stocké dans le segment de code, qui est en lecture seule, comme le code. Et, s est un pointeur sur "teststring", vous essayez en même temps de modifier la valeur d’une plage de mémoire en lecture seule. Ainsi, faute de segmentation.

Mais avec:

char s[] = "teststring";

s est initialisé avec "teststring", qui se trouve bien sûr dans le segment de code, mais une opération de copie supplémentaire est en cours sur la pile dans ce cas.

Quel compilateur et quel débogueur utilisez-vous? En utilisant gcc et gdb, je compilerais le code avec l'option -g, puis je l'exécuterais dans gdb. Quand il segfaults, je voudrais juste faire une trace (commande bt dans gdb) et voir quelle ligne est à l'origine du problème. En outre, je voudrais simplement exécuter le code étape par étape, tout en "regardant". les valeurs du pointeur dans gdb et savoir où se situe exactement le problème.

Bonne chance.

Comme certaines des réponses fournies ci-dessus, la mémoire de chaîne est en lecture seule. Cependant, certains compilateurs fournissent une option pour compiler avec des chaînes en écriture. Par exemple. avec gcc , versions 3.x prises en charge -fwritable-strings mais pas les versions plus récentes.

Voir la la question 1.32 dans la liste des FAQ C:

  

Quelle est la différence entre ces initialisations?

char a[] = "string literal";
char *p  = "string literal";
     

Mon programme se bloque si j'essaie d'attribuer une nouvelle valeur à p [i] .

     

Réponse:

     

Un littéral de chaîne (terme formel pour une chaîne à double guillemet dans la source C) peut être utilisé de deux manières légèrement différentes:

     

En tant qu'initialiseur pour un tableau de caractères, comme dans la déclaration de caractère a [] , il spécifie les valeurs initiales des caractères de ce tableau (et, si nécessaire, sa taille).

     

Partout ailleurs, il se transforme en un tableau statique de noms non nommé. ce tableau non nommé peut être stocké dans une mémoire en lecture seule et ne peut donc pas nécessairement être modifié . Dans un contexte d'expression, le tableau est immédiatement converti en un pointeur, comme d'habitude (voir la section 6), de sorte que la deuxième déclaration initialise p pour qu'il pointe vers le premier élément du tableau non nommé.

     

Certains compilateurs ont un commutateur contrôlant si les littéraux de chaîne sont en écriture ou non (pour compiler l’ancien code), et certains peuvent avoir des options pour que les littéraux de chaîne soient officiellement traités comme des tableaux de const char (pour meilleure erreur de capture).

     

( mine d'emphase )

Voir aussi Retour aux sources par Joel .

Je pense que strlen ne peut pas travailler puisque s n'est pas NULL terminé. Le comportement de votre itération n’est donc pas celui que vous attendez. Puisque le résultat de strlen sera supérieur à la longueur, vous écrivez en mémoire où vous ne devriez pas être.

De plus, s pointe vers une chaîne constante maintenue par une mémoire à lecture seule. Vous ne pouvez pas le modifier. Essayez de vous lancer à l’aide de la fonction d’obtention, comme dans strlen exemple

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top