Es geht sogar noch weiter:
char *a = "123";
char *b = "3";
Wenn man jetzt Zeiger vergleicht:
(a+2) == b
Dann ist bei hoher Optimierung das Resultat oftmals true und bei niedriger meist false.
Da auf unspezifiziertem Verhalten basierend.
Wobei die Spezifikation in einem Punkt recht klar ist: Zeigervergleiche sind nur spezifiziert, wenn sie auf die Identische Speicherstruktur zugreifen. Und eben das ist hier nicht gewährleistet.
Das ist mir mehr als klar. Darum verwende ich meist die {} Initialisierung.
Eben um Verwechselungen auszuschließen. Und {} Initialisierungen sind Typesicherer.
int a = 3.14; // geht ohne Murren durch
int b {3.14}; // erregt Aufmerksamkeit
Sowas habe ich schon lange aufgegeben!
Da kommen zu viele Randbedingungen ins Spiel. Da lohnt es sich eher den generierten Code zu analysieren.
Wie Zeichenketten Literale im Speicher angeordnet, wer auf was Verweist, ist in C und C++ nicht spezifiziert. Das schafft Raum für Optimierungen der Compilerersteller. Es ist also eigentlich völlig Sinnfrei, über das "Wie?" nachzudenken, wenn man selber keinen Compiler bauen will.
Jeglicher Code, der sich auf eine solcherart Annahme stützt ist per Definition fehlerhaft.
Denn er kann mit jeder Optimierungsstufe, Compiler Version oder Ersteller, Zielprozessor etwas anderes produzieren.
So auch dieser:
#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout = Serial; // cout Emulation für Arme
const char* const a = "123";
const char b[] = "123";
const char *c = "123";
const char d[] = "123";
void setup()
{
Serial.begin(9600);
cout << F("Start: ") << F(__FILE__) << endl;
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
cout << "123" << endl;
cout << "---------------" << endl;
// mogeln
char *ch1 = (char *)b; // const modifizierer entfernen
*ch1 = 'X';
char *ch2 = (char *)c; // const modifizierer entfernen
*ch2 = 'Y';
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
cout << "123" << endl;
}
void loop()
{
}
Resultat:
Start: E:\Programme\arduino\portable\sketchbook\sketch_jul10c\sketch_jul10c.ino
123
123
123
123
123
---------------
Y23
X23
Y23
123
Y23
Hier muss man streng unterscheiden:
Undefiniertes Verhalten:
Wenn man in sein Programm ein undefiniertes Verhalten einbaut, kann es passieren, dass der Arduino einem das Wohnzimmer neu tapeziert.
Unspezifiziertes Verhalten:
Der Compiler tut es, er tut es auch so, dass es funktioniert. Das ist spezifiziert. Aber wie er es tut/erreicht, bleibt ihm überlassen. Vollständig.
Implementation definiertes Verhalten:
Der Compiler tut, was er soll. Die Compiler Doku liefert die Spezifikation für das Verhalten. Die Compiler der verschiedensten Ersteller unterscheiden sich in dem Punkt.
Spezifiziertes Verhalten:
Das Verhalten ist in der Sprache selber festgeschrieben. Alle Compiler verhalten sich gleich.
Ich habe das mal auf 4 verschiedene Verhaltensmuster runter gebrochen.
Man sollte sich schon tunlichst im spezifizierten Bereich bewegen, wenn man keine Wackelkandidaten bauen will.