Tôi đã hỏi một số question tương tự về các biến giao diện ẩn cách đây không lâu.Trình biên dịch có xử lý các biến giao diện ngầm được ghi lại không?
Nguồn của câu hỏi này là lỗi trong mã của tôi do tôi không biết về sự tồn tại của biến giao diện ngầm được tạo bởi trình biên dịch. Biến này đã được hoàn thành khi thủ tục sở hữu nó đã hoàn thành. Điều này lần lượt gây ra một lỗi do tuổi thọ của biến dài hơn tôi đã dự đoán.
Bây giờ, tôi có một dự án đơn giản để minh họa cho một số hành vi thú vị từ các trình biên dịch:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
được biên dịch cũng giống như bạn tưởng tượng. Biến cục bộ I
, kết quả của hàm, được chuyển thành tham số ngụ ý var
ẩn thành Create
. Việc dọn dẹp cho StoreToLocal
kết quả trong một cuộc gọi đến IntfClear
. Không có bất ngờ ở đó.
Tuy nhiên, StoreViaPointerToLocal
được xử lý khác nhau. Trình biên dịch tạo ra một biến cục bộ ẩn mà nó chuyển tới Create
. Khi trả về Create
, việc gán cho P^
được thực hiện. Điều này rời khỏi thường trình với hai biến cục bộ chứa tham chiếu đến giao diện. Việc dọn dẹp cho StoreViaPointerToLocal
dẫn đến hai cuộc gọi đến IntfClear
.
Mã biên soạn cho StoreViaPointerToLocal
là như thế này:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
tôi có thể đoán là tại sao các trình biên dịch được thực hiện điều này. Khi nó có thể chứng minh rằng việc gán cho biến kết quả sẽ không làm tăng ngoại lệ (tức là nếu biến là một biến cục bộ) thì biến đó sử dụng biến kết quả trực tiếp. Nếu không, nó sử dụng một địa phương ngầm và sao chép giao diện một khi hàm đã trả về, do đó đảm bảo rằng chúng ta không làm rò rỉ tham chiếu trong trường hợp ngoại lệ.
Nhưng tôi không thể tìm thấy bất kỳ tuyên bố nào trong tài liệu này. Điều quan trọng bởi vì tuổi thọ của giao diện là quan trọng và là một lập trình viên bạn cần để có thể ảnh hưởng đến nó vào dịp này.
Vì vậy, không ai biết liệu có bất kỳ tài liệu nào về hành vi này không? Nếu không ai có thêm kiến thức về nó? Các trường mẫu được xử lý như thế nào, tôi chưa kiểm tra. Tất nhiên tôi có thể thử tất cả cho bản thân mình nhưng tôi đang tìm kiếm một tuyên bố chính thức hơn và luôn luôn muốn tránh dựa vào chi tiết thực hiện làm việc ra bởi thử và sai.
Cập nhật 1
Để trả lời câu hỏi của Remy, nó quan trọng với tôi khi tôi cần thiết để hoàn thành các đối tượng đằng sau giao diện trước khi thực hiện quyết toán khác.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Như được viết như thế này thì được. Nhưng trong đoạn mã thực, tôi đã có một địa phương tiềm ẩn thứ hai đã được hoàn thành sau khi GIL được phát hành và bị đánh bom. Tôi giải quyết vấn đề bằng cách giải nén mã bên trong Acquire/Release GIL thành một phương thức riêng biệt và do đó thu hẹp phạm vi của biến giao diện.
Không biết tại sao điều này lại bị bỏ phiếu, khác với câu hỏi thực sự phức tạp. Upvoted cho là cách trên đầu của tôi. Tôi biết chính xác điều này bit của núi lửa dẫn đến một số tài liệu tham khảo tinh vi đếm lỗi trong một ứng dụng tôi đã làm việc trên một năm trước đây. Một trong những chuyên viên giỏi nhất của chúng tôi đã dành hàng giờ để tìm ra. Cuối cùng chúng tôi đã làm việc xung quanh nó nhưng không bao giờ hiểu cách trình biên dịch được dự định làm việc. –
Khi bạn làm việc với các giao diện đếm tham chiếu, bạn không nên giả định rằng không có các trình khách khác (trình biên dịch trong trường hợp của bạn) có tham chiếu giao diện riêng của chúng. Nếu tất cả các client thực hiện 'AddRef' /' Release' một cách chính xác, tất cả sẽ hoạt động OK. Nếu không nó là một lỗi (lỗi của bạn kể từ khi tôi giả định rằng trình biên dịch làm tham chiếu của nó đếm chính xác). – kludg
@Serg Trình biên dịch đã tính toán tham chiếu của nó một cách hoàn hảo. Vấn đề là có một biến phụ giữ một tham chiếu mà tôi không thể nhìn thấy. Những gì tôi muốn biết là những gì khiêu khích các trình biên dịch để có một, thêm ẩn, tham khảo. –