Nicht so ganz... Const ist auch ein CallbyReference, der verbotene Schreibzugriff wird über Compilermagic geregelt... Oder?
Das kommt drauf an. Bei trivialen Datentypen verhält sich
const
wie CallByValue:
Delphi-Quellcode:
Unit1.pas.45: A(X);
005CDEEF 8B45F8 mov eax,[ebp-$08] // CallByValue
005CDEF2 E8D9FFFFFF call A // procedure A(X: Integer)
Unit1.pas.46: B(X);
005CDEF7 8D45F8 lea eax,[ebp-$08] // CallByReference
005CDEFA E8D5FFFFFF call B // procedure B(var X: Integer)
Unit1.pas.47: C(X);
005CDEFF 8B45F8 mov eax,[ebp-$08] // CallByValue
005CDF02 E8D1FFFFFF call C // procedure C(const X: Integer)
Bei komplexen Datentypen (Objekte, Records, Interfaces, Strings) dahingegen wie CallByReference (dann wird allerdings auch der Parameter, welcher komplett ohne
const
oder
var
deklariert wurde als CallByReference umgesetzt. Der einzige Unterschied zwischen
const
und "nichts" besteht hier wirklich nur in der Schreibschutzprüfung - welche wie du bereits vermutet hast - zur Compiletime umgesetzt wird):
Delphi-Quellcode:
Unit1.pas.52: A(X);
005CDEEF 8D45F4 lea eax,[ebp-$0c] // CallByReference
005CDEF2 E8D9FFFFFF call A // procedure A(X: TStruct)
Unit1.pas.53: B(X);
005CDEF7 8D45F4 lea eax,[ebp-$0c] // CallByReference
005CDEFA E8D5FFFFFF call B // procedure B(var X: TStruct)
Unit1.pas.54: C(X);
005CDEFF 8D45F4 lea eax,[ebp-$0c] // CallByReference
005CDF02 E8D1FFFFFF call C // procedure C(const X: TStruct)
Hierbei ist Delphi sogar so klug, Records mit z.b. nur einem einzigen Integer Element trotzdem als trivialen Datentyp zu behandeln.
Die eigentliche Optimierung sieht man im Falle des Structs hier ganz schön (jeweils komplett leere Funktionen):
Delphi-Quellcode:
// procedure A(X: TStruct)
Unit1.pas.32: begin
005CDED0 55 push ebp
005CDED1 8BEC mov ebp,esp
005CDED3 81C46CFEFFFF add esp,$fffffe6c
005CDED9 56 push esi
005CDEDA 57 push edi
005CDEDB 8BF0 mov esi,eax
005CDEDD 8DBD6CFEFFFF lea edi,[ebp-$00000194]
005CDEE3 B965000000 mov ecx,$00000065
005CDEE8 F3A5 rep movsd
Unit1.pas.34: end;
005CDEEA 5F pop edi
005CDEEB 5E pop esi
005CDEEC 8BE5 mov esp,ebp
005CDEEE 5D pop ebp
005CDEEF C3 ret
// procedure B(var X: TStruct)
Unit1.pas.37: begin
005CDEF0 55 push ebp
005CDEF1 8BEC mov ebp,esp
005CDEF3 51 push ecx
005CDEF4 8945FC mov [ebp-$04],eax
Unit1.pas.39: end;
005CDEF7 59 pop ecx
005CDEF8 5D pop ebp
005CDEF9 C3 ret
// procedure C(const X: TStruct)
Unit1.pas.42: begin
005CDEFC 55 push ebp
005CDEFD 8BEC mov ebp,esp
005CDEFF 51 push ecx
005CDF00 8945FC mov [ebp-$04],eax
Unit1.pas.44: end;
005CDF03 59 pop ecx
005CDF04 5D pop ebp
005CDF05 C3 ret
Und bei Strings sieht die Sache ähnlich aus (ebenfalls jeweils komplett leere Funktionen):
Delphi-Quellcode:
// procedure A(X: String)
Unit1.pas.32: begin
005CDED0 55 push ebp
005CDED1 8BEC mov ebp,esp
005CDED3 51 push ecx
005CDED4 8945FC mov [ebp-$04],eax
005CDED7 8B45FC mov eax,[ebp-$04]
005CDEDA E881BEE3FF call @UStrAddRef
005CDEDF 33C0 xor eax,eax
005CDEE1 55 push ebp
005CDEE2 6803DF5C00 push $005cdf03
005CDEE7 64FF30 push dword ptr fs:[eax]
005CDEEA 648920 mov fs:[eax],esp
Unit1.pas.34: end;
005CDEED 33C0 xor eax,eax
005CDEEF 5A pop edx
005CDEF0 59 pop ecx
005CDEF1 59 pop ecx
005CDEF2 648910 mov fs:[eax],edx
005CDEF5 680ADF5C00 push $005cdf0a
005CDEFA 8D45FC lea eax,[ebp-$04]
005CDEFD E87ABDE3FF call @UStrClr
005CDF02 C3 ret
005CDF03 E990B3E3FF jmp @HandleFinally
005CDF08 EBF0 jmp $005cdefa
005CDF0A 59 pop ecx
005CDF0B 5D pop ebp
005CDF0C C3 ret
// procedure B(var X: String)
Unit1.pas.37: begin
005CDF10 55 push ebp
005CDF11 8BEC mov ebp,esp
005CDF13 51 push ecx
005CDF14 8945FC mov [ebp-$04],eax
Unit1.pas.39: end;
005CDF17 59 pop ecx
005CDF18 5D pop ebp
005CDF19 C3 ret
// procedure C(const X: String)
Unit1.pas.42: begin
005CDF1C 55 push ebp
005CDF1D 8BEC mov ebp,esp
005CDF1F 51 push ecx
005CDF20 8945FC mov [ebp-$04],eax
Unit1.pas.44: end;
005CDF23 59 pop ecx
005CDF24 5D pop ebp
005CDF25 C3 ret