Beschädigter String in C #

70

Ich bin auf "CorruptedString" (Lösung) gestoßen . Hier ist folgender Programmcode aus dem Buch:

var s = "Hello";
string.Intern(s);
unsafe
{
  fixed (char* c = s)
    for (int i = 0; i < s.Length; i++)
      c[i] = 'a';
}
Console.WriteLine("Hello"); // Displays: "aaaaa"

Warum zeigt dieses Programm "aaaaa" an? Ich verstehe dieses Programm wie folgt:

  1. Die CLR reserviert "Hallo" im internen Pool (ich stelle mir den internen Pool als eine Reihe von Zeichenfolgen vor).
  2. string.Intern(s) tut eigentlich nichts, weil die CLR die Zeichenfolge "Hallo" reserviert hat - sie gibt nur die Adresse der reservierten Zeichenfolge "Hallo" zurück (Objekt s hat dieselbe Adresse)
  3. Das Programm ändert den Inhalt der Zeichenfolge "Hallo" über einen Zeiger
  4. ??? Die Hello-Zeichenfolge sollte im internen Pool fehlen und ein Fehler sein! Aber es ist in Ordnung; Das Programm wird erfolgreich ausgeführt.

Wie ich den internen Pool verstehe, ist es wie eine Art Wörterbuch von String zu String. Oder habe ich etwas verpasst?

LmTinyToon
quelle
11
für (int i = 0; i <s.Length; i ++) c [i] = 'a'; Scheint, als würden Sie jedes Zeichen durch das Zeichen für 'a' ersetzen
Dieter B
Es ist nicht wie ein Wörterbuch von String zu String. Es ist eher wie ein Hashset von String
M.kazem Akhgary
8
Das unsichere Schlüsselwort gibt Ihnen einen Hinweis ... :-)
23
Sie haben das Sicherheitssystem ausgeschaltet und dann eine Menge Müll in den Speicher geschrieben, den Sie nicht besitzen. An diesem Punkt kann alles passieren , also die Antwort auf "Warum ist X passiert?" ist "X passiert ist konsistent mit allem, was passieren kann ". Sie haben das Recht verloren, in einer vorhersehbaren Welt zu leben, als Sie das Sicherheitssystem ausgeschaltet und dann das Privileg missbraucht haben.
Eric Lippert
3
lustiges OT: das funktioniert auch in java.
JIV

Antworten:

65

Wenn Sie "Hallo" zum ersten Mal verwenden, wird es in den globalen String-Speicher der Anwendung integriert. Basierend auf der Tatsache, dass Sie im unsafeModus ausführen (mehr dazu unsafe hier ), erhalten Sie einen direkten Verweis auf Daten, die an den Speicherorten gespeichert sind, die ursprünglich für den Wert der Zeichenfolge zugewiesen swurden

for (int i = 0; i < s.Length; i++)
      c[i] = 'a';

Sie bearbeiten, was sich im Speicher befindet. Wenn es das nächste Mal auf den Speicher für internierte Zeichenfolgen zugreift, wird dieselbe Adresse im Speicher verwendet, die die gerade geänderten Daten enthält. Ohne wäre das nicht möglich unsafe. string.Intern(s);spielt hier keine Rolle; es verhält sich genauso, wenn Sie es auskommentieren.

Dann vorbei

Console.WriteLine("Hello"); // Displays: "aaaaa"

.NETprüft, ob es einen Eintrag für eine Adresse gibt, für die er erhalten wurde, "Hello"und einen: den, den Sie gerade aktualisiert haben "aaaaa". Die Anzahl der 'a'Zeichen wird durch die Länge von bestimmt "Hello".

Jaroslav Kadlec
quelle
6
Tut string.Intern(s)eigentlich nichts in diesem Programm. Sie können diese Zeile kommentieren und das Programm funktioniert genauso (weil "Hallo" reserviert hatte). Aber ich stimme Ihnen zu
LmTinyToon
Ich nehme an, dass JIT alle Vorkommen von Literal durch seinen Getter to Table durch den angegebenen berechneten Hash-Schlüssel ersetzt. Zu diesem Zeitpunkt hat sich der angegebene Wert geändert
LmTinyToon
1
".net prüft, ob ein Eintrag für den von" Hallo "erhaltenen Hash-Schlüssel vorhanden ist und ob. - was? Wenn zwei Hashes gleich sind, bedeutet dies, dass Equals ausgeführt werden sollte. Ich habe keine Ahnung, wie es funktioniert, aber ich denke, alles wird zur Kompilierungszeit erledigt (?). Console.WriteLine("Hello");- "Hallo" ist nur ein Verweis auf bereits bekannte Zeichenfolge.
Apokalypse
1
Dieser Schritt wird von JIT ausgeführt. Zur Kompilierungszeit kennen Sie nur Literale, ohne die interne Tabelle zu erwähnen.
LmTinyToon
6
Huh. Sohn eines JIT.
C. Tewalt
5

Obwohl die Antwort von @Jaroslav Kadlec korrekt und vollständig ist, möchte ich weitere Informationen zum Verhalten des Codes hinzufügen und erklären, warum diesstring.Intern(s); in diesem Fall nutzlos ist .

Über Intern Pool

Tatsächlich führt .NET die String-Internierung für alle String-Literale automatisch aus. Dazu wird eine spezielle Tabelle verwendet, in der Verweise auf alle eindeutigen Strings in unserer Anwendung gespeichert sind.

Es ist jedoch wichtig zu beachten, dass in der Kompilierungsphase nur explizit deklarierte Zeichenfolgen interniert werden .

Betrachten Sie den folgenden Code:

var first = "Hello"; //Will be interned
var second = "World"; //Will be interned
var third = first + second; //Will not be interned

Natürlich möchten wir unter bestimmten Umständen einen String zur Laufzeit internieren, und dies kann String.Internnach Überprüfung mit erfolgen String.IsInterned.

Kommen wir also zum Ausschnitt des OP zurück:

//...
var s = "Hello";
string.Intern(s);
//...

In diesem Fall string.Intern(s);ist es nutzlos, da es bereits in der Kompilierungsphase interniert ist.

Sid
quelle
1
Wichtig bei der Verwendung zu erwähnen string.Intern(s). Zeichenfolgen können nicht interniert werden (siehe msdn.microsoft.com/en-us/library/… )
LmTinyToon