web-dev-qa-db-fra.com

Comment différentes chaînes peuvent-elles avoir la même adresse

Je sais que pour comparer deux chaînes en C, vous devez utiliser la fonction strcmp(). Mais j'ai essayé de comparer deux chaînes avec le == opérateur, et cela a fonctionné. Je ne sais pas comment, car il compare simplement l'adresse des deux chaînes. Cela ne devrait pas fonctionner si les chaînes sont différentes. Mais ensuite j'ai imprimé l'adresse des cordes:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char* str1 = "First";
    char* str2 = "Second";
    char* str3 = "First";

    printf("%p %p %p", str1, str2, str3);

    return 0;
}

Et la sortie était:

00403024 0040302A 00403024
Process returned 0 (0x0)   execution time : 0.109 s
Press any key to continue.

Comment est-il possible que str1 et str3 avez la même adresse? Ils peuvent contenir la même chaîne, mais ils ne sont pas la même variable.

19
Drakalex

Il n'y a aucune garantie que ce sera toujours comme ça. En général, les implémenteurs maintiennent un pool de littéraux en conservant chacun des littéraux de chaîne une seule fois, puis pour plusieurs utilisations du littéral de chaîne, la même adresse est utilisée. Mais on pourrait l'implémenter d'une manière différente - la norme ne pose pas de contrainte à ce sujet.

Maintenant, votre question: vous regardez le contenu des deux pointeurs pointant vers le même littéral de chaîne. Le même littéral de chaîne a donné lieu à la même valeur (ils se sont désintégrés en un pointeur vers le premier élément). Mais cette adresse est la même pour la raison indiquée au premier paragraphe.

Aussi, je voudrais souligner la fourniture de l'argument de la %p spécificateur de format avec le (void*) cast.

23
user2736738

Il y a un point intéressant ici. Ce que vous avez en fait, ce ne sont que 3 pointeurs pointant tous vers const des chaînes littérales. Le compilateur est donc libre de créer une seule chaîne pour "First" et avoir les deux str1 et str3 pointez là.

Ce serait un cas complètement différent:

char str1[] = "First";
char str2[] = "Second";
char str3[] = "First";

J'ai déclaré 3 tableaux de caractères différents initialisés à partir de chaînes littérales. Testez-le et vous verrez que le compilateur a attribué des adresses différentes pour les 3 chaînes différentes.

Ce que vous devez retenir de cela: les pointeurs et les tableaux sont des animaux différents, même si les tableaux peuvent se désintégrer en pointeurs (plus à ce sujet dans ce post de la FAQ C )

14
Serge Ballesta

Lorsqu'un littéral de chaîne particulier apparaît plusieurs fois dans un fichier source, le compilateur peut choisir que toutes les instances de ce littéral pointent au même endroit.

La section 6.4.5 de la norme C , qui décrit les littéraux de chaîne, indique ce qui suit:

7 Il n'est pas précisé si ces tableaux sont distincts à condition que leurs éléments aient les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement n'est pas défini.

Lorsque le "comportement non spécifié" est défini à la section 3.4.4 comme suit:

utilisation d'une valeur non spécifiée ou d'un autre comportement lorsque la présente Norme internationale offre deux possibilités ou plus et n'impose aucune autre exigence sur laquelle est choisie dans tous les cas

Dans votre cas, la chaîne littérale "First" apparaît deux fois dans la source. Le compilateur utilise donc la même instance du littéral pour les deux, ce qui donne str1 et str3 pointant vers la même instance.

Comme indiqué ci-dessus, ce comportement n'est pas garanti. Les deux instances de "First" pourrait être distinct les uns des autres, ce qui entraînerait str1 et str3 pointant vers différents endroits. Le fait que deux instances identiques d'un littéral de chaîne résident au même endroit n'est pas spécifié.

9
dbush

Les littéraux de chaîne, tout comme les littéraux composés C99 +, peuvent être regroupés. Cela signifie que deux occurrences différentes dans le code source peuvent en fait entraîner une seule instance dans le programme en cours d'exécution.
Cela pourrait même être le cas si votre cible ne prend pas en charge la protection matérielle en écriture.

3
Deduplicator

La raison pour laquelle cela est si déroutant pourrait être: "Mais que se passe-t-il si je mets str1[1] = 'u';? "Étant donné que l'implémentation a défini si str1 == str3 (et si l'adresse du littéral "world!" est l'adresse de "hello, world!" plus 7), est-ce que ça tourne aussi str3 dans un prince allemand?

La réponse est: peut-être. Ou peut-être que cela ne change que str1, ou peut-être qu'il ne parvient pas à changer en silence, ou peut-être qu'il bloque le programme parce que vous avez écrit dans la mémoire en lecture seule, ou peut-être qu'il provoque un autre bogue subtil parce qu'il a réutilisé ces octets pour un autre but, ou autre chose entièrement.

Le fait que vous pouvez même affecter un littéral de chaîne à un char* du tout, au lieu d'avoir à utiliser const char*, est fondamentalement dépouillé pour le bien d'un code hérité vieux de plusieurs décennies. Les premières versions de C n'avaient pas const. Certains compilateurs existants permettent aux programmes de modifier les constantes de chaîne, et d'autres non. Lorsque le comité des normes a décidé d'ajouter le mot clé const de C++ à C, il n'était pas disposé à casser tout ce code, alors il a donné aux compilateurs la permission de faire pratiquement n'importe quoi lorsqu'un programme modifie un littéral de chaîne.

L'implication pratique de ceci est: ne jamais affecter un littéral de chaîne à un char* ce n'est pas const. Et ne supposez jamais que les constantes de chaîne se chevauchent ou ne se chevauchent pas (sauf si vous le garantissez avec restrict). Ce type de code est obsolète depuis 1989 et vous permet simplement de vous tirer une balle dans le pied. Si vous voulez un pointeur sur un littéral de chaîne (qui peut ou non partager la mémoire avec d'autres constantes), stockez-le dans un const char* ou, mieux encore, const char* const. Cela vous avertit si vous essayez de le modifier. Si vous voulez un tableau de char qui peut être modifié (et est garanti de ne pas alias d'autres variables), stockez-le dans un char[].

Si vous pensez que vous voulez comparer des chaînes par leurs adresses, ce que vous voulez vraiment, c'est une valeur de hachage ou un handle unique.

2
Davislor

Pour ajouter aux autres réponses: il s'agit d'une technique appelée string interning où le compilateur se rend compte que les chaînes sont les mêmes et ne les stocke donc qu'une seule fois. Java a tendance à faire cela aussi (bien que, comme mentionné par l'autre affiche, cela dépend du compilateur).

1
wolfson