Autor |
Beitrag |
trm
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Di 06.07.10 00:53
Hallo,
wieder ein neues Problem, bei dem ich einen Denkanstoß brauche:
Ich deklariere einen eigenen Typ:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7:
| type T_GridVariables = record NewDay_Caption: string; GridCell_Uhrzeit: string; GridCell_Nummer: string; GridCell_Name: string; end; |
Und nutze den Typ als Array:
Delphi-Quelltext 1: 2:
| var GridVariables: array of T_GridVariables; |
Nun kommt es vor, dass in den Strings ein Apostroph drin steht (oder mehrere).
Bisher filtere ich die einfach mit einer Schleife, wenn ich die vorher eingefügten Inhalte auslese mittels StringReplace.
Gibt es eine andere, schnellere Möglichkeit (vielleicht über einen Zugriff als Pointer auf das Array) aus allen Feldern die Apostrophe zu entfernen, da meine Schleife sonst zu stark ausgebremst wird?
Gruß
~Mathias Moderiert von Narses: Topic aus Sonstiges (Delphi) verschoben am Di 06.07.2010 um 13:40
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Di 06.07.10 05:45
Pointer oder nicht, du musst ja alle Strings durchgehen. Besonders viel schneller wird das nicht gehen.
Die Frage ist aber woher diese Strings kommen. Denn wenn du diese bereits beim Eintragen in den Record löschst, wird es vermutlich schneller.
Dann ist noch die Frage ob du die löschst oder durch ein anderes Zeichen ersetzt. Wenn du ein Zeichen nur durch ein anderes ersetzt, wird es auch schnellere Möglichkeiten geben.
|
|
ASMFreak
      
Beiträge: 53
Erhaltene Danke: 9
|
Verfasst: Di 06.07.10 08:56
trm,
jaenicke hat Recht. Sobald etwas aus einem String entfernt wird, muss ein neuer erstellt werden und das dauert. Dennoch ein anderer Ansatz:
In SysUtils gibt es die Funktion AnsiDequotedStr, die AnsiExtractQuotedStr aufruft. Ich habe das nicht getestet, aber vielleicht ist die schneller als StringReplace.
Die Sache mit dem Pointer auf das Array ist im ersten Moment eine gute Idee, da man dann (z.B. mit Assembler und SCANSB) auch große Speicherbereiche schnell scannen kann. Man könnte dann mit STOSB den Apostrophen einfach überschreiben (womit auch immer). Ihn entfernen geht so nicht so einfach!
Es scheitert nur daran, dass im Array bzw. den Records, aus denen es besteht, nicht die Strings gespeichert sind, sondern nur deren Adressen. Was bedeutet, dass man jede Adresse auslesen müsste, um dann den einzelnen String zu bearbeiten. Und das tust Du schon mit Deiner Lösung! Es kann also nur darum gehen, das "langsame" StringReplace zu ersetzen.
Ein Ansatz, der schnellen Assembler nutzt, könnte darin bestehen: Man könnte mit SCANSB die Position des Apostrophen finden und alles danach mit MOVSB um eine Position "nach vorne" verschieben. Stringgröße anpassen(, das abschließende #0 nicht vergessen!) und weitersuchen. Das geht vermutlich sehr viel schneller als StringReplace. Allerdings hinterlässt man dann Speicherlöcher, da der String ja nicht neu aufgebaut wird und seinen Platz nicht ändert. Und im Falle dass der String dann irgendwann einmal gelöcht wird, werden eben nur die (um die Anzahl der entfernten Apostrophen reduzierten) Bytes im Speicher gelöscht. Ob die Speichervewaltung das packt ... Und vergrößern darf man den String auch nicht ...
Mehr fällt mir in der Kürze nicht ein.
Gruß
|
|
Horst_H
      
Beiträge: 1654
Erhaltene Danke: 244
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: Di 06.07.10 15:21
Hallo,
in Assembler alles per move zu verschieben ist ja nicht besonders sinnvoll, da man ja x-fach die hinteren Bereiche verschiebt.
Eine Pascal Procedure macht es auch fix.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102:
| type tpChar = array[1..1] of char;
procedure DelChars(delChar:char;var s:string);
var i,j :integer; pOrgChr, pNewChr : ^tpChar; Aktchar : char; begin j := 1; pOrgChr := @s[1]; pNewChr := pOrgChr; For i := 1 to length(s) do begin Aktchar := pOrgChr^[i]; IF AktChar <> delChar then begin pNewChr^[j] := Aktchar; inc(j); end; end; setlength(s,j-1); end;
|
Wenn DelChar eine Konstate geht es vielleicht noch schneller.
~20% der Zeit braucht setlength bei einem 670 Zeichen langen String.
Gruß Horst
Einloggen, um Attachments anzusehen!
Zuletzt bearbeitet von Horst_H am Di 06.07.10 15:45, insgesamt 1-mal bearbeitet
|
|
trm 
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Di 06.07.10 15:34
Hallo
Danke für die Antworten.
jaenicke, ich lese zuerst Daten aus einer Datenbank aus. Diese Daten werden sortiert in meinem Array-Typ abgelegt. Das sind die Rohdaten.
Anschließend gehe ich mein Array durch und gebe die Daten weiter. (Anmerkung: ich kann die Daten nicht direkt übernehmen, weil die Ausgabe auf verschiedene Ziele geht. Z.B. in eine CSV, HTML und Excel. Darum benötige ich die Rohdaten, um später wieder einzufügen. Das ist auch für die Übersichtlichkeit im Programm besser.)
Nun ist es aber so, dass mich die Apostrophe beim Export in CSV oder HTML nicht stören, die Quote ich dann einfach bei Bedarf in Anführungszeichen ( " <- 1 Zeichen, keine zwei Apostrophe ).
ASMFreak, Deine Idee konnte ich leider nicht so recht nachvollziehen, da mir das Wissen in ASM fehlt.
Jedoch habe ich mir die Funktionen mal angeguckt und verstehe hier einiges nicht:
Delphi-Quelltext 1: 2: 3: 4: 5:
| function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string; [..] Result := ''; if (Src = nil) or (Src^ <> Quote) then Exit; Inc(Src); |
Bewirkt dieser Vergleich: (Src^ <> Quote) eine Prüfung, ob das Zeichen im String vorhanden ist ?
*Wenn ja, ist so ein Vergleich auch auf mein Array-Typ komplett anwendbar? Denn dann könnte ich eine Prüfung vornehmen und direkt entscheiden, ob ich StringReplace nutzen muss oder nicht. Außerdem, könnte man mit so einem Verfahren herausbekommen, an welcher Stelle ein Zeichen im Array liegt und das dann gezielt anspringen, um nicht von 0..High(array) durchlaufen zu müssen?
Und seit wann kann man ein PChar mittels inc erhöhen - oder wird hierbei nur der Pointer auf den String gesetzt ?
Danke für die Hilfe,
liebe Grüße
~Mathias
Edit: Hallo Horst, habe Deinen Beitrag eben erst gelesen. Ich war noch mit der Beantwortung beschäftigt, als Du geschrieben hast. Ich guck mir das mal an, ist aber im Endeffekt auch wieder eine Stringbasierte Lösung. Aber vielleicht schneller als mit StringReplace. Danke für dafür 
|
|
Horst_H
      
Beiträge: 1654
Erhaltene Danke: 244
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: Di 06.07.10 17:05
Hallo,
Falls Du mal DelApos.dpr testen möchtest:
ich habe auf dem Notebook mit PentiumM 1700 Mhz etwa 300-350 Mb/s mit freepascal und 250 mit Turbo Delphi 2006 erreicht.
Mit Freepascal64 und Linux64 waren es bei einem Athlon 2 X2 2900 Mhz auch nur 380 Mb/s , aber unter 64 Bit erzeugt der Compiler merkwürdigen Code.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:
| |
Gruß Horst
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Di 06.07.10 18:02
@Horst_H: Das ist eine Branch-Negation, die man öfters mal antrifft, um die Branch-Prediction der CPU unter Kontrolle zu bekommen. Auch erreicht man damit, dass man die kurze Jump-Befehle nutzen kann, falls man doch u.U. etwas längere Wege zurückzulegen hat.
Was er aber mit dem %bl anstellt, kann ich grad nicht sagen, da ich mir dazu den x64-ASM noch zu wenig angeschaut hab.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
trm 
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Di 06.07.10 18:04
Hallo Horst,
ich kann mich im Bezug auf ASM nicht groß äußern, auch wenn Du das schön mit Pascalcode kommentiert hast.
Zu Deiner Funktion, habe ich anzumerken, dass es bei mir zu einem Fehler bei der Bereichsprüfung kommt (ERangeError). Dies aber erst im zweiten Durchlauf der Schleife, bei dieser Zuweisung: Aktchar := pOrgChr^[i];
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| for i := 1 to length(s) do begin Aktchar := pOrgChr^[i]; if AktChar <> delChar then begin pNewChr^[j] := Aktchar; |
Ich versuche noch ein wenig herum
Gruß
~Mathias
|
|
Horst_H
      
Beiträge: 1654
Erhaltene Danke: 244
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: Di 06.07.10 18:14
Hallo,
das liegt an meiner Typ-Definition
Delphi-Quelltext 1: 2: 3: 4:
| type tpChar = array[1..1] of char; |
Wie ich oben geschrieben habe geht das nur ohne RangeCheck
Probiere mal es so, oder
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
| type tpChar = array[1..1] of char;
procedure DelChars(delChar:char;var s:string);
var i,j :integer; pOrgChr, pNewChr : ^tpChar; Aktchar : char; begin j := 1; pOrgChr := @s[1]; pNewChr := pOrgChr; {$RangeChecks Off} For i := 1 to length(s) do begin Aktchar := pOrgChr^[i]; IF AktChar <> delChar then begin pNewChr^[j] := Aktchar; inc(j); end; end; {$RangeChecks On} setlength(s,j-1); end; |
Gruß Horst
Für diesen Beitrag haben gedankt: trm
|
|
trm 
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Di 06.07.10 18:50
Huhu,
hm, stimmt. ich hatte die Bereichsprüfung noch an (hatte mir jemand empfolen, um einen Speicherfehler zu finden).
Nach dem Ausschalten war die Fehlermeldung dann auch weg
Jetzt gehts. Und es ist für jedes Feld im Array wesentlich schneller, als wenn ich StringReplace nutze.
Ich habe dazu mit QueryPerformancecounter gemessen.
DelChars: 11 - 17 ms
StringReplace: 107 - 110 ms
Das ist schon erstaunlich.
Lustigerweise hatte ich mal so eine ähnliche Variente geschrieben, damit kann ich eine Menge von Chars aus einem String austauschen/wegschippeln. Bei mir kam es auf eine zusammenhängende Menge an, z.B. 0..36:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| function DelChar_smaller_Range(Dummy_String: string; Dummy_Char_Range_Start, Dummy_Char_Range_End: Integer): string; var x: Integer; begin Result := ''; for x := 0 to Length(Dummy_String) do if not (Dummy_String[x] in [Chr(Dummy_Char_Range_Start)..Chr(Dummy_Char_Range_End)]) then Result := Result + Dummy_String[x]; end; |
Das stelle ich mal schnell auf die aktuelle Lage um und berichte gleich vom Ergbnis
Edit:
Umgestellt und Fehler (for x:=1 statt for x:=0) beseitigt:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| function TForm1.DelChar_smaller_Range(Dummy_String: string; Dummy_Char_Range_Start, Dummy_Char_Range_End: Integer): string; var x: Integer; begin Result := ''; for x := 1 to Length(Dummy_String) do if not (Dummy_String[x] in [Chr(Dummy_Char_Range_Start)..Chr(Dummy_Char_Range_End)]) then Result := Result + Dummy_String[x]; end; |
ABER damit dauert es nochmal 100 ms länger als mit StringReplace.
Demzufolge ist Deine Idee die beste, die ich bisher gesehen habe.
Ich denke, damit kann ich gut arbeiten.
Dankeschön. 
|
|
Horst_H
      
Beiträge: 1654
Erhaltene Danke: 244
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: Di 06.07.10 19:35
Hallo,
falls Du lieber eine Funktion benutzen willst:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| type tpChar = array[1..maxlongint] of char;
function DelChars(delChar:char;const s:Ansistring):AnsiString; var i,j :integer; pOrgChr, pNewChr : ^tpChar; Aktchar : char; begin setlength(result,length(s)); j := 1; pOrgChr := @s[1]; pNewChr := @Result[1]; For i := 1 to length(s) do begin Aktchar := pOrgChr^[i]; IF AktChar <> delChar then begin pNewChr^[j] := Aktchar; inc(j); end; end; setlength(result,j-1); end; |
Viel Spaß damit
Zu Deiner Funktion:
Direkt den maximalen Speicherplatz für result belegen, wie bei meiner Funktion.
Result := Result + Dummy_String[x]; ist !sehr! langsam.
Gruß Horst
|
|
ASMFreak
      
Beiträge: 53
Erhaltene Danke: 9
|
Verfasst: Di 06.07.10 22:01
Mathias,
nur um Deine Frage noch zu beantworten:
Bei PChars handelt es sich um Zeiger; "Pointer to a CHARacter". Die kann man natürlich mit INC inkrementieren. Da Src ein PChar ist, heißt (Src^ <> Quote) nichts anderes als "wenn das char an der Position, auf die Src zeigt, verschieden vom Qoute (= ein Zeichen) ist, dann ..."
Nachdem Du aber offensichtlich eine passende Lösung gefunden hast, ist das Schnee von übergestern!
Gruß,
Trutz
|
|
trm 
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Di 06.07.10 22:55
Huhu,
danke für die Erklärung.
Dennoch verstehe ich nicht, warum Delphi den Weg über Pchar geht. Ich habe immer mal gelesen, dass ein String ein Array of Pchar ist. Warum übergibt man dann also nicht gleich den String, sondern packt den in ein Pchar rein?
Wenn man String[x] nimmt, bekommt man doch auch die einzelnen Zeichen?
(Bin Autodidakt (wenn ihc mal keine Fragen stelle  hier in dieser Materie, daher fehlen mir leider viele Grundkenntnisse  )
Gruß
~Mathias
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Di 06.07.10 23:07
Array of Char, nicht PChar
wobei das intern alles nocht wesentlich verschachtelter ist. Vieles in der Compiler-Magic erlaubt abe (zumindest in aktuelleren Versionen) PChar und String analog zu nutzen. Wobei bei PChar die Referenzzählund und die Speicherverwaltung beim Nutzer liegen. Daher ist u.U. eine Konvertierung eines Strings in einen PChar mit einer Kopie von diesem verbunden und kostet daher Zeit (interner Aufruf an UniqueString).
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Für diesen Beitrag haben gedankt: trm
|
|
ASMFreak
      
Beiträge: 53
Erhaltene Danke: 9
|
Verfasst: Mi 07.07.10 11:07
Mathias,
Das liegt an der Historie und daran, warum es Strings und PChars überhaupt gibt.
Das wird nun etwas umfangreicher, aber Deinen Äußerungen entnehme ich, dass Du wirklich etwas wissen möchtest.
Es gibt zwei klassische Programmiersprachen: Pascal, aus dem Delphi entstand, und C/C++, das für die Programmierung von Windows benutzt wurde. Beide Programmiersprachen haben unterschiedliche Ansätze.
Pascal/Delphi definiert einen String als Zeichenfolge, der die Information, wie lang der String ist, VORANGESTELLT. Ein AString[0] liefert damit die gleiche Information wie Length(AString).
C/C++ defineirt als String eine Zeichenfolge, der als Terminierungszeichen der Null-Char (#0) FOLGT. Zur Ermittlung der Stringlänge muss er also vom Beginn an solange durchsucht werden, bis man auf das #0 trifft.
Falls Du an dieser Stelle fragen willst: "Warum so umständlcih?" - lass es! Das ist eine Frage der historischen Entwicklung und unterschiedlicher Vorlieben der "Erfinder" der Sprachen. Nimms einfach hin.
Ein weiterer Unterschied ist, dass Pascal/Delphi eine Stringverwaltung besitzt, C/C++ nicht! Wird in Pascal/Delphi ein String erzeugt (AString := 'Du Greenhorn  '), so reserviert die Stringverwaltung irgendwo einen Speicherbereich, in dem die Zeichenfolge "Du Greenhorn  " abgelegt wird und weist AString die Adresse dahin zu. Wird der String gelöscht (AString := ''), so wird der Speicherbereich freigegeben. Aus diesem Grund werden, falls Du einmal debuggst und Dich wunderst, im Zusammenhang mit der Verarbeitung von Strings auch so viele Prozeduren aufgerufen, die Du niemals programmiert hast. Das ist die Stringverwaltung, die immer dann ins Spiel kommt, wenn ein String verlängert, verkürzt, kopiert oder gelöscht wird. Denn bei einer Verlängerung des Strings gemäß AString := AString + ', Du!' ist es nicht damit getan, einfach die Zeichen ', Du!' an 'Du Greenhorn  ' anzuhängen: Es muss Platz für den neuen String erzeugt werden, die beiden Teilstrings aneinandergehängt und dort abgelegt werden. Der Speicherbereich des alten Strings kann dann freigeeben werden. Ach ja: AString bekommt noch die Adresse des neuen Strings.
Das alles macht Delphis Stringverwaltung.
C/C++ kennt so viel Service nicht. Ein PChar ist ein Zeiger auf eine Zeichenfolge wie oben beschrieben, und gut ist! Nach seiner Deklaration, in der lediglich Platz für den zeiger reserviert wird,passiert nichts mehr. Wer wann warum wo wieviel Speicher reserviert hat oder auch nicht, interessiert C/C++ nicht die Bohne. Alles, was die Sringverwaltung von Delphi im Hintergrund macht, musst Du jetzt selbst tun: Einen Puffer reservieren, in dem die Zeichenfolge 'Du Greenhorn  ' abgelegt werden kann - zuzüglich Platz für das obligatorische folgende #0. Füllen des Puffers mit dieser Zeichenfolge und Zuweisung der Adresse des Puffers an PChar. Beim Hinzufügen eines zweiten Strings das Ganze von vorne: Ermittlung der Gesamtlänge, Reservierung eines Speicherbereichs, die aneinandergehängten Zeichenfolgen dort hinlegen, den alten Speicherbereich freigeben und PChar über die neue Adresse informieren.
Du siehst: Zwei unterschiedliche Strategieren, die sich inzwischen zu Philosophien entwickelt haben: Der eine schwört auf Pascal-Strings mit der Bequemlichkeit, sich um nichzts sorgen zu müssen, der andere will "die Flexibilität" nicht einbüßen, die ihm das Inkrementieren von Zeigern gibt. Reine Geschmackssache!
Doch nun zurück zum eigentlichen Thema.
Nachdem Delphi sich immer mehr dahin oritentierte (orientieren musste), eigene Vorstellungen zu verlassen, da man ja Windows-APIs nutzen wollte, musste man sich wohl oder übel daran gewöhnen, mit PChars zu arbeiten, weil es das ist, was die API-Funktionen (programmiert in C/C++!) erwarten.
Also "erfand" man einen neuen Stringtyp und erklärte ihn zum neuen "String": einen Zwitter, der vorangestellt die Längeninformation hat UND von einem #0 abgeschlossen wird. Nun kann man die Bequemlichkeit Delphis beibehalten, aber auch APIs nutzen, denen man nun einen Zeiger auf den Stringanfang übergeben kann - das erforderliche #0 ist ja vorhanden.
Daher ist auch ein type cast von String in PChar so einfach: bei APchar := AString erfolgt nichts anderes, als in APchar, das ja vom Typ Pointer ot a CHARacter ist, die Adresse des ersten Zeiches in AString einzutragen: Der String fungiert dann als Puffer.
De umgekehrte Weg ist nicht so einfach: Da für die Erzeugung eines Strings Delphis Stringvewaltung eingesetzt werden muss (damit die nicht durcheinandergerät!), kann das nicht über ein einfaches type casting erfolgen: Es muss ein String erstellt werden, in den dann die Zeichen aus APChar kopiert werden. Daher gibt es Funktionen die das erledigen (StrPCopy & Co,).
Das Problem besteht auch beim Kopieren von Strings. Die Stringverwaltung von Delphi führt Buch, weiviele Kopien eines Strings im Umlauf sind: Bei AString2 := AString1 wird aus Effinzienzgründen nicht etwa ein neuer String erstellt, sondern lediglich ein Zähler erhöht. Der String wird dann erst gelöscht, wenn der Zähler wieder auf 0 ist, weil alle Kopien in der Form AString1 := ''; AString2 := ''; gelöscht wurden.
Nicht so bei PChasrs. Bei APChar2 := APChar1, werden di ADRESSEN auf den Puffer kopiert, nicht etwa der Puffer selbst. Dabei wird aber im Unterchied zu Delphis Strings kein Zähler verwaltet. Und keiner merkt, wenn nach Löschen des Puffers PChar1 und PChar2 ins Nirwana zeigen! Auch hier bist Du wieder gefragt! Du must Buch führen, wer eine Kopie des Zeigers auf den Puffer haben könnte, den Du gerade gelöscht hast, und dort die Adressen auf "nil" setzen.
Ich hoffe, dass das klar geworden ist.
Nun noch ein paar Tips: Wann immer PChars im Spiel sind, sollten bei Dir im Hirn folgende Alarmglocken schrillen: Wer hat den Puffer erzuegt und wo? (Das ist wichtig, wenn man mit APIs arbeitet: Es gibt Funktionen, die PChars samt Puffer erzeugen, es aber dem Rufer überlassen, die wieder freizugeben!) Zeigt die PChar tatsächlich auf einen Puffer, oder enthält sie "nil"? (Das ist wichtig, weil APChar = nil nicht das Gleiche ist wie AString = ''! AString = '' existiert, der Puffer von APChar = nil offenbar nicht, zumindest wurde er, sollte er existieren, APchar nicht zugewiesen! Das korrekte Pendant zu AString = nil ist ein PChar mit einem Zieger auf einen Puffer, der mindestens ein führendes #0 hat!). Wird/wurde PChar über Änderungen am Puffer informiert oder zeigt es ggf. ins Nirwana, weil der Puffer längst freigegeben wurde? (Ein gerne gemachter Fehler ist, einen Puffer in einer Prozedur lokal zu erzeugen, einer globalen PChar dessen Adresse zuzuweisen, in der Prozedur den Puffer zu füllen und dann die Prozedur wieder zu verlassen. Und sich dann wundern, wenn nicht das herauskommt, was man denkt. Denn bei der Reservierung von Speicher innerhalb einer Routine wird der auf dem Stack angelegt. Und der wird nach Verlassen der Routine von der nächsten Routine überschrieben. Folge: die globale PChar zeigt auf eine Adresse, in der alles mögliche drin steht - nur nicht der Inhalt des zuvor gefüllten Puffers.)
Du siehst, mit PChars zu arbeiten erfordert ein bisschen mehr als vielleicht zunächst vermutet.
Aber Du siehst hoffentlich auch, dass es nur einen wesentlichen Unterschied zwischen Strings und PChasr gibt: Die Stringverwaltung von Delphi im Hintergrund. Denn auch eine Variable vom Typ String enthält schließlich - wie PChar - nur die Adresse eines Speicherbereiches, in dem die Zeichenfolge steht ...
Nun noch zu Deinen Bemerkungen:
Nicht Delphi geht den Weg über PChars, sondern der Programmierer, der PChars nutzt. Es gibt keinen Grund, PChars einzusetzen, alles ist mit Strings möglich - selbst der Aufruf von API-Funktionen, denen man die Adresse des Strings als PChar übergibt.
Es wird nichts in ein PChar "reingepackt". In ein PChar wird lediglich eine Adresse eines Puffers eingetragen, der eine Zeichenfolge enthält. Wenn das "zufälligerweise" die Adresse eines Strings ist, hat man ein type cast durchgeführt.
Und String[x] ist das gleiche wie PChar(@String + x)^; nur dass der Compiler dabei nicht mitspielt, weil der zwar Pointer inkrementieren kann, sich aber aus Gründen, die der Philosophie von Delphi folgen, bei typisierten Zeigern standhaft weigert.
Also als Tip: Solange es nicht absolut notwendig ist, würde ich mir die Bequemlichkeit der Delphi-Stringverwaltung nicht entgehen lassen. Sie ist eine der Dinge, in denen sich Delphi wohltuend von C/C++ unterscheidet. Bevor Du ernsthaft mit PChars arbeitest, musst Du ein "Gespür" für sie entwickeln und Dir bewusst sein, dass es nicht damit getan ist, ein paar Adressen zuzuordnen.
Abschließend: Du must Dich nicht rechtfertigen oder entschuldigen! Wer sich über Deine Fragen aufregt, hat vergessen, dass er selbst einmal in dieser Situation war. Das zeugt von Arroganz. Denn es ist wahr: Es gibt keine dummen Fragen, es gibt nur dumme Antworten und Kommentare!
Ich hoffe, das hat geholfen.
Gruß Trutz
Für diesen Beitrag haben gedankt: BenBE, jaenicke, trm
|
|
trm 
      
Beiträge: 491
Erhaltene Danke: 19
Windows 7x64
Delphi 7
|
Verfasst: Mi 07.07.10 11:48
Huhu.
Danke für Deine Erklärung. Mir brummt jetzt der Kopf
Lustige Anekdote:
Bisher gab es bei mir immer eine Exception, wenn ich einzelne Zeichen mitels String[x] ermittelt habe und x=0 war. jetzt weiss ich wenigstens, dass da doch was ist
Wenn ich könnte, würde ich hier bei Deiner Ausführung 2xDanke geben
Noch eine Frage habe ich aber:
Wenn ich nun einem String folgenden Wert zuweise:
Delphi-Quelltext 1: 2: 3: 4: 5:
| var dummy_string:string; begin dummy_string:='test'+#0; [..] |
Ist dann dummy_string automatisch ein PChar oder ist das eine Typecast-Variante oder ist das immer noch ein Delphi-String?
Danke für Dein Verständnis
Gruß
~Mathias
Moderiert von Narses: Delphi-Tags hinzugefügt
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 07.07.10 15:15
Das ist immer noch ein String. Allerdings würdest Du beim Betrachten des Speicherbereiches am Ende 2x #0 sehen, da das Abschließende #0 von der Compiler-Magic von Delphi automatisch angehängt werden wird.
P.S.: Verwende bitte Delphi-Tags für Quelltexte; liest sich besser.
@ASMFreak: Nur für ShortStrings ist s[0] wirklich definiert; für AnsiStrings und WideString ist das mehr oder weniger das MSB der Längenangabe (also in der Regel #0  ).
Die Längenangabe ist ein DWORD, davor steht der Referenzzähler. Ist dieser (signed) betrachtet negativ, handelt es sich um einen String-Literal.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
ASMFreak
      
Beiträge: 53
Erhaltene Danke: 9
|
Verfasst: Mi 07.07.10 16:13
Mathias,
nochmal ganz langsam:
Stell Dir vor, irgendwo im Speicher steht folgende Folge an Bytes:
$04, $74 $65 $73 $74 $00.
Jetzt hast Du eine Variable dummy_string: string. Diese Variable zeigt, das hat die Stringverwaltung so gemacht, auf das Byte $74. Das interpretiert Delphi als: Da gibt es einen Delphi-String mit den Zeichen 't', 'e', 's', 't'. Für Delphi ist damit Ende der Fahnenstange erreicht, weil vor dem ersten Buchstaben der Wert "4" steht, der bedeutet: Dieser String hat fünf Buchstaben. Str "ist" (besser: zeigt auf) 'test'.
Nun gibt es ein PChar, das zeigt auch auf das Byte $74. Um nun zu eruieren, was das PChar codiert, muss man solange vorangehen, bis man einen Wert #0 (= $00) findet. Also bis hinter das Byte $74. Das sind auch vier Zeichen. PChar zeigt also auf den String 'test'. Und warum ist das so? Weil der Typ PChar dem Compiler sagt: Da gibt es eine Position im Speicher, da stehen Characters. Wieviele das sind, musst du zählen. Zähle solange, bis du #0 findest."
Bis hierher ist, außer das bei Delphi ein Str[0] gleich die Länge angibt, wahrend man bei PChar erst zählen muss, alles gleich.
Nehmen wir nun an, dummy_str hätte zunächste den Wert 'test':
dummy_string := 'test'
dann wäre Dein Aufruf abzuändern in
dummy_string := dummy_string + #0.
Die Stringverwaltung mach daraus an einer anderen Speicherstelle
$04, $74 $65 $73 $74 $00 und fügt ein #0 an:
$04, $74 $65 $73 $74 $00 $00
(Das ist das Gleiche, als hätte man gleich gesagt: dummy_string := 'test' + #0
Was sagt Delphi nun hinsichtlich des Strings? Vier Zeichen (weil #0 für Delphi kein Zeichen ist. Anders sähe es aus, würdest Du '!' anfügen)! Also 't', 'e', 's', 't' = 'test'.
Und PChar? Das gesuchte Null-Byte steht vier Zeichen hinter $74, also 't', 'e', 's', 't' = 'test'. Was danach kommt ist vollkommen egal.
Ds bedeutet, dass das Hinzufügen von #0 nichts, aber auch gar nichts bewirkt.
Eine Anweisung dummy_string := 'test' heißt eigentlich nur: "Lieber Compiler, nimm die Bytes $74 $65 $73 $74, interpretiere sie als "String" (weil dummy_string vom Typ "String" ist) und schreibe sie in den Speicher". Die Speicherverwaltung macht den Rest (einschließlich des Längenbytes und der abschließenden #0).
Und was passiert, wenn man dem Compiler die Anweisung gibt: dummy_pchar := 'test'? Vermutlich Schreckliches, denn höchstwahrscheinlich steht in PChar der Wert nil. Der Compiler versucht dann, an die Adresse nil die Zeichenfolge 'test' mit abschließenden #0 zu schreiben, und das geht schief. Daher muss, bevor man diese Anweisung gibt, dafür gesorgt werden, dass der Compiler ein Ziel findet: PChar := @Buffer, wobei Buffer z.B. ein statisches Array[0..255] of Char ist. Dann funktionert PChar := 'test'. (Wobei es, nimmt man es genau, PChar^ := 'test' heißen muss. Aber das sieht der Compiler nciht so eng!). Grund: Strings haben die Speicherverwaltung, PChars nicht!
Löse Dich von der Vorstellung, dass Strings und PChars etwas unterschiedliches sind! Ist einmal der Speicherort für die Zeichenfolge geschaffen und stehen die gewünschten Bytes da drin, sind PChars und Strings sysnonym zu verwenden und beliebig austauschbar.
Der Unterschied zwischen beiden ist lediglich,
- wie die Läänge der Ziechenfolge bestimmt wird (String: Längenbyte vor der Zeichenfolge, PChar: #0 hinter der Zeichenfolge und
- wie kommt die Zeichnfoflge wohin (String: automatisch durch die Speicherverwaltung, PChar manuell durch Dich!)
Es gibt also keine "type cast variante" oder was auch immer! Es gibt immer nur die Zeichenfolge im Speicher, und wie sie interpretiert werden muss! Fehlt die Längenangeabe VOR der Zeichenfolge, MUSS es ein PChar sein, da Strings diese Angabe zwingend erwarten. Ist diese Angabe vorhanden und fehlt das folgende #0, MUSS es ein String (vom alten Pascal-Typem) sein, da PChars (und die neuen Strings) diese #0 zwingend verlangen (und im Zweifel solange weitersuchen, bis irgendwann einmal eine vorgefunden wird. Und das passiert wirklich!). Ist beides vorhanden, kannst Du den Würfel werfen, was Du nutzt: String oder PChar oder beides. Fehlt beides, hast Du die A-Karte, weil dann nicht eruiert werden kann, welche Zeichen zum "String" gehören und welche nicht.
Und nochmals, was das type casting betrifft: Ein type casting ist nichts anderes als eine Anweisung an den Compiler, wie der Daten zu interpretieren hat. Wenn Du im obigen Fall dem Compiler sagst:
I (vom Typ Integer) := PInteger(@Zeichenfolge)^,
sagst Du ihm: Nimm die Adresse von Zeichenfolge und stell Dir vor, da steht eine Integer (mit vier Bytes Größe, weil Integers vier Byte groß sind). Diesen Wert weise dann I zu. Dann interpretiert der die Zeichenfolge als $74657374 und weist daher der Variblen I den Wert 1952805748 zu.
Sagst Du ihm: an der Adresse von Zeichenfolge steht ein String, so sucht er nach dem Längenbyte davor und interpretiert die Zeichenfolge als 'test'.
Sagst Du ihm: an der Adresse von Zeichnfolge steht ein PChar, so sucht er das abschließende #0 und interprtiert das Ganze ebenfalls als 'test'.
Das heißt: Ein type casting "macht" oder "verändert" nichts! Ein type casting dient lediglich der "Uminterpretierung" von Daten.
Gruß Trutz
Für diesen Beitrag haben gedankt: trm
|
|
ASMFreak
      
Beiträge: 53
Erhaltene Danke: 9
|
Verfasst: Mi 07.07.10 16:17
BenBE,
Stimmt schon! Aber hätte ich das auch noch erklären sollen? Ich glaube, dann wäre Mathias noch verwirrter. Es kann ja schon allein aus dem Grunde kein LängenBYTE sein, da Stings ja etwas größer als 256 Zeichen werden können
Gruß Trutz
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 07.07.10 16:25
'test'#0 ergibt im Speicher $05 $00 $00 $00 >$74 $65 $73 $74 $00 $00
Das mit dem 4 als Länge ist ein Fehler mancher Funktionen der Compiler-Magic  Sprich: Mit Nullbytes macht man sich in Strings keine Freunde; ABER es geht.
Ja, Aber man sollte es schon richtig machen. Wenn man Strings schreibt, sollte man wenigstens erwähnen, welchen der inzwischen 4 Typen man meint 
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
|