Autor Beitrag
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Di 04.07.17 19:42 
Hallo zusammen,

ich brauche mal eure Hilfe und/oder Erfahrung in einer Sprach-Standard-Argumentation. Ich sag zuerst nicht warum, das ist Teil der Übung ;)

Wir definieren:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
type TMyEnum = (enZero, enOne, enTwo, enThree, enFour);

function GetEnumTimesTen(aValue: TMyEnum): Integer;
begin
  case aValue of
    enZero   : Result:= 0;
    enOne    : Result:= 10;
    enThree  : Result:= 30;
    enFour   : Result:= 40;
  else
    Result:= -1;
  end;
end;


Was passiert in den folgenden Fällen?
  1. ausblenden Delphi-Quelltext
    1:
    2:
    var x: integer;
    x:= GetEnumTimesTen(enOne);

  2. ausblenden Delphi-Quelltext
    1:
    2:
    var x: integer;
    x:= GetEnumTimesTen(enTwo);

  3. Wir exportieren die Funktion in einer DLL, die von folgendem C-Code genutzt wird (*)
    ausblenden C++-Quelltext
    1:
    int x = GetEnumTimesTen(3);					

  4. Wir exportieren die Funktion in einer DLL, die von folgendem C-Code genutzt wird (*).
    ausblenden C++-Quelltext
    1:
    int x = GetEnumTimesTen(42);					

(*) Für die C-Header nehmen wir an, dass wir ganz altmodisch einen in der Größe passenden int verwenden und die Elemente als dumme #defines definiert sind (aber hier nicht verwendet werden).


Nicht ausprobieren (jedenfalls nicht als Erstes), mich interessiert eure Intuition / Sprachverständnis. Wenn ihr der Meinung seid, dass das vom Compiler/Dialekt/Flags abhängt, bitte auch mit angeben.
Wenn ihr Arbeitskollegen habt, die vielleicht andere Compiler kennen, gerne auch die befragen und die Antworten weitergeben.

Wenn wir ein paar Antworten haben, verrate ich worum es geht. Wer da anderswo vielleicht schon mitgelesen hat: nicht verraten ;)

Vielen Dank!

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."


Zuletzt bearbeitet von Martok am Di 04.07.17 22:51, insgesamt 6-mal bearbeitet
Bergmann89
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1742
Erhaltene Danke: 72

Win7 x64, Ubuntu 11.10
Delphi 7 Personal, Lazarus/FPC 2.2.4, C, C++, C# (Visual Studio 2010), PHP, Java (Netbeans, Eclipse)
BeitragVerfasst: Di 04.07.17 19:49 
a) 10
b) -1
c) 30
d) -1

;)

_________________
Ich weiß nicht viel, lern aber dafür umso schneller^^

Für diesen Beitrag haben gedankt: Martok
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Di 04.07.17 19:56 
- Nachträglich durch die Entwickler-Ecke gelöscht -

Für diesen Beitrag haben gedankt: Martok
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 376
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Di 04.07.17 22:42 
a) 10
b) -1
c) 30 weil enThree das dritte Element von TMyEnum ist.
d) -1 weil es kein Element 42 in TMyEnum gibt.

Bei c und d würde ich allerdings eine Fehlermeldung des Compilers erwarten. Zu einer DLL gehört eine Header-Datei in der jeweiligen Sprache, hier also C++. Ich kenne C++ kaum, kann mir aber nicht vorstellen, dass man einer Funktion eine Integer-Variablen übergeben kann, in deren Header ein Aufzähltyp definiert ist.

Trotzdem, schönes Spiel,
Gruß
GuaAck

Für diesen Beitrag haben gedankt: Martok
Martok Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Di 04.07.17 22:47 
Guter Punkt zur C-Seite des Imports der hier zweimal kam, ich habe das oben mal präzisiert. In C++ oder C# hätten wir mehr Möglichkeiten, C hat die aber nicht wirklich.

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mi 05.07.17 04:09 
- Nachträglich durch die Entwickler-Ecke gelöscht -


Zuletzt bearbeitet von Frühlingsrolle am Do 06.07.17 00:48, insgesamt 1-mal bearbeitet

Für diesen Beitrag haben gedankt: Martok
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1652
Erhaltene Danke: 243

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: Mi 05.07.17 08:28 
Hallo,

ich habe keinen blassen Schimmer von C und Konsorten...
Bei c stelle ich mir aber vor, dass die Enum-Werte von 0..3 laufen
a) 10
b) enZwo ist nicht definiert, kann ich wohl nicht kompilieren.
c) 40
d) -1

Gruß Horst

Für diesen Beitrag haben gedankt: Martok
FinnO
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1331
Erhaltene Danke: 123

Mac OSX, Arch
TypeScript (Webstorm), Kotlin, Clojure (IDEA), Golang (VSCode)
BeitragVerfasst: Mi 05.07.17 20:52 
Da ich nie Delphi verwende, bin ich wahrscheinlich prädestiniert für diese Aufgabe:

  1. ausblenden Quelltext
    1:
    x = 10					

  2. ausblenden Quelltext
    1:
    x = -1					

  3. Ich nehme an, dass Delphi-Enums aus historischen Gründen 1-indiziert sind. Ich gehe auch einfach mal davon aus, dass der C-int mit dem Typen mit dem Delphi intern Enums repräsentiert, binärkompatibel ist. Daher würde ich sagen, 3 entspricht enTwo und deshalb x = -1.
  4. ausblenden Quelltext
    1:
    x = -1					



Was auch immer du damit herauskriegen möchtest. :nixweiss:

Für diesen Beitrag haben gedankt: Martok
Sinspin
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1321
Erhaltene Danke: 117

Win 10
RIO, CE, Lazarus
BeitragVerfasst: Mi 05.07.17 21:33 
Ich schließe mich
user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
a) 10
b) -1
c) 30
d) -1

;)
an.

Das entspricht jedenfalls meinen Erfahrungen.
Da Du Export schreibst, ist die reale Funktion ja nach wie vor in Delphi.dll geschrieben.
Btw., Enum geht bei Null los.

_________________
Wir zerstören die Natur und Wälder der Erde. Wir töten wilde Tiere für Trophäen. Wir produzieren Lebewesen als Massenware um sie nach wenigen Monaten zu töten. Warum sollte unser aller Mutter, die Natur, nicht die gleichen Rechte haben?

Für diesen Beitrag haben gedankt: Martok
Martok Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: So 09.07.17 14:40 
So, schreiten wir mal zur Lösung ;)

Zunächst mal meine Erwartung:
ausblenden Quelltext
1:
2:
3:
4:
a) 10,  weil wir einen direkten Treffer haben 
b) -1,  weil enTwo kein Case-Label hat, also else gilt
c) 30,  weil 3 der Ordninalwert von enThree ist
d) -1,  weil es kein definiertes Label mit Ordinalwert 42 gibt, also else gilt


So verhalten sich auch alle Borland-Compiler seit 5.0 sowie Stanford Pascal für Mainframes. In ISO-Pascal hat CASE keine ELSE, da stellt sich die Frage also nicht. Vielleicht auch frühere Borland-Compiler, aber seit den 5.0-Büchern steht das explizit so in der Sprachreferenz. Das Handbuch zu TP3 hab ich zwar hier, aber da ist das nicht so deutlich. Das Ganze ist unabhängig von Rangechecks ({$R+}), da wir keine eigene Zuweisung an aValue haben, sondern nur ein Stück Speicher übergeben kriegen.

Und jetzt der Knaller: Freepascal sieht das anders, auch in den TP und Delphi-Modes. :shock:

Die Antwort auf a-c) ist noch gleich, d) jedoch ist undefiniertes Verhalten, da:
Am 02.07.2017 um 22:02 schrieb Florian Klämpfl:
Because it is a fundamental question: if there is any defined behavior possible if a variable contains an invalid value. I consider a value outside of the declared range as invalid.

Das Programm kann dort also in den Else-Block springen, oder nichts tun oder abstürzen oder sonst irgendwas Beliebiges tun.

"Etwas Beliebiges" ist an der Stelle auch, zur "Optimierung" Code zu generieren, der zwar auf den ersten Blick gut aussieht, aber gravierende Probleme enthält. Ich persönlich habe das Problem gefunden, als ich eine real existierende Remote Code Execution debuggt habe.

Interessanterweise klingt sein Statement ja sehr eindeutig, aber nur in der Implementation von CASE..OF wird diese Annahme auch umgesetzt. Würde man das Beispiel durch eine lange If-Then-Else-If-Kette ersetzen, wäre es wieder voll definiert.

Macht das für euch in irgend einer Weise Sinn? Für mich nämlich nicht. :gruebel:

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10181
Erhaltene Danke: 1254

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: So 09.07.17 22:26 
Moin!

Tja, das klingt nach einer sehr formalistisch geprägten Sicht auf die (Hoch-)Sprache, eben "informatiker-like"... :? Ich kann diese Sichtweise (nach Erklärung!) durchaus nachvollziehen, aber ich würde sie in einem real umgesetzten Stück Maschinencode nicht mehr erwarten. Hier würde ich die technisch, ingenieurhafte "fail-safe"-Version, nämlich den else-Fall erwarten. :nixweiss: Alles andere wäre in meinen Augen zwar vielleicht für einen Informatiker "richtig", aber das sind nicht die Leute, die für´s Programmieren bezahlt werden, die würden nämlich - völlig zu recht - auf die Barrikaden gehen, da "undefinierte Zustände" der Alptraum eines jeden professionellen Codeschmieds sind. :hair: Als Compiler-Bauer steht man der Informatik vielleicht etwas näher (es ist nicht umsonst die Königsdisziplin dieser Zunft), aber ein theoretisches Prinzip um diesen Preis im erzeugten Code "durchzusetzen"... damit vertreibt man langfristig die Leute, die das Werkzeug eigentlich nutzen sollten. :schmoll:

btw: Der Vergleich mit der if-then-else-Kette hinkt (zumindest in der Theorie) etwas, denn hier mache ich verkettete Fallunterscheidungen, die (genau genommen) nichts miteinander zu tun haben müssen (also kontextfrei sind). Das ist beim case-of ja durchaus nicht so, hier betrachte ich schon einen Wertebereich. :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mo 10.07.17 05:10 
- Nachträglich durch die Entwickler-Ecke gelöscht -
Martok Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Di 11.07.17 16:19 
(Bastelbeitrag während der Recherche für mich, und hier fürs Archiv)

Folgendes Testprogramm untersucht mehrere Dinge auf einmal, die ich unten diskutieren werde:
ausblenden volle Höhe 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:
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:
type
  {$Z1}
  TEnum = (enZero, enOne, enTwo,enThree);
  TEnumExtra = (exZero, exOne, exTwo, e1,e2,e3,e4,e5,e6,e7,e8,e9,ea,exThree);

procedure TestA1;
var
  s: TEnum;
begin
  if s in [low(TEnum)..high(TEnum)] then
    WriteLn(1)
  else
    WriteLn(2);
end;

procedure TestA2;
var
  s: TEnum;
begin
  case s of
    low(TEnum)..high(TEnum): WriteLn(1)
  else
    WriteLn(2);
  end;
end;

procedure TestA3;
var
  s: TEnumExtra;
begin
  case s of
    exZero: WriteLn(1);
    exOne: WriteLn(1);
    exTwo: WriteLn(1);
    e1: WriteLn(1);
    e2: WriteLn(1);
    e3: WriteLn(1);
    e4: WriteLn(1);
    e5: WriteLn(1);
    e6: WriteLn(1);
    e7: WriteLn(1);
    e8: WriteLn(1);
    e9: WriteLn(1);
    ea: WriteLn(1);
    exThree: WriteLn(1);
  else
    WriteLn(2);
  end;
end;


type
  TFakeEnum = type Byte;
const
  efZero = TFakeEnum(0);
  efOne  = TFakeEnum(1);
  efTwo  = TFakeEnum(2);
  efThree= TFakeEnum(3);

procedure TestB1;
var
  s: TFakeEnum;
begin
  if s in [efZero..efThree] then
    WriteLn(1)
  else
    WriteLn(2);
end;

procedure TestB2;
var
  s: TFakeEnum;
begin
  case s of
    efZero..efThree: WriteLn(1)
  else
    WriteLn(2);
  end;
end;


begin
  WriteLn('Sizeof(TEnum) = ', Sizeof(TEnum));
  WriteLn('Sizeof(TFakeEnum) = ', Sizeof(TFakeEnum));
  TestA1;
  TestA2;
  TestA3;
  TestB1;
  TestB2;
end.


Punkt 1: Was ist ein Enum?
Die Deklarationen von TEnum und TFakeEnum+Konstanten sind laut TurboPascal 5, 7 sowie Delphi 4 Sprachreferenz formal equivalent.
TP7 Language Guide, p.26 hat folgendes geschrieben:
When an identifier occurs within the identifier list of an enumerated type, it's declared as a constant for the block the enumerated type is declared in. The constant's type is the enumerated type being declared.
Mit der Funktion Ord() kann man den Wert der Konstante im Basistyp abfragen. Was ist der Basistyp?
TP7 Language Guide, p.219 hat folgendes geschrieben:
An enumerated type is stored as an unsigned byte if the enumeration has 256 or fewer values; otherwise, it's stored as an unsigned word.
Die Standardgröße sich mit Delphi geändert, mit $Z/$MINENUMSIZE bleibt aber alles gleich.
Im Testprogramm sind beide Typen 1 Byte groß.

Punkt 2: Unterscheidet sich ein Case mit genau einem Label und Else-Teil von If+In? (Test*1 vs Test*2)
Rein von der Logik her: nein. Beides fragt "haben wir einen Wert in diesem Bereich, oder nicht?"
In Delphi erzeugen alle 4 Testprozeduren exakt den gleichen Code.
In FPC erzeugt If+In ein cmp/jae, während Case sub/ja erzeugt. Das aber sowohl von der Funktion als auch Cyclecount identisch (siehe Punkt 4).

Punkt 3: Unterscheidet sich ein Case mit genau einem Range-Label von "alle Elemente aufzählen"? (TestA2 vs TestA3)
Sowohl in Delphi als auch in FPC wird bei ausreichender Anzahl Label eine Jumptable generiert. Delphi packt die direkt in den laufenden Codeblock, FPC in eine Data-Section.
In Delphi gehen Werte außerhalb der Angegebenen explizit in den Else-Block (cmp/jnbe).
FPC lässt den Bereichscheck weg und ist deswegen undefiniert.

Punkt 4: Lohnt es sich denn, hier den Check wegzuoptimieren?
Nach den Cycle-Angaben in dieser PDF: nicht wirklich.
ausblenden Quelltext
1:
2:
3:
4:
            K7 {taken/not taken}   Haswell {predicted/mispredict}
CMP+Jcc     1/3 + {1/3, 2}         0.25 + {0.5, 2}
SUB+Jcc     1/3 + {1/3, 2}         0.25 + {0.5, 2}
JMP indir   >=2                    2

Klingt erstmal so als ob der Check auf alter CPU genauso und auf neuer noch fast halb so teuer wäre wie der Jump, aber: man darf nicht vergessen dass der Jump in nicht-trivialem Code sehr wahrscheinlich weit genug springt um ein instruction cache flush zu erzwingen. Dagegen ist eine branch misprediction eher unwahrscheinlich.

Punkt 5: Könnte man etwas anderes optimieren?
Alle TestA*-Prozeduren sind Tautologien. Bei konsequent strikter Interpretation von Enums könnte der Else-Block immer weggelassen werden.

Bei Jumptables wird der Sprungindex in Delphi mit $7F und in FPC mit $FF maskiert. Würde man die Tabelle bis auf eine Power-of-Two füllen und die korrekte Maske nutzen, könnte man den Check wirklich weglassen (zum Preis der größeren Tabelle).

Muss man wirklich die 0.75 cycles sparen, ist es eine etablierte Technik, die Jumptable manuell zu deklarieren. Beispiel: Dispatch Tables für Bytecode-Interpreter. Es ist nicht üblich, dass der Compiler das ungefragt tut.

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."