2011-10-13 30 views
83

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.

+7

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. –

+0

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

+3

@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. –

Trả lời

13

Nếu có bất kỳ tài liệu nào về hành vi này, nó có thể nằm trong vùng biên dịch của các biến tạm thời để giữ kết quả trung gian khi chuyển các kết quả hàm dưới dạng tham số.Hãy xem xét mã này:

procedure UseInterface(foo: IInterface); 
begin 
end; 

procedure Test() 
begin 
    UseInterface(Create()); 
end; 

Trình biên dịch có để tạo ra một biến temp ngầm để giữ kết quả của Tạo vì nó được thông qua vào UseInterface, để đảm bảo rằng giao diện có một đời> = tuổi thọ của cuộc gọi UseInterface . Biến tạm thời tiềm ẩn đó sẽ được xử lý ở cuối thủ tục sở hữu nó, trong trường hợp này là ở cuối thủ tục Test(). Có thể trường hợp gán con trỏ của bạn có thể rơi vào cùng một nhóm như truyền các giá trị giao diện trung gian như các tham số hàm, vì trình biên dịch không thể "nhìn thấy" trong đó giá trị đang đi.

Tôi nhớ đã có một vài lỗi trong khu vực này trong những năm qua. Cách đây lâu (D3? D4?), Trình biên dịch không tham chiếu đếm giá trị trung gian chút nào. Nó làm việc hầu hết thời gian, nhưng đã gặp rắc rối trong các tình huống bí danh tham số. Một khi đã được giải quyết có một theo dõi liên quan đến const params, tôi tin. Luôn luôn có một mong muốn di chuyển xử lý của giao diện giá trị trung gian lên càng sớm càng tốt sau khi tuyên bố trong đó nó là cần thiết, nhưng tôi không nghĩ rằng bao giờ đã được thực hiện trong trình tối ưu hóa Win32 vì trình biên dịch chỉ không được thiết lập lên để xử lý việc xử lý theo tuyên bố hoặc mức độ chi tiết của khối.

0

Bạn không thể đảm bảo rằng trình biên dịch sẽ không quyết định tạo biến vô hình theo thời gian.

Và ngay cả khi bạn làm như vậy, việc tắt tối ưu hóa (hoặc thậm chí ngăn xếp khung?) Có thể làm hỏng mã được kiểm tra hoàn hảo của bạn.

Và ngay cả khi bạn quản lý để xem lại mã của bạn dưới tất cả các kết hợp có thể có tùy chọn dự án - biên dịch mã của bạn dưới cái gì đó như Lazarus hoặc thậm chí phiên bản Delphi mới sẽ mang lại địa ngục trở lại.

Đặt cược tốt nhất là sử dụng quy tắc "biến nội bộ không thể sống lâu hơn". Chúng ta thường không biết, nếu trình biên dịch sẽ tạo ra một số biến nội bộ hay không, nhưng chúng ta biết, rằng bất kỳ biến nào (nếu được tạo) sẽ được hoàn thành khi thường trình tồn tại.

Do đó, nếu bạn có mã như thế này:

// 1. Some code which may (or may not) create invisible variables 
// 2. Some code which requires release of reference-counted data 

Ví dụ:

Lib := LoadLibrary(Lib, 'xyz'); 
try 
    // Create interface 
    P := GetProcAddress(Lib, 'xyz'); 
    I := P; 
    // Work with interface 
finally 
    // Something that requires all interfaces to be released 
    FreeLibrary(Lib); // <- May be not OK 
end; 

Sau đó, bạn nên chỉ quấn "Làm việc với giao diện" khối vào chương trình con:

procedure Work(const Lib: HModule); 
begin 
    // Create interface 
    P := GetProcAddress(Lib, 'xyz'); 
    I := P; 
    // Work with interface 
end; // <- Releases hidden variables (if any exist) 

Lib := LoadLibrary(Lib, 'xyz'); 
try 
    Work(Lib); 
finally 
    // Something that requires all interfaces to be released 
    FreeLibrary(Lib); // <- OK! 
end; 

Đó là một quy tắc đơn giản nhưng hiệu quả.

+0

Trong trường hợp của tôi, tôi: = CreateInterfaceFromLib (...) đã dẫn đến một địa phương tiềm ẩn. Vì vậy, những gì bạn đề nghị sẽ không giúp đỡ. Trong mọi trường hợp, tôi đã chứng minh rõ ràng một cách giải quyết trong câu hỏi. Một dựa trên tuổi thọ của người dân địa phương tiềm ẩn được kiểm soát bởi phạm vi chức năng. Câu hỏi của tôi liên quan đến các kịch bản sẽ dẫn đến những người dân địa phương tiềm ẩn. –

+0

Quan điểm của tôi là đây là một câu hỏi sai khi hỏi ngay từ đầu. – Alex

+0

Bạn được chào đón đến điểm quan điểm đó nhưng bạn nên thể hiện nó như một bình luận. Thêm mã cố gắng (không thành công) để tái tạo các cách giải quyết của câu hỏi, có vẻ lạ với tôi. –

Các vấn đề liên quan