2016-03-10 36 views
9

Wystąpił problem podczas pracy z procedurami i ciągami w Delphi. Faktem jest, że spodziewałem się zobaczyć ciąg wyjściowy "1S2S3S4S5S6S", ale faktycznym wyjściem jest "1234S5S6". Podczas procesu debugowania jest napisane, że zmienne łańcuchowe S1, S2, S3 i S6 nie są inicjowane (S1, S2, S3, S6 to "łańcuchy", S4 i S5 mają wartość "S"). Czy ktoś może mi to wyjaśnić? Oto kod:Procedury Delphi z parametrami łańcuchowymi

program StringTest; 

{$APPTYPE CONSOLE} 

procedure MyProcedure(S1: String; const S2: String; var S3: String; 
         S4: String; const S5: String; var S6: String; 
         out S7: String); 
begin 
    S7 := '1' + S1 + '2' + S2 + '3' + S3 + '4' + S4 + '5' + S5 + '6' + S6; 
end; 

procedure Work; 
var 
    S: String; 
begin 
    S := 'S'; 
    MyProcedure(S, S, S, S, S, S, S); 
    writeln(S); 
end; 

begin 
    Work; 
    readln; 
end. 
+4

Uwaga: Kiedy deklarujesz parametr z 'const', mówisz kompilatorowi, że nie powinien oczekiwać, że parametr zmieni się na czas trwania tej funkcji. Twoim obowiązkiem jest upewnić się, że dotrzymasz tej obietnicy; kompilator nie może tego dla ciebie sprawdzić. W tym przypadku modyfikujesz 'S' do' S7', jednocześnie twierdząc, że 'S2' i' S5' nie ulegną zmianie. –

Odpowiedz

17

Twój parametr S7 została zadeklarowana jako parametr out, więc kompilator będzie ustawić zmienną przekazany do pustej struny, gdy funkcja jest wywoływana. Przekazujesz tę samą zmienną S dla wszystkich parametrów, łącznie z parametrem wyjściowym, dzięki czemu wartość S zostanie wymazana z pamięci, zanim wartości parametrów zostaną użyte w funkcji.

celu dalszego dopracowania procedurę stosuje konwencję register wywołującego, gdzie S1 .. S3 są przekazywane do rejestrów CPU (EAX EDX i ECX, odpowiednio) i S4 .. S6 są przekazywane na stosie, a nie. Zmienna wejściowa string jest kasowana wyczyszczona po tym, jak jej bieżąca wartość jest pchana na stos dla S4 i S5 (S3 i S6 są tylko wskaźnikami do zmiennej), a przed wartością jest przypisana do S1 i S2. Więc S1 i S2 skończyć zerowa, S4 i S5 zawierać odnośniki do oryginalnych danych 'S' przed wytrzeć i S3 i S6 są wskazując na string zmiennej, która została zniszczona.

Debugger może pokazać ci to wszystko w akcji. Jeśli umieścisz punkt przerwania na linii gdzie MyProcedure() nazywa, a następnie otworzyć widok procesora, można zobaczyć następujące instrukcje montażu:

StringTest.dpr.17: MyProcedure(S, S, S, S, S, S, S); 
00405A6C 8B45FC   mov eax,[ebp-$04] // [ebp-$04] is the current value of S 
00405A6F 50    push eax   // <-- assign S4 
00405A70 8B45FC   mov eax,[ebp-$04] 
00405A73 50    push eax   // <-- assign S5 
00405A74 8D45FC   lea eax,[ebp-$04] 
00405A77 50    push eax   // <-- assign S6 
00405A78 8D45FC   lea eax,[ebp-$04] 
00405A7B E8B0EDFFFF  call @UStrClr  // <-- 'out' wipes out S! 
00405A80 50    push eax   // <-- assign S7 
00405A81 8D4DFC   lea ecx,[ebp-$04] // <-- assign S3 
00405A84 8B55FC   mov edx,[ebp-$04] // <-- assign S2 
00405A87 8B45FC   mov eax,[ebp-$04] // <-- assign S1 
00405A8A E8B9FEFFFF  call MyProcedure 

Aby rozwiązać ten problem, należy użyć innej zmiennej, aby otrzymać wyjście :

procedure Work; 
var 
    S, Res: String; 
begin 
    S := 'S'; 
    Proc(S, S, S, S, S, S, Res); 
    WriteLn(Res); 
end; 

Alternatywnie, zmiany procedury do funkcji, która zwraca nowy String poprzez jego Result zamiast stosowania parametru out:

function MyFunction(S1: String; const S2: String; var S3: String; 
         S4: String; const S5: String; var S6: String): String; 
begin 
    Result := '1' + S1 + '2' + S2 + '3' + S3 + '4' + S4 + '5' + S5 + '6' + S6; 
end; 

procedure Work; 
var 
    S: String; 
begin 
    S := 'S'; 
    WriteLn(MyFunction(S, S, S, S, S, S)); 
end; 
+0

Dziękuję za poradę, ale nadal nie rozumiem, dlaczego S4 i S5 mają wartość "S", a inne nie. Co jest nie tak? – Alexander

+0

OK, otrzymuję teraz :) Ostatnie pytanie, dlaczego tak się dzieje w takiej kolejności? Chodzi mi o to, że wartości S4, S5 są najpierw przekazywane do stosu, a następnie S1, S2, S3 do rejestrów. – Alexander

+5

Jeśli parametry rejestru były najpierw zapisane w rejestrach, @Alexander, rejestry te nie byłyby dostępne dla kompilatora do obliczenia wartości parametrów stosu. Kompilator wie, że może obliczać wartości parametrów w dowolnej kolejności, więc wybiera zamówienie, które jest wygodne dla kompilatora. –