Je peux voir deux littéraux 'some'
dans le code assembleur généré par MSVC, mais un seul avec clang et gcc. Cela conduit à des résultats totalement différents de l'exécution du code.
static const char *A = "some";
static const char *B = "some";
void f() {
if (A == B) {
throw "Hello, string merging!";
}
}
Quelqu'un peut-il expliquer la différence et les similitudes entre ces sorties de compilation? Pourquoi clang/gcc optimise-t-il quelque chose même lorsqu'aucune optimisation n'est demandée? Est-ce une sorte de comportement indéfini?
Je remarque également que si je modifie les déclarations par celles indiquées ci-dessous, clang/gcc/msvc ne laisse aucun "some"
dans le code assembleur. Pourquoi le comportement est-il différent?
static const char A[] = "some";
static const char B[] = "some";
Ce n'est pas un comportement indéfini, mais un comportement non spécifié. Pour littéraux de chaîne ,
Le compilateur est autorisé, mais non obligatoire, à combiner le stockage pour des littéraux de chaîne égaux ou superposés. Cela signifie que des littéraux de chaîne identiques peuvent ou non être comparés de la même manière par pointeur.
Cela signifie que le résultat de A == B
pourrait être true
ou false
, sur lequel vous ne devriez pas dépendre.
De la norme, [Lex.string]/16 :
Que tous les littéraux de chaîne soient distincts (c'est-à-dire qu'ils soient stockés dans des objets ne se chevauchant pas) et que les évaluations successives d'un littéral de chaîne donnent le même objet ou un objet différent n'est pas spécifié.
Les autres réponses expliquent pourquoi vous ne pouvez pas vous attendre à ce que les adresses de pointeur soient différentes. Pourtant, vous pouvez facilement récrire ceci de manière à garantir que A
et B
ne se comparent pas entre égaux:
static const char A[] = "same";
static const char B[] = "same";// but different
void f() {
if (A == B) {
throw "Hello, string merging!";
}
}
La différence étant que A
et B
sont maintenant des tableaux de caractères. Cela signifie qu'ils ne sont pas des pointeurs et que leurs adresses doivent être distinctes, tout comme celles de deux variables entières. C++ confond ceci parce que les pointeurs et les tableaux semblent interchangeables (operator*
et operator[]
semblent se comporter de la même manière), mais ils sont vraiment différents. Par exemple. quelque chose comme const char *A = "foo"; A++;
est parfaitement légale, mais const char A[] = "bar"; A++;
ne l’est pas.
Une façon de penser à la différence est que char A[] = "..."
dit "donnez-moi un bloc de mémoire et remplissez-le avec les caractères ...
suivi de \0
", alors que char *A= "..."
dit " donnez-moi une adresse à laquelle je peux trouver les caractères ...
suivi de \0
".
Qu'un compilateur choisisse ou non d'utiliser le même emplacement de chaîne pour A
et B
appartient à l'implémentation. Formellement, vous pouvez dire que le comportement de votre code est non spécifié.
Les deux choix implémentent correctement la norme C++.
C'est une optimisation pour économiser de l'espace, souvent appelée "pooling de chaînes". Voici la documentation pour MSVC:
https://msdn.Microsoft.com/en-us/library/s0s0asdt.aspx
Par conséquent, si vous ajoutez/GF à la ligne de commande, vous devriez voir le même comportement avec MSVC.
En passant, vous ne devriez probablement pas comparer les chaînes avec des pointeurs comme ceux-là. Tout outil d'analyse statique correct signalera ce code comme étant défectueux. Vous devez comparer ce à quoi ils pointent, pas les valeurs réelles du pointeur.