'format'-Funktion als Fehlerquelle

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

Hallo Ihr Lieben,
ich hoffe Ihr seid alle gut ins neue Jahr gestartet!

Obwohl ich ja schon sehr lange in Pascal, Delphi und nun Lazarus programmiere habe ich doch eine ziemlich grundsätzliche Frage, die mich in einem aktuellen Projekt ziemlich stört und vielleicht gibt es dafür eine schöne Lösung, auf die ich bisher nicht gestoßen bin.

Ich verwende die Format-Funktion sehr intensiv für das Logging einer Konsolen-Anwendung die im Hintergrund regelmäßig aufgerufen wird um ihren Dienst zu tun. Da niemand zuschaut was das Programm macht ist es sehr wichtig, dass die Log-Datei alles Relevante enthält um Probleme nachzuvollziehen und sie zu korrigieren.
Insgesamt ist Free-Pascal ja sehr schön darin viele Fehler in der Compilerzeit abzufangen. So sorge ich dafür, dass es wirklich zu keinen Meldungen oder Warnings kommt.
Aber leider macht mir die Format-Funktion da einen gewaltigen Strich durch die Rechnung.

Beispiel

Code: Alles auswählen


function EineFunktion(out  a : Integer; out b : String) : Boolean;
begin
  a := Random(5000);
  b := 'Ein String';
  Result := (a<>123);
end;

procedure MachEinenFehler;
var
  a : Integer = 0;
  b : String = '';
begin
  if not EineFunktion(a,b) then
    LogThis(Format('Es ist ein Fehler (%d) bei der Bearbeitung von "%s" aufgetreten!',[a,b]));
end;
In diesem Fall passen die Parameter im Format String zu den übergebenen Variablen.
Die Funktion liefert nur sehr selten False zurück, so gelangt die Meldung kaum ins Log und debuggen kan man das auch nicht wirklich. (Ja man könnte manuell die Bedingung if not EineFunktion(a,b) then in if TRUE or not EineFunktion(a,b) then ändern, aber das ist in einem Programm mit sehr vielen Verzweigungen und den daraus resultierenden "Vielleichts" recht komplex.
Aber das Obige Programm würde ja funktioneren und tun was es soll.

Jetzt kommt aber ein unbedarfter Programmierer (ich!) auf die Idee die EineFunktion wie folgt zu ändern

Code: Alles auswählen

{Der Typ des Parameters a wurde von Integer in String geändert!}
function EineFunktion(out  a : String; out b : String) : Boolean;
var
  r : Integer;
begin
  r := Random(5000);
  a := IntToStr(r);
  b := 'Ein String';
  Result := (r<>123);
end;
beim Compilieren schlägt der Compiler Alarm.
eintest.pas(50,24) Error: Call by var for arg no. 1 has to match exactly: Got "LongInt" expected "AnsiString"
Er meint zu Recht, dass in der Zeile
if not EineFunktion(a,b) then
der Parameter a nicht vom Typ String sei.
Also ändert man schnell

Code: Alles auswählen

procedure MachEinenFehler;
var
  a : String = ''; // War Integer
  b : String = '';
begin
  if not EineFunktion(a,b) then
    LogThis(Format('Es ist ein Fehler (%d) bei der Bearbeitung von "%s" aufgetreten!',[a,b]));
end;
und der Compiler ist glücklich.
Das Programm rennt auch schön, aber in einem unbeaufsichtigten Moment kommt es zu einer Exception bei der Ausführung von Format.
"Invalid argument index in format "'Es ist ein Fehler (%d) bei der Bearbeitung von "%s" aufgetreten!".
Das Programm findet zu Recht, dass das "%d" nicht zum ersten Parameter passen will.
Und das ist aus mehreren Gründen sehr böse:
1.) Geht die Information verloren, die für den Fehler gesorgt und ursächlich für den Aufruf war.
2.) Schlägt die Exception bis irgendwohin durch, wo man tatsächlich nicht damit gerechnet hat.

Also was kann man da tun?
Gibt es irgendeine schlaue Methode diese Format-Strings wenigstens zur Laufzeit durchzutesten um so beim Starten (oder wenigstens in einer Debug-Funktion) diese Fehler zu finden?
Wie macht ihr das?

Liebe Grüße aus Hildesheim
Ekkehard

alfware17
Beiträge: 249
Registriert: Di 14. Dez 2010, 23:27

Re: 'format'-Funktion als Fehlerquelle

Beitrag von alfware17 »

Hm, ich habe nie mit Pascal produktiv programmiert und kenne dieses format() auch erst seit kurzem und frage mich in deinem Problem, warum ihr das so macht. Weil das ganze Ergebnis ein String sein soll und einzelne Elemente in einem Writeln() nichts bringen. Ich glaube das format() gibts auch in Python und C und anderswo - ich mochte es nie aber okay es ist noch sauberer als alles mit .tostring() und dann mit + zu verketten,

Aber, ich sehe das Problem durchaus in der Änderung in der MachEinenFehler. Die Typen von a und b sind doch bekannt und dann muß man schon auch die format() Anweisung in den % zumindestens prüfen und anpassen oder nicht?

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2874
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: 'format'-Funktion als Fehlerquelle

Beitrag von m.fuchs »

Ekkehard hat geschrieben: Fr 2. Jan 2026, 18:42 Also was kann man da tun?
Gibt es irgendeine schlaue Methode diese Format-Strings wenigstens zur Laufzeit durchzutesten um so beim Starten (oder wenigstens in einer Debug-Funktion) diese Fehler zu finden?
Wie macht ihr das?
Würde ich lösen, indem ich mir eine (bzw. mehrere) sichere Funktionen bauen würde, die das ganze typsicher machen:

Code: Alles auswählen

program SafeFormatTest;
{$MODE ObjFpc}
{$H+}

uses
  Classes, SysUtils;

function FormatError(ErrorNumber: Integer; TaskName: String): String;
begin
  Result := Format('Es ist ein Fehler (%d) bei der Bearbeitung von "%s" aufgetreten!', [ErrorNumber, TaskName]);
end;


procedure LogThis(Logline: String);
begin
  WriteLn(Logline);
end;

var
  a: Integer = 23;
  b: String = 'First Task';
  c: String = '42';
  d: String = 'Second Task';

begin
   LogThis(FormatError(a, b)); // <= geht
   LogThis(FormatError(c, d)); // <= Compiler Error
end.
Für die Funktion FormatError schreibt man dann noch Unittests, damit auch da drin nichts schiefgeht.
0118999881999119725-3

Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

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

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Mathias »

alfware17 hat geschrieben: Fr 2. Jan 2026, 21:02 Hm, ich habe nie mit Pascal produktiv programmiert und kenne dieses format() auch erst seit kurzem und frage mich in deinem Problem, warum ihr das so macht. Weil das ganze Ergebnis ein String sein soll und einzelne Elemente in einem Writeln() nichts bringen. Ich glaube das format() gibts auch in Python und C und anderswo - ich mochte es nie aber okay es ist noch sauberer als alles mit .tostring() und dann mit + zu verketten,

Aber, ich sehe das Problem durchaus in der Änderung in der MachEinenFehler. Die Typen von a und b sind doch bekannt und dann muß man schon auch die format() Anweisung in den % zumindestens prüfen und anpassen oder nicht?
Ja, in C, gibts diese Funktion auch, nur heist die dort sprintf., Und die Formatierung dort ist sehr mächtig.
Das gute daran, man kann sie sehr gut auch in Pascal anbinden, da sie praktisch auf jeder Plattform zu Verfügung steht.
Für die reine Textausgabe nimmt man printf, welche die gleichen Formatierungs Optionen hat.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7073
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: 'format'-Funktion als Fehlerquelle

Beitrag von af0815 »

Format verschiebt einmal die Auswertung in die Laufzeit. Damit kann ein Fehler auch nur zur Laufzeit ausgewertet werden. Format ist einfach die Antwort von Pascal auf die fprintf von C. Mit denselben Problemen, deshalb habe ich das meistens mit Pascal selbst Type sicher gebaut. Die Typen Sicherheit von Pascal war mir halt wichtiger als der vermeintliche Komfort.

Jeder halt so wie er es für sich sinnvoll hält.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

alfware17 hat geschrieben: Fr 2. Jan 2026, 21:02 Hm, ich habe nie mit Pascal produktiv programmiert und kenne dieses format() auch erst seit kurzem und frage mich in deinem Problem, warum ihr das so macht. Weil das ganze Ergebnis ein String sein soll und einzelne Elemente in einem Writeln() nichts bringen. Ich glaube das format() gibts auch in Python und C und anderswo - ich mochte es nie aber okay es ist noch sauberer als alles mit .tostring() und dann mit + zu verketten,

Aber, ich sehe das Problem durchaus in der Änderung in der MachEinenFehler. Die Typen von a und b sind doch bekannt und dann muß man schon auch die format() Anweisung in den % zumindestens prüfen und anpassen oder nicht?
Die Funktion Format entspricht der sprintf in C und die Problematik taucht entsprechend dort auch auf.
Natürlich könnte man mittels manueller Umwandlung und der vorgeschlagenen Verbindung auch einen entsprechenden String basteln. Allerdings hat man damit einen wesentlichen Vorteil zerstört, nämlich den das Format der Ausgabe von dem der Daten zu trennen.
Man stelle sich dazu nur eine mehrsprachige Anwendung vor, die könnte dann nämlich die Format-Strings aus einer Datei lesen und dann zur Laufzeit verwenden. Eine derartige Dynamik könnte man sonst nur sehr komplex mittels endlosen case-Vereilern oder Call-Backs erreichen.
Und daran wird dann auch deutlich, warum die Überprüfung hinsichtlich der bekannten Typen von a und b fehlschlägt.

Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

m.fuchs hat geschrieben: Sa 3. Jan 2026, 00:02 Würde ich lösen, indem ich mir eine (bzw. mehrere) sichere Funktionen bauen würde, die das ganze typsicher machen:
Für die Funktion FormatError schreibt man dann noch Unittests, damit auch da drin nichts schiefgeht.
Das ist mir deutlich zu unflexibel, es läuft dann darauf hinaus mehrere solcher Funktionen zu bauen, für beliebige Kombinationen aus Zahlen und Strings. Ist zwar auf jeden Fall sicherer, aber nicht gut handhabbar.
Aber immerhin eine Lösung. :D

Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

Vielen Dank für Eure Beiträge und Gedanken!
Vorhin hatte ich die Idee, ob es nicht möglich sein könnte ein Plugin für die Lazarus-IDE zu basteln, welches nach dem Kompilieren durch den Pascal-Code läuft und alle Stellen in denen die Formatfunktion mit einem konstanten Formatstring aufgerufen wird, gegen die übergebenen Parameter zu prüfen und entsprechende Warnings zu werfen. Dann würde man jedenfalls den Fall meines Beispiels eingefangen bekommen.

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 657
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon, Laz 4.5 Fpc 3.2.3 und allerlei mit FpcUpDeLuxe
Kontaktdaten:

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Niesi »

Ekkehard hat geschrieben: Sa 3. Jan 2026, 12:43 Vielen Dank für Eure Beiträge und Gedanken!
Vorhin hatte ich die Idee, ob es nicht möglich sein könnte ein Plugin für die Lazarus-IDE zu basteln, welches nach dem Kompilieren durch den Pascal-Code läuft und alle Stellen in denen die Formatfunktion mit einem konstanten Formatstring aufgerufen wird, gegen die übergebenen Parameter zu prüfen und entsprechende Warnings zu werfen. Dann würde man jedenfalls den Fall meines Beispiels eingefangen bekommen.
Ist der Aufwand das wirklich wert?

Mir geht es da wie Andreas:
af0815 hat geschrieben: Sa 3. Jan 2026, 10:30 Format verschiebt einmal die Auswertung in die Laufzeit. Damit kann ein Fehler auch nur zur Laufzeit ausgewertet werden. Format ist einfach die Antwort von Pascal auf die fprintf von C. Mit denselben Problemen, deshalb habe ich das meistens mit Pascal selbst Type sicher gebaut. Die Typen Sicherheit von Pascal war mir halt wichtiger als der vermeintliche Komfort.

Jeder halt so wie er es für sich sinnvoll hält.
Die Funktion 'format' habe ich vor längerer Zeit ausprobiert - es war mir einfach zu aufwändig. Nicht nur, dass da Fehler zur Laufzeit möglich sind, da jedesmal die Formatierung einzugeben ist einfach nervig. Ich muss viele Zahlenwerte mit Beschreibung sauber lesbar ausgeben, aber das ist mit einem selbst gestaltetem Float2Str, Int2Str und dem entsprechendem Zeichensatz sehr viel einfacher, übersichtlicher und sicherer.

Frei nach Niklaus Wirth: Programmieren muss einfach sein und Spaß machen ... :!:
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

Niesi hat geschrieben: Sa 3. Jan 2026, 13:05
Ist der Aufwand das wirklich wert?

Die Funktion 'format' habe ich vor längerer Zeit ausprobiert - es war mir einfach zu aufwändig. Nicht nur, dass da Fehler zur Laufzeit möglich sind, da jedesmal die Formatierung einzugeben ist einfach nervig. Ich muss viele Zahlenwerte mit Beschreibung sauber lesbar ausgeben, aber das ist mit einem selbst gestaltetem Float2Str, Int2Str und dem entsprechendem Zeichensatz sehr viel einfacher, übersichtlicher und sicherer.
Wie schon weiter oben geschrieben, der Ansatz mit manuellen Umsetzungen scheitert etwa bei mehrsprachigen Anwendungen, da ist der Einsatz von Format wesentlich effizienter und auch pflegeleichter, weil man konkret an der Stelle wo der Fehler gemeldet wird auch alles zusammen hat.
Aber ja, natürlich die Problematik dabei hatte ich selber angesprochen.

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 657
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon, Laz 4.5 Fpc 3.2.3 und allerlei mit FpcUpDeLuxe
Kontaktdaten:

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Niesi »

Ekkehard hat geschrieben: Sa 3. Jan 2026, 13:18
Niesi hat geschrieben: Sa 3. Jan 2026, 13:05
Ist der Aufwand das wirklich wert?

Die Funktion 'format' habe ich vor längerer Zeit ausprobiert - es war mir einfach zu aufwändig. Nicht nur, dass da Fehler zur Laufzeit möglich sind, da jedesmal die Formatierung einzugeben ist einfach nervig. Ich muss viele Zahlenwerte mit Beschreibung sauber lesbar ausgeben, aber das ist mit einem selbst gestaltetem Float2Str, Int2Str und dem entsprechendem Zeichensatz sehr viel einfacher, übersichtlicher und sicherer.
Wie schon weiter oben geschrieben, der Ansatz mit manuellen Umsetzungen scheitert etwa bei mehrsprachigen Anwendungen, da ist der Einsatz von Format wesentlich effizienter und auch pflegeleichter, weil man konkret an der Stelle wo der Fehler gemeldet wird auch alles zusammen hat.
Aber ja, natürlich die Problematik dabei hatte ich selber angesprochen.

Was hast Du mit "mehrspachigen Anwendungen" gemeint? Versteh ich jetzt nicht - meine Anwendung von damals ist in Deutsch, Englisch und Chinesisch ...
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

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

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Mathias »

Als die schnellste und einfachste Pascal Lösung ist, das alt bewährte WriteStr. Oder man nimmt val und str.
Schlussendlich landen alle RTL-Funktionen, wie format, inttostr, etc bei str. Nur habe die alle sehr viel overhead, weil sie alle möglichen Sprachen berücksichtigen, ZB. das Komma anstelle des Punktes für Dezimaltrenner.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Ekkehard
Beiträge: 96
Registriert: So 12. Feb 2023, 12:42
OS, Lazarus, FPC: Windows Lazarus 4.0, FPC 3.2.2
CPU-Target: 64-Bit
Wohnort: Hildesheim

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Ekkehard »

Niesi hat geschrieben: Sa 3. Jan 2026, 13:39
Ekkehard hat geschrieben: Sa 3. Jan 2026, 13:18
Niesi hat geschrieben: Sa 3. Jan 2026, 13:05
Ist der Aufwand das wirklich wert?

Die Funktion 'format' habe ich vor längerer Zeit ausprobiert - es war mir einfach zu aufwändig. Nicht nur, dass da Fehler zur Laufzeit möglich sind, da jedesmal die Formatierung einzugeben ist einfach nervig. Ich muss viele Zahlenwerte mit Beschreibung sauber lesbar ausgeben, aber das ist mit einem selbst gestaltetem Float2Str, Int2Str und dem entsprechendem Zeichensatz sehr viel einfacher, übersichtlicher und sicherer.
Wie schon weiter oben geschrieben, der Ansatz mit manuellen Umsetzungen scheitert etwa bei mehrsprachigen Anwendungen, da ist der Einsatz von Format wesentlich effizienter und auch pflegeleichter, weil man konkret an der Stelle wo der Fehler gemeldet wird auch alles zusammen hat.
Aber ja, natürlich die Problematik dabei hatte ich selber angesprochen.

Was hast Du mit "mehrspachigen Anwendungen" gemeint? Versteh ich jetzt nicht - meine Anwendung von damals ist in Deutsch, Englisch und Chinesisch ...

Beispiel

Code: Alles auswählen

var
  ErrorFormatStrings : TStringList; // Wird bei der Auswahl der Sprache mit Format Strings befüllt
// Beispiel für den ersten eingefügten Formatstring:
// Deutsch: 'Datei "%s" kann nicht geöffnet werden, Fehler "%s" in Schritt %d'
// Englisch: 'File "%s" could not be opened, error  "%s" at step %d'
const
  FileOpenErrorFmtNdx = 0;
In der Funktion dann

Code: Alles auswählen

try
  sl.LoadFromFile(fn);
...
except
  on E : Exception do
    AddLog(Format(ErrorFormatStrings[FileOpenErrorFmtNdx],[fn, E.Message, 5]));
end;
Wenn man das elegant anders lösen will, wird es schnell sehr kompliziert oder extrem unflexibel, weil man nur fixierte und recht kryptisch zu lesende Fehlermeldungen ausgeben kann.

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

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Warf »

Format ist ein ziemlich tiefes "rabbit hole", die Funktion ist alt und damit sehr weit verbreitet aber eigentlich von grundauf falsch designed. Für die Format Funktion liegen alle informationen zum verifizieren zur compilezeit vor (zumindest sollte es so sein, wenn man sie richtig benutzt) aber die Überprüfung findet zur runtime statt.

Deshalb versuchen Programmiersprachen diesen Fehler seit Jahrzehnten zu fixen. In Python z.b. gibt es mittlerweile sog. F-Strings:

Code: Alles auswählen

s=f"Hello {Name}"
# statt vorher
S="Hello {0}". format(Name)
Das sieht man so ähnlich in sehr vielen sprachen z.b. auch Typescript.
Rust was ja ein sehr starkes Typsystem hat hat einfach sie Typen bei format weggelassen und lost es über generics

Code: Alles auswählen

format("Hello {}", Name);
Wobei man alles übergeben kann solang es eine to_string Funktion für diesen Typen gibt.
Selbst C++, obwohl format strings ein Kernstück von C sind hatte bis 2020 kein Format wegen genau dieser Problematik mit C++20 haben sie dann das system wie Rust eingebaut mit compilezime checking:

Code: Alles auswählen

std::format("Hello {}", Name);
// das wirft Fehler:
std::format("Hello {}"); // Fehler weil mehr format Platzhalter als parameter
Was aber nur geht wegen C++ unglaublich mächtiger compilezeit Auswertung von templates.

Pascal hat das Problem das Format aus einer Zeit kommt bevor man sich darüber Gedanken gemacht hat und quasi das selbe format (ein bisschen wenige schlimm wegen open arrays) wie C hat. Wenn man nicht zu tippfaul ist ists daher vermutlich besser stattdessen einfach Write zu nehmen:

Code: Alles auswählen

WriteStr(s, 'Hello ', Name);
Das ist natürlich für Lokalisierungsstrings doof

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

Re: 'format'-Funktion als Fehlerquelle

Beitrag von Mathias »

Ich nehme gerne WriteStr, das es ehr einfach in der Handhabung ist.

Code: Alles auswählen

const
  f: single = 12.34;
var
  s: string;
begin
  WriteStr(s, 'Float: ', f: 4: 2);
  WriteLn(s);
end.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten