Die Adressen von zwei Zeichenzeigern auf verschiedene Zeichenfolgenliterale sind gleich


80
#include<stdio.h>
#include<string.h>

int main()
{
    char * p = "abc";
    char * p1 = "abc";
    printf("%d %d", p, p1);
}

Wenn ich die Werte der beiden Zeiger drucke, wird dieselbe Adresse gedruckt. Warum?


66
Warum denkst du, sollte es nicht? Diese beiden Zeiger zeigen genau dasselbe. Was Sie sehen, ist wahrscheinlich der Effekt einer Optimierungstechnik, die als String-Pooling bezeichnet wird.
Daniel Kamil Kozar

2
Obwohl die Daten gleich sind, unterscheiden sich die Variablen.
Seereddi Sekhar

2
Die Variablen sind natürlich unterschiedlich. Wenn Sie die Adresse von pund genommen hätten p1, hätten Sie bemerkt, dass diese beiden Zeiger unter zwei unterschiedlichen Adressen gespeichert sind. Die Tatsache, dass ihr Wert gleich ist, ist in diesem Fall irrelevant.
Daniel Kamil Kozar

Ja, wenn ich die Werte ändere, sind die Adressen unterschiedlich.
Seereddi Sekhar

11
@ JanHudec: Lies die Frage noch einmal. In diesem Fall (aufgrund der Compileroptimierung) p == p1(sie unterscheiden sich nicht), aber &p != &p1(sie unterscheiden sich).
MSalters

Antworten:


86

Ob zwei verschiedene Zeichenfolgenliterale mit demselben Inhalt am selben Speicherort oder an verschiedenen Speicherorten abgelegt werden, hängt von der Implementierung ab.

Sie sollten immer pund p1als zwei verschiedene Zeiger behandeln (obwohl sie denselben Inhalt haben), da sie möglicherweise auf dieselbe Adresse verweisen oder nicht. Sie sollten sich nicht auf Compiler-Optimierungen verlassen.

C11 Standard, 6.4.5, String-Literale, Semantik

Es ist nicht spezifiziert, ob diese Arrays unterschiedlich sind, vorausgesetzt, ihre Elemente haben die entsprechenden Werte. Wenn das Programm versucht, ein solches Array zu ändern, ist das Verhalten undefiniert.


Das Druckformat muss sein %p:

  printf("%p %p", (void*)p, (void*)p1);

In dieser Antwort erfahren Sie, warum.


Ich habe volatile verwendet, damit es keine Speicheroptimierungen gibt, auch wenn sie dieselbe Adresse haben. Eine Frage ist, dass, wenn ich einen der Zeiger ändere, auch die Daten im anderen Zeiger geändert werden.
Megharaj

8
@ Megharaj i modify one of the pointer, will the data in the other pointed also be modifiedSie können den Zeiger ändern, aber nicht das Zeichenfolgenliteral. Zum Beispiel char *p="abc"; p="xyz";ist völlig in Ordnung , während char *p="abc"; p[0]='x';Invokes Verhalten undefiniert . Das hat nichts damit zu tun volatile. Ob Sie verwenden volatileoder nicht, sollte kein Verhalten ändern, das uns hier interessiert. volatileErzwingt grundsätzlich, die Daten jedes Mal aus dem Speicher zu lesen.
PP

2
@ MSharathHegde Ja. Weil pauf das String-Literal verweist "abc"und p[0]='x'versucht, das erste Zeichen eines String-Literals zu ändern. Der Versuch, ein String-Literal zu ändern, ist ein undefiniertes Verhalten in C.
PP

2
@MSharathHegde Weil der C-Standard dies angibt. Der Grund ist hauptsächlich historisch, da die vorstandardisierte C-Sprache das Ändern von Zeichenfolgenliteralen erlaubt. Später wurde es vom C-Standard (C89) undefiniert, sodass der neue Code dies nicht tut und der alte Code (vor dem Standard) so funktioniert, wie er war. Grundsätzlich ist es ein Kompromiss, vorhandenen Code (vor dem Standard) nicht zu beschädigen, glaube ich. Ein weiterer Grund ist, dass der Typ des Zeichenfolgenliteral char []in C ist. const char*Wenn es also schreibgeschützt ist ( wie dies in C ++ der Fall ist), muss auch der Typ geändert werden . [Fortsetzung]
PP

7
Es gibt eine Zeile in K & R 2. Ausgabe in Anhang C : "Strings are no longer modifiable, and so may be placed in read-only memory", ein historischer Beweis dafür, dass String-Literale früher modifizierbar waren ;-)
PP

28

Ihr Compiler scheint ziemlich schlau zu sein und erkennt, dass beide Literale gleich sind. Und da Literale konstant sind, hat der Compiler beschlossen, sie nicht zweimal zu speichern.

Es scheint erwähnenswert zu sein, dass dies nicht unbedingt der Fall sein muss. Bitte lesen Sie die Antwort von Blue Moon dazu .


Übrigens: Die printf()Aussage sollte so aussehen

printf("%p %p", (void *) p, (void *) p1);

as "%p"wird zum Drucken von Zeigerwerten verwendet und ist nur für Zeiger vom Typ definiert void *. * 1


Ich würde auch sagen, dass dem Code eine returnAnweisung fehlt , aber der C-Standard scheint gerade geändert zu werden. Andere könnten dies freundlicherweise klarstellen.


* 1: Das Casting void *hierher ist nicht für char *Zeiger erforderlich , sondern für Zeiger auf alle anderen Typen.


Vielen Dank. Die Schlussfolgerung lautet also: Compileroptimierung, oder? in C Hauptfunktion standardmäßig 0 zurückgibt
seereddi sekhar

@seereddisekhar: Ja, es ist eine Art Optimierung.
Alk

2
@seereddisekhar Aber seien Sie vorsichtig, es bedeutet nicht, dass Sie zwei Zeichenfolgen (sogar Zeiger) vergleichen sollten, indem ==Sie die strcmpy()Funktion verwenden. Da andere Compiler möglicherweise keine Optimierung verwenden (es liegt an der Compiler-Implementierung), antwortete Alk PS: Blue Moon hat gerade etwas hinzugefügt.
Grijesh Chauhan

2
Lieber @Megharaj: Darf ich Sie bitten, eine separate Frage zu stellen? Sie können hier einen Link zu dieser neuen Frage als Kommentar posten.
Alk

1
@ Megharaj: Sie können den Wert eines String-Literal nicht ändern. Wie ich in meiner Frage erwähnt habe, ist es konstant.
Alk

18

Ihr Compiler hat etwas namens "String Pooling" durchgeführt. Sie haben angegeben, dass Sie zwei Zeiger möchten, die beide auf dasselbe Zeichenfolgenliteral verweisen. Daher wurde nur eine Kopie des Literal erstellt.

Technisch: Es hätte sich bei Ihnen beschweren sollen, dass Sie die Zeiger nicht "const" gemacht haben.

const char* p = "abc";

Dies liegt wahrscheinlich daran, dass Sie Visual Studio oder GCC ohne -Wall verwenden.

Wenn Sie ausdrücklich möchten, dass sie zweimal im Speicher gespeichert werden, versuchen Sie Folgendes:

char s1[] = "abc";
char s2[] = "abc";

Hier geben Sie ausdrücklich an, dass Sie zwei C-String-Zeichenarrays anstelle von zwei Zeigern auf Zeichen möchten.

Vorsichtsmaßnahme: String-Pooling ist eine Compiler- / Optimierungsfunktion und keine Facette der Sprache. Daher erzeugen unterschiedliche Compiler in unterschiedlichen Umgebungen ein unterschiedliches Verhalten, abhängig von der Optimierungsstufe, den Compiler-Flags und der Frage, ob sich die Zeichenfolgen in unterschiedlichen Kompilierungseinheiten befinden.


1
gcc (Debian 4.4.5-8) 4.4.5beschwert sich nicht (warnt), obwohl mit -Wall -Wextra -pedantic.
Alk

1
Ja, ab V4.8.1 warnt gcc standardmäßig nicht davor, keine constZeichenfolgenliterale zu verwenden. Die Warnung wird optional aktiviert -Wwrite-strings. Es ist offenbar nicht durch eine andere Option aktiviert ist (wie zum Beispiel -Wall, -Wextraoder -pedantic).
Sleske

1
Sowohl GCC 4.4.7 als auch 4.7.2 geben mir die Warnung mit oder ohne -Wall. pastebin.com/1DtYEzUN
kfsone

14

Wie andere bereits gesagt haben, bemerkt der Compiler, dass sie denselben Wert haben, und entscheidet sich daher dafür, dass sie Daten in der endgültigen ausführbaren Datei gemeinsam nutzen. Aber es wird schicker: wenn ich folgendes mit kompilieregcc -O

#include<stdio.h>
#include<string.h>

int main()
{
  char * p = "abcdef";
  char * p1 = "def";
  printf("%d %d", p, p1);
}

es druckt 4195780 4195783für mich. Das heißt, p1beginnt 3 Bytes später p, sodass GCC das gemeinsame Suffix von def(einschließlich des \0Terminators) erkannt und eine ähnliche Optimierung wie die von Ihnen gezeigte durchgeführt hat.

(Dies ist eine Antwort, da es zu lang ist, um einen Kommentar abzugeben.)


3

Zeichenfolgenliterale im Code werden in einem schreibgeschützten Datensegment des Codes gespeichert. Wenn Sie ein String-Literal wie "abc" aufschreiben, wird tatsächlich ein 'const char *' zurückgegeben, und wenn Sie alle Compiler-Warnungen darauf hätten, würden Sie feststellen, dass Sie an diesem Punkt Casting durchführen. Sie dürfen diese Zeichenfolgen nicht aus dem Grund ändern, auf den Sie in dieser Frage hingewiesen haben.


2

Wenn Sie ein Zeichenfolgenliteral ("abc") erstellen, wird es in einem Speicher gespeichert, der Zeichenfolgenliterale enthält. Wenn Sie auf dasselbe Zeichenfolgenliteral verweisen, werden beide Zeiger auf dieselbe Stelle verweisen, an der das " abc "String-Literal wird gespeichert.

Ich habe das vor einiger Zeit gelernt, daher hätte ich es vielleicht nicht wirklich klar erklärt, sorry.


2

Dies hängt tatsächlich davon ab, welchen Compiler Sie verwenden .

In meinem System mit TC ++ 3.5 druckt es zwei verschiedene Werte für die beiden Zeiger also zwei verschiedene Adressen .

Ihr Compiler ist so konzipiert, dass er prüft, ob ein Wert im Speicher vorhanden ist, und abhängig von seiner Existenz dieselbe Referenz des zuvor gespeicherten Werts neu zuweist oder verwendet, wenn auf denselben Wert verwiesen wird.

Denken Sie also nicht zu viel darüber nach, da dies davon abhängt, wie der Compiler den Code analysiert .

DAS IST ALLES...


1

weil der String "abc" selbst eine Adresse im Speicher ist. Wenn Sie erneut "abc" schreiben, wird dieselbe Adresse gespeichert


1

Es ist eine Compiler-Optimierung, aber vergessen Sie die Optimierung für die Portabilität. Manchmal sind kompilierte Codes besser lesbar als tatsächliche Codes.


0

Sie verwenden String-Literal,

Wenn Complier zwei gleiche Zeichenfolgenliterale fängt,

es gibt den gleichen Speicherort an, daher zeigt es den gleichen Zeigerort an./

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.