2013-09-04 27 views
23

Tôi đã có một số vi phạm truy cập không mong muốn đối với mã Delphi mà tôi nghĩ là chính xác, nhưng có vẻ bị sai. Tôi có thể giảm số này thànhMã sai khi kết hợp các thủ tục ẩn danh và lồng nhau

procedure Run(Proc: TProc); 
begin 
    Proc; 
end; 

procedure Test; 
begin 
    Run(
    procedure 
    var 
     S: PChar; 

     procedure Nested; 
     begin 
     Run(
      procedure 
      begin 
      end); 
     S := 'Hello, world!'; 
     end; 

    begin 
     Run(
     procedure 
     begin 
      S := 'Hello'; 
     end); 
     Nested; 
     ShowMessage(S); 
    end); 
end; 

Điều gì xảy ra với tôi là S := 'Hello, world!' đang lưu trữ ở vị trí sai. Do đó, hoặc vi phạm quyền truy cập được nâng lên hoặc ShowMessage(S) hiển thị "Xin chào" (và đôi khi, vi phạm quyền truy cập được nâng lên khi giải phóng các đối tượng được sử dụng để thực hiện các thủ tục ẩn danh).

Tôi đang sử dụng Delphi XE, tất cả các bản cập nhật đã được cài đặt.

Làm thế nào tôi có thể biết nơi này sẽ gây ra vấn đề? Tôi biết cách viết lại mã của mình để tránh các thủ tục ẩn danh, nhưng tôi gặp khó khăn trong việc xác định chính xác tình huống nào dẫn đến mã sai, vì vậy tôi không biết phải tránh chúng ở đâu. Nó sẽ là thú vị với tôi để biết nếu điều này là cố định trong các phiên bản sau của Delphi, nhưng không có gì thú vị hơn, nâng cấp không phải là một lựa chọn vào thời điểm này.

Trên QC, báo cáo gần đây nhất tôi có thể tìm thấy số #91876 tương tự, nhưng điều đó được giải quyết trong XE Delphi.

Cập nhật:

Dựa trên ý kiến ​​AlexSC, với một sửa đổi nhỏ:

... 

     procedure Nested; 
     begin 
     Run(
      procedure 
      begin 
      S := S; 
      end); 
     S := 'Hello, world!'; 
     end; 

... 

làm việc.

Mã máy tạo ra cho

S := 'Hello, world!'; 

trong chương trình thất bại là

ScratchForm.pas.44: S := 'Hello, world!'; 
004BD971 B89CD94B00  mov eax,$004bd99c 
004BD976 894524   mov [ebp+$24],eax 

trong khi phiên bản chính xác là

ScratchForm.pas.45: S := 'Hello, world!'; 
004BD981 B8B0D94B00  mov eax,$004bd9b0 
004BD986 8B5508   mov edx,[ebp+$08] 
004BD989 8B52FC   mov edx,[edx-$04] 
004BD98C 89420C   mov [edx+$0c],eax 

Các mã được tạo trong chương trình thất bại không nhìn thấy rằng S đã được chuyển đến một lớp được tạo bởi trình biên dịch, [ebp+$24] cách các biến cục bộ bên ngoài của các phương thức lồng nhau được truy cập cách các biến cục bộ được truy cập.

+0

Trong các thử nghiệm của tôi, tôi nhận được cảnh báo này "Đơn vị cảnh báo [DCC ]1.pas (45): W1036 Biến '$ frame' có thể chưa được khởi tạo". Vì tôi đã không khai báo bất kỳ biến $ frame nào, tôi giả sử nó được tạo bởi trình biên dịch khi khai báo các giao diện thực hiện các phương thức nặc danh. Cảnh báo cho thấy rằng không phải mọi thứ đã được thực hiện một cách chính xác bởi trình biên dịch, vì vậy nó có vẻ là một lỗi. Thay đổi mã để biến S tuyên bố là chuỗi làm cho vấn đề hiển thị trước đó. Gỡ lỗi cho thấy biến S không được xử lý đúng cách bởi mã được tạo. – AlexSC

+0

@AlexSC Phát hiện "có thể chưa được khởi tạo" nổi tiếng là xấu, có rất nhiều các lỗi tích cực không trỏ đến bất kỳ vấn đề thực sự nào và không ảnh hưởng đến mã được tạo, vì vậy đó là một cảnh báo cần được bỏ qua. Tôi cũng có thể nhận được cảnh báo đó (bao gồm cả biến biên dịch tạo ra '$ frame') trong mã đơn giản hơn, hoạt động chính xác. – hvd

+1

Biên dịch và hoạt động ok trong XE2 –

Trả lời

0

Làm cách nào để biết điều này sẽ gây ra sự cố?

Thật khó để nói vào thời điểm này.
Nếu chúng tôi biết bản chất của bản sửa lỗi trong Delphi XE2, chúng tôi sẽ ở vị trí tốt hơn.
Tất cả những gì bạn có thể làm là không sử dụng các chức năng ẩn danh.
Delphi đã có các biến thủ tục, do đó, nhu cầu về các chức năng ẩn danh sẵn sàng không phải là khủng khiếp.
Xem http://www.deltics.co.nz/blog/posts/48.

Nó sẽ là thú vị với tôi để biết nếu điều này là cố định trong các phiên bản sau này của Delphi

Theo @Sertac Akyuz này đã được cố định trong XE2.

Cá nhân tôi không thích các phương pháp nặc danh và phải cấm mọi người sử dụng chúng trong các dự án Java của tôi bởi vì một tỷ lệ khá lớn cơ sở mã của chúng tôi đang ẩn danh (trình xử lý sự kiện).
Được sử dụng trong điều độ cực đoan Tôi có thể thấy trường hợp sử dụng.
Nhưng ở Delphi, nơi chúng tôi có các biến thủ tục và thủ tục lồng nhau ... Không quá nhiều.

+0

Xin lỗi, nhưng điều này không có gì để trả lời câu hỏi của tôi. "Điều đó không thể biết được rõ ràng." Có thật không? Đó là một lỗi xảy ra đối với một số kết hợp chức năng lồng nhau và chức năng ẩn danh, và ai đó có nhiều kỹ năng hoặc kiến ​​thức hơn tôi chắc chắn sẽ có khả năng mô tả rằng "kết hợp nhất định" chi tiết hơn, đó là những gì tôi yêu cầu. Không cần phải tránh chúng hoàn toàn. – hvd

+1

Tôi có một trường hợp sử dụng, nơi chúng rất khó tránh mà không làm phức tạp mã: một hàm trả về 'tham chiếu đến thủ tục' được chuyển qua và kết thúc bằng mã khác. Đối tượng helper do trình biên dịch tạo ra được giải phóng tự động, vì kiểu 'tham chiếu đến thủ tục' được tính tham chiếu. Với 'thủ tục của đối tượng', tôi sẽ cần phải tự thêm nhiều mã phụ để đảm bảo rằng đối tượng được giải phóng. Cách thay thế thực tế duy nhất là tự định nghĩa một 'giao diện' và có lớp trợ giúp thực hiện nó. Java có bộ sưu tập rác, vì vậy nó không phải là một vấn đề ở đó. – hvd

1

Nếu không nhìn thấy toàn bộ mã Assembler cho toàn bộ (thủ tục kiểm tra) và chỉ giả định trên đoạn trích bạn đã đăng, có thể là trên không có đoạn trích chỉ một con trỏ đã được di chuyển ở trên phiên bản chính xác có một số dữ liệu được di chuyển quá . Vì vậy, có vẻ như S: = S hoặc S: = '' làm cho trình biên dịch tạo ra một tham chiếu bởi chính nó và thậm chí có thể cấp phát một số bộ nhớ, điều này sẽ giải thích tại sao nó hoạt động sau đó. Tôi cũng giả định đó là lý do tại sao Vi phạm Truy cập xảy ra mà không có S: = S hoặc S: = '', bởi vì nếu không có Bộ nhớ được cấp cho Chuỗi (nhớ bạn chỉ khai báo S: PChar) thì Vi phạm Truy cập được nâng lên vì bộ nhớ không được cấp phát đã được truy cập.

Nếu bạn chỉ cần khai báo S: String thay vào đó, điều này có thể sẽ không xảy ra.

Bổ sung sau khi mở rộng Nhận xét:

PChar chỉ là một con trỏ đến cấu trúc dữ liệu, phải tồn tại. Một vấn đề phổ biến khác với PChar là khai báo biến cục bộ và sau đó chuyển PChar đến biến đó cho các Procs khác, bởi vì điều xảy ra là biến cục bộ được giải phóng sau khi thường trình kết thúc, nhưng PChar sẽ vẫn trỏ đến nó. Truy cập Vi phạm khi truy cập.

Khả năng duy nhất tồn tại trên mỗi Tài liệu là tuyên bố một cái gì đó như vậy const S: PChar = 'Hello, world!' hoạt động này vì Trình biên dịch có thể giải quyết một Địa chỉ tương đối với nó. Nhưng điều này chỉ hoạt động cho các hằng số và không cho các biến như trong ví dụ trên. Làm như trong ví dụ trên cần lưu trữ để được phân bổ cho chuỗi ký tự mà PChar sau đó trỏ đến như S:String; P:PChar; S:='Hello, world!'; P:=PChar(S); hoặc tương tự.

Nếu vẫn không khai báo chuỗi hoặc số nguyên thì biến có thể biến mất ở đâu đó dọc hoặc đột nhiên không hiển thị trong proc, nhưng đó sẽ là một vấn đề khác không liên quan gì đến vấn đề PChar hiện có đã giải thích.

Kết luận cuối cùng:

Có thể làm S:PChar; S:='Hello, world!' nhưng trình biên dịch sau đó chỉ cần phân bổ nó như là một hằng số ở địa phương hay toàn cầu như const S: PChar = 'Hello, world!' không được lưu vào thực thi, một giây S := 'Hello' sau đó tạo ra một số khác mà cũng được lưu vào Có thể thực thi và cứ thế - nhưng S thì chỉ có điểm cuối cùng được phân bổ, tất cả các điểm khác vẫn còn trong Tệp thực thi nhưng không thể truy cập được nữa mà không biết Vị trí chính xác, vì chỉ S chỉ điểm đến điểm cuối cùng được phân bổ.

Vì vậy, tùy thuộc vào số nào là S điểm cuối cùng hoặc là Hello, world! hoặc Hello.Trên ví dụ trên tôi chỉ có thể đoán cái nào là cuối cùng và ai biết có lẽ Trình biên dịch chỉ có thể đoán và tùy thuộc vào tối ưu hóa và các yếu tố không thể đoán trước khác S đột nhiên có thể trỏ đến Mem chưa được phân bổ thay vì lần cuối cùng theo Thời gian Showmessage(S) sau đó tăng Vi phạm Truy cập.

+0

Tôi đảm bảo với bạn, tôi biết cách con trỏ hoạt động. Tôi biết rằng các biến con trỏ không chứa dữ liệu được trỏ tới. Nhưng các chuỗi ký tự không được tính lại, chúng tồn tại trực tiếp trong hình ảnh thực thi và không có dữ liệu nào cần được sao chép, chỉ là con trỏ. Tôi không sử dụng 'string', bởi vì các kiểu không được quản lý như' PChar' tạo ra một assembly đơn giản hơn, giúp việc kiểm tra dễ dàng hơn của assembly được tạo ra. Nhưng nó không thành công khi sử dụng 'chuỗi' là tốt, và trên thực tế, bạn có thể thấy vấn đề ngay cả khi bạn sử dụng một biến kiểu' Integer'. – hvd

+0

Có thể nó cũng không thành công, nhưng bạn vẫn không thể chỉ đơn giản là gán 'Hello, world!' với một PChar như vậy bởi vì nó chỉ là một con trỏ đến dữ liệu, nhưng trên ví dụ trên không có dữ liệu mà nó trỏ đến. Nếu nó vẫn không thành công với việc khai báo 'String' hoặc' Integer' thì có lẽ biến sẽ biến mất ở đâu đó dọc theo hoặc đột nhiên không thể nhìn thấy nữa cho một proc. Một nguồn ASM đầy đủ của toàn bộ Proc sẽ giúp tiết lộ những gì thực sự xảy ra khác bước theo dõi máng và xem mình nơi biến biến mất hoặc chính xác nơi vi phạm truy cập xảy ra để thu hẹp nó xuống. – Amenominakanushi

+0

Như tôi đã nói, các chuỗi ký tự không được tính lại, chúng không biến mất. Bạn có thể giữ một con trỏ đến một chuỗi chữ mà không cần phải lo lắng. Đó không chỉ là chi tiết triển khai, đó là một lời hứa rõ ràng được đưa ra trong tài liệu an toàn để dựa vào. [Đọc nó ở đây.] (Http://docwiki.embarcadero.com/RADStudio/XE/en/Internal_Data_Formats#Long_String_Types) Để kiểm tra hội đồng được tạo ra, tôi đã làm điều đó rồi và tôi đã trình bày các chi tiết liên quan trong câu hỏi. Biến không biến mất và tôi đã tìm ra chính xác nơi xảy ra vi phạm truy cập. – hvd

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