web-dev-qa-db-fra.com

Est-il possible de modifier une chaîne de caractères en C?

Je me bats depuis quelques heures avec toutes sortes de didacticiels C et de livres sur les pointeurs, mais ce que je veux vraiment savoir, c'est s'il est possible de changer un pointeur de caractère une fois qu'il est créé.

C'est ce que j'ai essayé:

char *a = "This is a string";
char *b = "new string";

a[2] = b[1]; // Causes a segment fault

*b[2] = b[1]; // This almost seems like it would work but the compiler throws an error.

Alors, y a-t-il un moyen de changer les valeurs à l'intérieur des chaînes plutôt que les adresses de pointeur?

Merci

MODIFIER:

Merci à tous pour vos réponses. Cela a plus de sens maintenant. Il est particulièrement logique de savoir pourquoi parfois cela fonctionnait bien et d’autres fois ne fonctionnait pas. Parce que parfois je passais un pointeur de caractères et d'autres fois un tableau de caractères (le tableau de caractères fonctionnait bien).

57
John Baker

Lorsque vous écrivez une "chaîne" dans votre code source, il est écrit directement dans l'exécutable car cette valeur doit être connue au moment de la compilation (il existe des outils permettant de séparer les logiciels et de trouver toutes les chaînes de texte brut). Lorsque vous écrivez char *a = "This is a string", l'emplacement de "Ceci est une chaîne" se trouve dans l'exécutable et l'emplacement indiqué par un point est dans l'exécutable. Les données de l'image exécutable sont en lecture seule.

Ce que vous devez faire (comme l'ont souligné d'autres réponses), c'est créer cette mémoire dans un emplacement qui n'est pas en lecture seule - sur le tas ou dans le cadre de la pile. Si vous déclarez un tableau local, un espace est créé sur la pile pour chaque élément de ce tableau et le littéral chaîne (stocké dans l'exécutable) est copié dans cet espace de la pile.

char a[] = "This is a string";

vous pouvez également copier ces données manuellement en allouant de la mémoire sur le tas, puis en utilisant strcpy() pour copier un littéral de chaîne dans cet espace.

char *a = malloc(256);
strcpy(a, "This is a string");

Chaque fois que vous allouez de l'espace en utilisant malloc(), n'oubliez pas d'appeler free() lorsque vous avez terminé (lisez: fuite de mémoire).

Fondamentalement, vous devez garder une trace de l'emplacement de vos données. Chaque fois que vous écrivez une chaîne dans votre source, cette chaîne est en lecture seule (sinon, vous pourriez potentiellement changer le comportement de l'exécutable. Imaginez si vous avez écrit char *a = "hello";, puis changé a[0] en 'c'. Puis, quelque part d'autre a écrit printf("hello");. Si vous étiez autorisé pour changer le premier caractère de "hello", et votre compilateur ne l’a stocké qu’une fois (il devrait le faire), alors printf("hello"); afficherait cello!)

124
Carson Myers

Non, vous ne pouvez pas le modifier car la chaîne peut être stockée dans une mémoire en lecture seule. Si vous souhaitez le modifier, vous pouvez utiliser un tableau à la place, par exemple.

char a[] = "This is a string";

Ou bien, vous pouvez allouer de la mémoire en utilisant malloc, par exemple.

char *a = malloc(100);
strcpy(a, "This is a string");
free(a); // deallocate memory once you've done
20
Jonathan Maddison

Beaucoup de gens s'embrouillent sur la différence entre char * et char [] en conjonction avec des littéraux de chaîne en C. Lorsque vous écrivez:

char *foo = "hello world";

... vous dirigez réellement foo vers un bloc constant de mémoire (en fait, ce que le compilateur fait avec "hello world" dans cet exemple dépend de l'implémentation.)

Utiliser char [] indique au compilateur que vous souhaitez créer un tableau et le remplir avec le contenu, "hello world". foo est le pointeur sur le premier index du tableau de caractères. Les deux sont des pointeurs de caractères, mais seul char [] pointe vers un bloc de mémoire alloué localement et mutable.

8
Jeff Ober

La mémoire pour a & b n'est pas allouée par vous. Le compilateur est libre de choisir un emplacement de mémoire en lecture seule pour stocker les caractères. Donc, si vous essayez de changer cela peut entraîner une faute de segmentation. Je vous suggère donc de créer vous-même un tableau de caractères. Quelque chose comme: char a[10]; strcpy(a, "Hello");

6
Naveen

Il semble que votre question ait reçu une réponse, mais vous pouvez maintenant vous demander pourquoi char * a = "String" est stocké dans une mémoire en lecture seule. En fait, il n’est pas défini par le standard c99, mais la plupart des compilateurs le choisissent de cette façon, par exemple:

printf("Hello, World\n");

norme c99 (pdf) [page 130, section 6.7.8]:

La déclaration:

char s[] = "abc", t[3] = "abc";

définit les objets "simples" de tableau de caractères s et t dont les éléments sont initialisés avec des littéraux de chaîne de caractères . Cette déclaration est identique à char 

s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };

Le contenu des tableaux est modifiable. D'autre part, la déclaration

char *p = "abc";

définit p avec le type "pointeur sur char" et l'initialise pour pointer sur un objet de type "tableau de char" de longueur 4 dont les éléments sont initialisés avec un littéral de chaîne de caractères. Si vous tentez d'utiliser p pour modifier le contenu du tableau, le comportement n'est pas défini.

3
Sweeney

Vous pouvez aussi utiliser strdup:

   The strdup() function returns a pointer to a new string which is a duplicate of the string  s.
   Memory for the new string is obtained with malloc(3), and can be freed with free(3).

Pour vous exemple:

char *a = strdup("stack overflow");
2
Maxime Chéramy

Toutes les réponses sont bonnes pour expliquer pourquoi vous ne pouvez pas modifier les littéraux de chaîne car ils sont placés dans une mémoire en lecture seule. Cependant, lorsque Push arrive à s’imposer, il existe un moyen de le faire. Découvrez cet exemple:

#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int take_me_back_to_DOS_times(const void *ptr, size_t len);

int main()
{
    const *data = "Bender is always sober.";
    printf("Before: %s\n", data);
    if (take_me_back_to_DOS_times(data, sizeof(data)) != 0)
        perror("Time machine appears to be broken!");
    memcpy((char *)data + 17, "drunk!", 6);
    printf("After: %s\n", data);

    return 0;
}

int take_me_back_to_DOS_times(const void *ptr, size_t len)
{
    int pagesize;
    unsigned long long pg_off;
    void *page;

    pagesize = sysconf(_SC_PAGE_SIZE);
    if (pagesize < 0)
        return -1;
    pg_off = (unsigned long long)ptr % (unsigned long long)pagesize;
    page = ((char *)ptr - pg_off);
    if (mprotect(page, len + pg_off, PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
        return -1;
    return 0;
}

J'ai écrit ceci dans le cadre de mes pensées un peu plus profondes sur la const-correctness , que vous pourriez trouver intéressantes (j'espère :)).

J'espère que ça aide. Bonne chance!

1
user405725

Vous devez copier la chaîne dans une autre mémoire tampon en lecture seule et la modifier à cet endroit. Utilisez strncpy () pour copier la chaîne, strlen () pour détecter la longueur de chaîne, malloc () et free () pour allouer de manière dynamique un tampon pour la nouvelle chaîne.

Par exemple (C++ comme pseudocode):

int stringLength = strlen( sourceString );
char* newBuffer = malloc( stringLength + 1 );

// you should check if newBuffer is 0 here to test for memory allocaton failure - omitted

strncpy( newBuffer, sourceString, stringLength );
newBuffer[stringLength] = 0;

// you can now modify the contents of newBuffer freely

free( newBuffer );
newBuffer = 0;
0
sharptooth
char *a = "stack overflow";
char *b = "new string, it's real";
int d = strlen(a);

b = malloc(d * sizeof(char));
b = strcpy(b,a);
printf("%s %s\n", a, b);
0
luca