String bekommt neue Adresse

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Mathias
Beiträge: 6956
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

String bekommt neue Adresse

Beitrag von Mathias »

Wieso wechselt der String die Adresse, wen ich nur das 6. Zeichen ändere ?
PtrUInt spuckt verschieden Werte aus.
Mache ich noch eine Änderung, das 8. Zeichen, dann bleibt die Adresse gleich.

Code: Alles auswählen

var
  s: string;
begin
  s := 'Hello World';
  WriteLn(PtrUInt(s): 16, '  ', s);
  s[6] := 'X';
  WriteLn(PtrUInt(s): 16, '  ', s);
  s[8] := 'Y';
  WriteLn(PtrUInt(s): 16, '  ', s);  
end.  
Ausgabe:

Code: Alles auswählen

         4358160  Hello World
 139716655480912  HelloXWorld
 139716655480912  HelloXWYrld
Die ist jetzt nur ein kleiner String, aber bei langen String ist dies sicher ineffizient.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Nixsager
Beiträge: 168
Registriert: Sa 8. Okt 2016, 08:38
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit
Wohnort: Polska

Re: String bekommt neue Adresse

Beitrag von Nixsager »

Reine Vermutung!

Weiß nicht wie ich mich ausdrücken kann.
Ich vermute, das intern die RT(?) nach der ersten Änderung neuen Speicher hinzufügt weil ihm das Objekt nicht so ganz bekannt ist, und bei den nächsten ändert sich ja nur ein Byte. Da muss dir RT nichts neues zuweisen

Versuche mal andere Typen, wo du nur ein Byte änderst.

Ich packe das mal in eine Schleife und lassen 'Random' über die Position und dem Charakter laufen.
Jeder der sagt, ich könnte programmieren, der hat noch weniger Ahnung vom programmieren als ich!!!

Warf
Beiträge: 2142
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: String bekommt neue Adresse

Beitrag von Warf »

Ganz einfach, 'Hello World' ist ein Konstanter string, der mit in die Anwendung kompiliert wird und bei der ausführung im Konstantenspeicher rumliegt und sich während der Laufzeit nie ändert. Wenn du dann was an dem String ändern willst dann muss eine neue Änderbare Kopie erzeugt werden.

Und das ist auch gut so, stell dir mal vor das wäre nicht der fall:

Code: Alles auswählen

for i:=0 to 10 do
  WriteLn('I ist: ', i);
Wenn der String 'I ist' in diesem Fall nicht Konstant wäre, müsste der in jeder Iteration eine Kopie davon erzeugen, so wird einfach immer wieder der gleiche String benutzt.

Das gilt auch für verschiedene Variablen:

Code: Alles auswählen

  s1 := 'Hello World';
  s2 := 'Hello World';
  WriteLn(IntPtr(s1) = IntPtr(s2)); // True
Hier bekommen s1 und s2 das selbe String Objekt weil es der selbe string ist. Wenn nicht müssten hier 2 Kopien angefertigt werden.

Zusätzlich dazu ist es generell so das Strings so genanntes Lazy Copy verfahren haben. D.h. wenn du einen String änderst aber mehrere Referenzen auf den selben String hast, dann wird eine Kopie erzeugt, um die anderen Strings nicht zu beeinflussen:

Code: Alles auswählen

  ReadLn(s1);
  s2 := s1;
  WriteLn('S1: ', s1);
  WriteLn('S2: ', s2); // gleich
  s1[1] := 'F';
  WriteLn('S1: ', s1); // Geändert
  WriteLn('S2: ', s2); // Original 
Wenn du aber nur eine Referenz auf den string hast, dann wird einfach in-place geändert ohne eine kopie zu erzeugen (daher lazy copy, da immer nur kopiert wenn notwendig)

martin_frb
Beiträge: 588
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: String bekommt neue Adresse

Beitrag von martin_frb »

1) In diesem Falle ist der erste string eine Konstante.

2) Copy-on-write

Wenn ein string (mit mehr als einer Referenz) verändert wird, dann wird er kopiert, so dass die anderen Variablen sich nicht ändern (dyn arrays, werden NICHT kopiert).

Code: Alles auswählen

var a,b,c: string;
begin
  a := inttostr(random(999)); // ref cnt = 1
  b := a; // beide haben dieselbe Adresse  // beide ref cnt = 2
  c := a; // alle haben dieselbe Adresse  // alle ref cnt = 3
  a[1] := 'x'; // neue Adresse für a, a hat ref cnt = 1 /// b und c haben die alte Adresse und ref cnt = 2
  
  a[1] := '0'; // a behält Adresse, weil ref cnt war 1 => also keine anderen variablen...
  
"b := a" > die selbe Adresse bedeutet
PtrUint(a)=PtrUint(a)
@a[1] = @b[1]
Aber
@a <> @b // 2 variablen
Zuletzt geändert von martin_frb am Mi 12. Jul 2023, 21:40, insgesamt 1-mal geändert.

martin_frb
Beiträge: 588
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: String bekommt neue Adresse

Beitrag von martin_frb »

Den ref cnt kann man in Lazarus 3.0 mit FpDebug beobachten: https://wiki.freepascal.org/FpDebug-Wat ... r-array.29

Mathias
Beiträge: 6956
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: String bekommt neue Adresse

Beitrag von Mathias »

Ups, an das habe ich gar nicht gedacht, das der String ursprünglich nur auf die Konstante zeigt.
Jetzt funktioniert auch folgendes:

Code: Alles auswählen

const
  s: string;
var
  c: PChar;

begin
  s := 'Hello World';
  WriteLn(PtrUInt(s): 16, '  ', s);
  s[6] := 'X';
  WriteLn(PtrUInt(s): 16, '  ', s);
  c := PChar(s);
  WriteLn(PtrUInt(@s[1]): 16, '  ', s);
  WriteLn(PtrUInt(@c[0]): 16, '  ', c);
  s[1] := '-';
  WriteLn(PtrUInt(@s[1]): 16, '  ', s);
  WriteLn(PtrUInt(@c[0]): 16, '  ', c);
  c[0] := '+';
  WriteLn(PtrUInt(@s[1]): 16, '  ', s);
  WriteLn(PtrUInt(@c[0]): 16, '  ', c);
end.    
Und dies wird mit einem Laufzeitfehler 216 quittiert, weil er versucht in den Bereich der Konstante zu schreiben.

Code: Alles auswählen

begin
  s := 'Hello World';
  c := PChar(s);
  c[0] := '+';      
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten