Các mã được tạo khi tôi biên dịch này, theo thiết lập debug, cũng giống như vậy:
begin
005A9414 55 push ebp
005A9415 8BEC mov ebp,esp
005A9417 83C4E4 add esp,-$1c
005A941A 33C9 xor ecx,ecx
005A941C 894DEC mov [ebp-$14],ecx
005A941F 894DE8 mov [ebp-$18],ecx
005A9422 894DE4 mov [ebp-$1c],ecx
005A9425 8955F0 mov [ebp-$10],edx
005A9428 8945F4 mov [ebp-$0c],eax
005A942B 33C0 xor eax,eax
005A942D 55 push ebp
005A942E 6890945A00 push $005a9490
005A9433 64FF30 push dword ptr fs:[eax]
005A9436 648920 mov fs:[eax],esp
mov X, eax
005A9439 8945FC mov [ebp-$04],eax
mov Y, edx
005A943C 8955F8 mov [ebp-$08],edx
Khi mã bắt đầu thực hiện, eax
thực sự là con trỏ tự. Nhưng trình biên dịch đã chọn lưu nó vào ebp-$0c
và sau đó là số. Đó là thực sự lên đến trình biên dịch.
Mã trong cài đặt bản phát hành khá giống nhau. Trình biên dịch vẫn chọn zeroise eax
. Tất nhiên, bạn không thể dựa vào trình biên dịch làm điều đó.
begin
005A82A4 55 push ebp
005A82A5 8BEC mov ebp,esp
005A82A7 33C9 xor ecx,ecx
005A82A9 51 push ecx
005A82AA 51 push ecx
005A82AB 51 push ecx
005A82AC 51 push ecx
005A82AD 51 push ecx
005A82AE 33C0 xor eax,eax
005A82B0 55 push ebp
005A82B1 6813835A00 push $005a8313
005A82B6 64FF30 push dword ptr fs:[eax]
005A82B9 648920 mov fs:[eax],esp
mov X, eax
005A82BC 8945FC mov [ebp-$04],eax
mov Y, edx
005A82BF 8955F8 mov [ebp-$08],edx
Hãy nhớ rằng tham số truyền xác định trạng thái thanh ghi và ngăn xếp khi hàm bắt đầu thực hiện. Điều gì xảy ra tiếp theo, cách chức năng giải mã các tham số là xuống trình biên dịch. Đó là không có nghĩa vụ để lại ảnh hưởng đến các thanh ghi và ngăn xếp được sử dụng để đi qua tham số.
Nếu bạn chèn asm vào giữa một hàm, bạn không thể mong đợi các thanh ghi dễ bay hơi như eax
để có các giá trị cụ thể. Họ sẽ giữ bất cứ điều gì trình biên dịch đã xảy ra để đưa vào chúng gần đây nhất.
Nếu bạn muốn kiểm tra sổ đăng ký vào lúc bắt đầu thực hiện hàm, bạn cần sử dụng hàm asm thuần túy để đảm bảo tránh trình biên dịch sửa đổi thanh ghi đã được sử dụng để chuyển tham số:
var
X, Y: Pointer;
asm
mov X, eax
mov Y, edx
// .... do something with X and Y
end;
Trình biên dịch sẽ lựa chọn rất nhiều phụ thuộc vào mã trong phần còn lại của hàm. Đối với mã của bạn, sự phức tạp của việc lắp ráp chuỗi để chuyển đến ShowMessage
gây ra một phần mở đầu khá lớn. Hãy xem xét mã này để thay thế:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
i: Integer;
function Sum(j: Integer): Integer;
end;
....
procedure TForm1.FormCreate(Sender: TObject);
begin
i := 624;
Caption := IntToStr(Sum(42));
end;
function TForm1.Sum(j: Integer): Integer;
var
X: Pointer;
begin
asm
mov X, eax
end;
Result := TForm1(X).i + j;
end;
Trong trường hợp này mã đơn giản đủ để trình biên dịch rời khỏi eax
một mình. Mã xây dựng bản phát hành được tối ưu hóa cho Sum
là:
begin
005A8298 55 push ebp
005A8299 8BEC mov ebp,esp
005A829B 51 push ecx
mov X, eax
005A829C 8945FC mov [ebp-$04],eax
Result := TForm4(X).i + j;
005A829F 8B45FC mov eax,[ebp-$04]
005A82A2 8B80A0030000 mov eax,[eax+$000003a0]
005A82A8 03C2 add eax,edx
end;
005A82AA 59 pop ecx
005A82AB 5D pop ebp
005A82AC C3 ret
Và khi bạn chạy mã, chú thích của biểu mẫu được thay đổi thành giá trị mong đợi.
Hoàn toàn trung thực, lắp ráp nội tuyến, được đặt dưới dạng khối asm bên trong hàm Pascal, không hữu ích lắm. Điều về viết assembly là bạn cần hiểu đầy đủ trạng thái của thanh ghi và ngăn xếp. được xác định rõ ở đầu và cuối của một hàm, được xác định bởi ABI.
Nhưng ở giữa một hàm, trạng thái đó phụ thuộc hoàn toàn vào các quyết định của trình biên dịch. Việc tiêm các khối asm trong đó yêu cầu bạn phải biết các quyết định mà trình biên dịch đã thực hiện. Nó cũng có nghĩa là trình biên dịch không thể hiểu được các quyết định mà bạn đã thực hiện. Điều này thường không thực tế. Thật vậy cho trình biên dịch x64 Embarcadero cấm các khối asm nội tuyến như vậy. Cá nhân tôi chưa bao giờ sử dụng khối nội tuyến asm trong mã của tôi. Nếu bao giờ tôi viết asm tôi luôn luôn viết chức năng asm tinh khiết.
Cảm ơn bạn đã bình luận am hiểu của bạn! – SOUser