Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   System.Length: Warum Integer und nicht Cardinal ? (https://www.delphipraxis.net/208003-system-length-warum-integer-und-nicht-cardinal.html)

Rollo62 26. Mai 2021 13:21

Delphi-Version: 10.4 Sydney

System.Length: Warum Integer und nicht Cardinal ?
 
Hallo zusammen,

ich hätte da mal die oben genannte, blöde Frage an die Sprachexperten.
Macht es aus irgendeinem Grund Sinn das System.Length ein Integer-Resultat zurückgibt für Arrays/Strings,
obwohl es dies logischerweise nie < 0 erreichen kann ?

sakura 26. Mai 2021 13:34

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Das dürfte historisch bedingt sein. Bis Delphi 4 gab es gar keine richtigen Unsigned Integer mit 32 Bit. Aber auch schon in 16 Bit Pascal waren es Signed Integers. Somit konnten Funktionen wie Pos/IndexOf usw. -1 als Ergebnis zurück liefern, wenn ein Element/Zeichen nicht in einem Array/String vorhanden war. Auch kann man so leichter Fehler in der Index-Berechnung erkennen.

Sicherlich liegt es aber auch daran, dass das das native Verhalten der x86-Prozessoren ist.

...:cat:...

Der schöne Günther 26. Mai 2021 13:41

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Wenn du einen Index für eine For-schleife nimmst dann nimmst du hoffentlich logischerweise auch immer einen vorzeichenbehafteten Typ (wie Integer).

Beispiel:
Delphi-Quellcode:
for var i: Cardinal := Low(items) to High(items)
verursacht ein Out Of Range wenn items leer ist. Warum? Weil
Delphi-Quellcode:
High(..)
-1 zurückgibt.

Rollo62 26. Mai 2021 13:48

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Dankesehr für die Antworten.

Zitat:

Zitat von sakura (Beitrag 1490176)
Das dürfte historisch bedingt sein.

Das war auch meine Vermutung.

Zitat:

Zitat von sakura (Beitrag 1490176)
Somit konnten Funktionen wie Pos/IndexOf usw. -1 als Ergebnis zurück liefern, wenn ein Element/Zeichen nicht in einem Array/String vorhanden war. Auch kann man so leichter Fehler in der Index-Berechnung erkennen.

Für alle Pos/Index ist das richtig, aber Length ?
Ich vermute Du meinst Pos/Length, oder wo hilft das bei Length Fehler zu Erkennen ?

Was daran blöd ist:
Wenn ich bewusst Cardinal benutze, um Fehleingaben (< 0) im Keim zu Ersticken,
und dann Length() mit Cardinal vergleiche:
Delphi-Quellcode:
var
    LMaxLen : Cardinal;

...

    if Length( LArray) <= LMaxLen then
    begin
    ....
dann bekomme ich immer ein Warning, das ich wegcasten muss obwohl es unnötig wäre.

Rollo62 26. Mai 2021 13:50

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1490177)
Wenn du einen Index für eine For-schleife nimmst ...

Dankesehr, mir geht es um den Fall unten beschrieben, mit der blöden Meldung.

himitsu 26. Mai 2021 13:55

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Arrays (Strings sind auch Arrays) können eh maximal 2 GB-1 groß werden, auch in 64 Bit-Compilern,
somit ist es vollkommen egal ob Integer oder Cardinal.

Integer ist es, weil es schon immer so war.
Und ja, bei High ist das Integer sogar notwendig, für Arrays die bei 0 beginnen, es -1 sein kann. (gut, man könnte die -1 auch nach Cardinal casten, wie z.B. beim INVALID_HANDLE_VALUE)
Bis vor Kurzem hatten in iOS und Android sogar die Strings mit 0 begonnen. (mitten in 10.4 wurde dieses Verhalten endlich für alle Platformen gleichgezogen, aber die Methoden des StringHelper sind immernoch 0)

freimatz 26. Mai 2021 13:55

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Rollo62 (Beitrag 1490178)
Dankesehr für die Antworten.

Zitat:

Zitat von sakura (Beitrag 1490176)
Das dürfte historisch bedingt sein.

Das war auch meine Vermutung.
...

Das halte ich für falsch.
Wie Günther schon erwähnte gibt es immer wieder Operationen bei denen negative Werte herauskommen.
Eben das High()-1. Oder wenn man Length(A)-Length(B) macht kann es negative Werte geben. Ebenso bei "if Length(A)<Length(B) then ...". Und nicht zuletzt Dein "if Length( LArray) <= LMaxLen then". Ein Vergleich impliziert eine Subtraktion.
Dann muss Delphi den Typ implizit erweitern auf den nächstgrösseren Typ z.B. auf Int64 und das ist unschön. Und deswegen die Warnung.

Der schöne Günther 26. Mai 2021 14:01

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Ganz genau das wollte ich grade auch schreiben.

Jetzt bleibt mir nur noch der Verweis auf andere Sprachen zum Vergleich (size_t in C++, oder dass size() bzw Length in Java oder C# auch mit Vorzeichen sind).

Rollo62 26. Mai 2021 14:48

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Ich verstehe Euch ja, und habe auch Integer an 1000 Stellen dafür genutzt.
Aber insbesondere bei Funktionsaufrufen würde ich mir gerne einen "Guard" ersparen,
deshalb der Cardinal

Delphi-Quellcode:
procedure CopyDing( const AArray : TBytes; const ALen : Integer )
begin
    if ALen < 0 then
        Exit; //<== DAS WÜRDE ICH SICHERHEITSHALBER EINBAUEN MÜSSEN, STATT CARDINAL ==

    Assert( ALen >= 0, 'CopyDing crash' ); //<== ODER ARBEITET IHR MIT ASSERT ? Hilft aber nur beim Debuggen.

    if Length( AArray) >= ALen then //<== Dann gehts auch mit Integer
    begin
       ....
    end;
end;

himitsu 26. Mai 2021 15:53

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Rollo62 (Beitrag 1490191)
Hilft aber nur beim Debuggen

Kann man aber auch in der Release aktivieren, wenn man möchte.


Du darfst in deiner Funktion den Integer gern nach Cardinal casten und mit Length vergleichen (Length auch nach Integer gecastet, damit der Compiler nichts für den Vergleich unnötig nach Int64 erweitert)

-1 ähhhh $FFFFFFFF ist ja größer als Length und somit raucht die Längenprüfung auch da ab, ohne daß man explizit <0 prüfen muß.
Delphi-Quellcode:
if Cardinal(ALen) <= Cardinal(Length(AArray)) then
  ÜberlaufBzwNichtGenugBytesImArray

Der schöne Günther 26. Mai 2021 16:09

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Rollo62 (Beitrag 1490191)
Delphi-Quellcode:
 //<== DAS WÜRDE ICH SICHERHEITSHALBER EINBAUEN MÜSSEN, STATT CARDINAL ==

//<== ODER ARBEITET IHR MIT ASSERT ? Hilft aber nur beim Debuggen.

Stillschweigend die Methode einfach zu verlassen ist glaube ich nicht der richtige Weg.

Delphi-Quellcode:
if(ALen < 0) then
   raise EArgumentOutOfRangeException.Create(..);
if(ALen > Length(AArray)) then
   raise EArgumentOutOfRangeException.Create(..);

Rollo62 26. Mai 2021 16:21

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1490194)
Stillschweigend die Methode einfach zu verlassen ist glaube ich nicht der richtige Weg.

Ok, mir ging es eigentlich um cast vs. Guard, das wird jetzt eher philosophisch.
Aber gut:

1.) Wenn ALen = 0 dann macht die Routine mit 0-Elementen nichts : OK
2.) Wenn ALen < 0 dann kann die Routine mit < 0 Elementen auch nichts machen: So what ?

Bei 1.) ist es kein Parameterfehler, und ich sehe das bei 2.) sehr ähnlich.
Deshalb kommt ja der Gedanke Cardinal zu nutzen, weil es dann < 0 Situationen technisch gar nicht geben kann.

Das 2.) jetzt noch weiter abzufangen und zu bearbeiten ist ja genau der extra Guard-Code den ich mir sparen möchte.
Wenn meine Funktion >= 0 verlangt, und ich < 0 ignoriere (gleiches Verhalten wie 0), ist das für mich logisch erstmal konform
( mach nur das was du wirklich machen kannst ).

Für die korrekte Übergabe der Parameter ist dann der Aufrufer verantwortlich, der sicher den passenden Guard-Code schon drin hat.

Der schöne Günther 26. Mai 2021 16:46

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Rollo62 (Beitrag 1490196)
Für die korrekte Übergabe der Parameter ist dann der Aufrufer verantwortlich, der sicher den passenden Guard-Code schon drin hat.

Dem würde ich aber härtestens widersprechen. Das schiebt doch nur die Verantwortung von sich weg.
https://de.wikipedia.org/wiki/Fail-Fast

Es ist doch kein Aufwand einmal eine Klassenmethode
Delphi-Quellcode:
CheckArguments(const bytes: TBytes; const index: NativeInt)
einzuführen, die kannst du überall recyclen. Alternativ bringt z.B. Spring4D gleich ein
Delphi-Quellcode:
Guard.CheckRange(..)
mit (wie viele andere auch). An Prüfungen sollte man wirklich nicht sparen wollen.

himitsu 26. Mai 2021 17:03

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von Rollo62 (Beitrag 1490196)
2.) Wenn ALen < 0 dann kann die Routine mit < 0 Elementen auch nichts machen:

Ist auch Philosophisch/Ideologisch/Mutmaßlich ... man könnte ja davon ausgehen, dass es ein Eingabefehler ist. :angle:


Zitat:

Zitat von Rollo62 (Beitrag 1490196)
Deshalb kommt ja der Gedanke Cardinal zu nutzen, weil es dann < 0 Situationen technisch gar nicht geben kann.

Im Gegenzug gibt es bei Cardinal aber auch 2 Milliarden Werte, die es technisch niemals geben kann. ( >2 GB )

Rollo62 26. Mai 2021 18:25

AW: System.Length: Warum Integer und nicht Cardinal ?
 
Zitat:

Zitat von himitsu (Beitrag 1490200)
Im Gegenzug gibt es bei Cardinal aber auch 2 Milliarden Werte, die es technisch niemals geben kann. ( >2 GB )

Schon klar,
aber ich brauche dann nur < Length() zu prüfen, was ich sowieso tun muss, und da schliesst sich der Kreis :stupid:

Zu dem Fail-first:
Es ist vieleicht die Frage was man als Fehler definiert, und was nicht.
Bei Div0 ist das ja klar.
Aber zum Beispiel könnte ein Ignorieren bei < 0 Teil der Funktion sein,
so dass man abhängig von einem Ergebnis eine Funktion auslösen kann.

Mal als Beispiel eine CutLeft Funktion (sorry, ist schon spät, mir fällt momentan nichts Besseres ein).
Delphi-Quellcode:
procedure CutLeft( var AArray : TBytes; const ACutCount : Integer );
var LTemp : TBytes;
begin
    if (ACutCount > 0) and (Max(ACutCount, Length(AArray)) <= Length(AArray)) then
    begin
        SetLength( LTemp, Length( AArray )- ACutCount );
        if Length( LTemp ) > 0 then
            System.Move( AArray[ 0 ], LTemp[ 0 ], Length( LTemp ) );
        AArray := ATemp;
    end;
end;
Die kann ich Anwenden von 1 bis MaxInt, und schneidet entsprechend viele Left-Zeichen raus.
Negative Werte werden ignoriert.

Ich kann die Anwenden mit Berechnungen, wie
Delphi-Quellcode:
var
  LValue : TBytes;
  LPos  : Integer;

begin
    LValue := Xyz;
    ...
    LPos := FindCut( LValue, CutSequence );
    ...
    LPos := LPos + 3; // Berechne CutPosition

    CutLeft( LValue, LPos ); //<== Ich kann mich drauf verlassen das nichts Ungewolltes passiert

    oder

    LPos := LPos - 3; // Berechne CutPosition

    CutLeft( LValue, LPos ); //<== Ich kann mich drauf verlassen das nichts Ungewolltes passiert

end;
Das Ergebnis ist immer korrekt ( aus meiner Sicht ) kein Grund hier irgendwelche "Exception" zu werfen.
Das Ergebnis ist entweder leer oder das gesamte Array, mögliche Überläufe werden ignoriert.

Wenn ich die Überläufe Vermeiden oder Erkennen muss, dann kann ich ganz simpel im Aufrufer LPos checken,
sowas in der Art meinte ich.
Die Funktion selbst ins intrinsisch resilient gegen irgendwelche Über/Unterläufe,
und ich kann mich auf das Ergebnis verlassen und z.B. ohne weitere Prüfungen weiterverarbeiten.

Falls da ein LPos-1 OffsetFehler drin sein sollte, dann hilft mir eine scharfe Abfrage doch auch nicht weiter.

Es gibt sicher Funktionen wo ich die scharfe Prüfung explizit möchte, das ist richtig, und die darf "Exception" werfen.
Ich denke es kann auch gute Gründe für eine "ignorante" Funktion geben :stupid:


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:37 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz