การตรวจจับการลบของการลบ [] กับการลบในเวลารวบรวม


19

ฉันต้องการทราบว่าเป็นไปได้หรือไม่ที่จะตรวจพบdeleteข้อผิดพลาดที่แสดงความคิดเห็นด้านล่างในเวลารวบรวม? โดยเฉพาะอย่างยิ่งฉันต้องการทราบเกี่ยวกับคอมไพเลอร์ g ++

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  

7
คุณไม่ควรโทรลบด้วยตนเอง
Martin York

9
@LokiAstari คุณคิดว่าความคิดเห็นนั้นมีประโยชน์หรือไม่?
James

5
@James: ใช่ กุญแจสำคัญคือ "ด้วยตนเอง"
Martin York

บางครั้งการปฏิบัติตามนี้อาจเกี่ยวข้องกับการเขียนรหัสดั้งเดิมจำนวนมาก
Nick Keighley

ใช้std::unique_ptr<ClassTypeA[]>แล้วคุณไม่จำเป็นต้อง
user253751

คำตอบ:


6

โดยทั่วไปคอมไพเลอร์ไม่สามารถตรวจพบข้อผิดพลาดดังกล่าว ตัวอย่าง: สมมติว่าสร้างสำหรับบางจัดสรรระดับบางข้อมูลสมาชิกใช้new TypeName[]แต่ destructor สมควรใช้แทนdelete delete[]หากตัวสร้างและ destructor ถูกกำหนดในหน่วยการคอมไพล์แยกต่างหากคอมไพเลอร์รู้อย่างไรเมื่อรวบรวมไฟล์ที่กำหนด destructor ที่การใช้งานไม่สอดคล้องกับในไฟล์คอมไพล์แยกต่างหากที่กำหนดตัวสร้าง?

เกี่ยวกับคอมไพเลอร์ GNU มันไม่ได้ ตามที่ระบุไว้ข้างต้นไม่สามารถทำได้ในกรณีทั่วไป คอมไพเลอร์ไม่จำเป็นต้องตรวจจับข้อผิดพลาดใหม่ / ลบที่ไม่ตรงกันเช่นนี้เนื่องจากพฤติกรรมที่ไม่ได้กำหนด UB เป็นการ์ด "ออกจากคุก" ของผู้จำหน่ายคอมไพเลอร์

เครื่องมือต่าง ๆ เช่น valgrind สามารถและตรวจจับชนิดใหม่ / ลบไม่ตรงกันเหล่านี้ แต่ทำได้ที่รันไทม์ อาจมีเครื่องมือวิเคราะห์แบบสแตติกที่ดูไฟล์ต้นฉบับทั้งหมดที่ในที่สุดจะถูกรวบรวมเพื่อสร้างไฟล์ที่เรียกใช้งานได้ แต่ฉันไม่มีเครื่องมือวิเคราะห์แบบสแตติกใด ๆ ที่ตรวจพบข้อผิดพลาดชนิดนี้


ฉันใช้เครื่องมือวิเคราะห์แบบคงที่ที่เรียกว่าParasoftซึ่งมีกฎสำหรับสถานการณ์เฉพาะนี้อย่างแน่นอน มันจะทำงานกับไฟล์ทั้งหมดในโครงการเฉพาะ (ถ้ามันถูกกำหนดค่าอย่างถูกต้อง) ที่ถูกกล่าวว่าฉันไม่แน่ใจว่ามันจัดการสถานการณ์เช่นความคิดเห็นของ Pete Kirkham ในคำตอบของ Kilian Foth ได้ดีเพียงใด
Velociraptors

28

คุณสามารถใช้เรียน RAII deleteที่เหมาะสมในการ นี้เป็นเพียงวิธีที่ปลอดภัยในการทำมันและข้อผิดพลาดนี้เป็นเพียงหนึ่งในมากหลายอย่างที่คุณจะพบการเรียกdeleteตัวเอง

ใช้คลาสเพื่อจัดการทรัพยากรอายุการใช้งานแบบไดนามิกและระบบชนิดจะบังคับใช้การทำลายทรัพยากรที่ถูกต้อง

แก้ไข: "จะเกิดอะไรขึ้นถ้าคุณกำลังตรวจสอบรหัสและไม่สามารถเปลี่ยนได้" คุณกำลังระยำ


18
-1 เพราะสิ่งนี้ไม่ตอบคำถาม
Mason Wheeler

2
วิธีเดียวที่จะตรวจจับความไม่ตรงกันคือการใช้ระบบพิมพ์ซึ่งเกี่ยวข้องกับการใช้คลาส RAII
DeadMG

9
... ที่ไม่สมเหตุสมผล การใช้คลาส RAII - กลไกไทม์ - เกี่ยวข้องกับข้อมูลระบบสแตติกชนิดใดที่คอมไพเลอร์รู้เกี่ยวกับเวลาคอมไพล์?
Mason Wheeler

6
@MasonWheeler ดู boost :: shared_ptr และ boost :: shared_array เป็นตัวอย่าง ทำลาย shared_ptr ลบวัตถุทำลาย shared_array ลบ [] s อาเรย์ คุณไม่สามารถกำหนด shared_array ให้กับ shared_ptr ดังนั้นตราบใดที่คุณไม่ได้สร้าง shared_ptr ด้วยอาร์เรย์ในตอนแรกระบบจะป้องกันการลบที่ไม่ถูกต้อง
Pete Kirkham

4
โดยปกติแล้วคำตอบเช่นนี้น่ารังเกียจกว่ามีประโยชน์ อย่างไรก็ตามในกรณีนี้มันเป็นเรื่องจริง เขากำลังมองหาคอมไพเลอร์บังคับใช้ของข้อผิดพลาดทั่วไปและการใช้ RAII อย่างถูกต้องป้องกันรูปแบบของข้อผิดพลาดนี้จึงให้สิ่งที่เขาต้องการ +1
riwalk

10

ข้อผิดพลาดนี้ - ใช่ ข้อผิดพลาดประเภทนี้โดยทั่วไป: น่าเสียดายที่ไม่มี! สิ่งนั้นจะเกี่ยวข้องกับการทำนายการไหลของการดำเนินการโดยที่ไม่ได้ดำเนินการจริงและเป็นไปไม่ได้สำหรับโปรแกรมที่กำหนดเอง (นั่นเป็นสาเหตุที่คอมไพเลอร์ส่วนใหญ่ไม่ได้พยายามตรวจหากรณีง่าย ๆ เช่นตัวอย่างของคุณ)

ดังนั้นคำตอบของ DeadMG จึงเป็นคำตอบที่เหมาะสม: อย่าพยายามทำให้ถูกต้องโดยให้ความสนใจ - ความสนใจของมนุษย์นั้นผิดพลาดได้ ใช้วิธีการที่ให้มาด้วยภาษาและให้คอมพิวเตอร์ใส่ใจ


สิ่งนี้ต้องการทำนายการไหลของการดำเนินการอย่างไร นี่ดูเหมือนความรู้แบบสแตติกหมดจดที่รวบรวมมาให้ฉัน ระบบชนิดของคอมไพเลอร์รู้ว่าอาเรย์คืออะไรและอะไรไม่
Mason Wheeler

แม้ในที่ที่มีการปลดเปลื้อง? ขออภัยถ้าฉันทำผิดฉันจะลบคำตอบ
Kilian Foth

12
@MasonWheeler ประเภทคงที่ของ abc_ptr คือClassTypeA*เพื่อให้คุณสามารถแทรกบรรทัดระหว่างใหม่และลบif ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;ไม่มีอะไรในระบบประเภทคงที่จะแสดงว่าabc_ptrจุดไปยังอาร์เรย์หรือวัตถุแบบไดนามิกหรือวิธีการบางส่วนในวัตถุหรืออาร์เรย์อื่น
Pete Kirkham

...โอ้ใช่. ฉันคุ้นเคยกับการทำงานกับภาษาที่มีประเภทอาเรย์จริง ๆ อยู่เสมอซึ่งฉันก็ยังลืมไปว่ามันจบสิ้นลงอย่างไรในซีแลนด์ :(
Mason Wheeler

1
@Pete Kirkham, @Mason Wheeler: แต่ถึงกระนั้นรันไทม์ควรดูว่ามีวัตถุจำนวนเท่าใดที่ถูกเก็บไว้ตามที่อยู่ที่ชี้ไปabc_ptrมิฉะนั้นจะสามารถจัดสรรหน่วยความจำในปริมาณที่เหมาะสมได้อย่างไร? ดังนั้นรันไทม์จึงรู้ว่าต้องจัดสรรคืนวัตถุจำนวนเท่าใด
Giorgio

4

กรณีเล็ก ๆ น้อย ๆ ที่คุณแสดงสามารถตรวจจับได้ในเวลารวบรวมเพราะการสร้างอินสแตนซ์และการทำลายของวัตถุอยู่ในขอบเขตเดียวกัน โดยทั่วไปการลบจะไม่อยู่ในขอบเขตเดียวกันหรือแม้แต่ไฟล์ต้นฉบับเดียวกันกับการสร้างอินสแตนซ์ และชนิดของตัวชี้ C ++ ไม่ได้มีข้อมูลเกี่ยวกับว่ามันอ้างอิงวัตถุเดียวของประเภทหรืออาเรย์ให้นับประสาโครงการจัดสรร ดังนั้นจึงเป็นไปไม่ได้ที่จะวินิจฉัยปัญหานี้ในเวลารวบรวมโดยทั่วไป

ทำไมไม่วินิจฉัยกรณีพิเศษที่เป็นไปได้?

ใน C ++ มีเครื่องมือสำหรับจัดการกับการรั่วไหลของทรัพยากรแบบไดนามิกที่เชื่อมโยงกับขอบเขตอยู่แล้วนั่นคือตัวชี้อัจฉริยะและอาร์เรย์ระดับสูงกว่า ( std::vector)

แม้ว่าคุณจะใช้deleteรสชาติที่ถูกต้องรหัสของคุณก็ยังไม่ปลอดภัยยกเว้น หากรหัสระหว่างnew[]และdelete[]สิ้นสุดโดยการออกแบบไดนามิกการลบจะไม่ดำเนินการ

เท่าที่การตรวจหารันไทม์Valgrindเครื่องมือทำงานได้ดีในการตรวจจับสิ่งนี้ในเวลาทำงาน ดู:

==26781== Command: ./a.out
==26781==
==26781== Mismatched free() / delete / delete []
==26781==    at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048498: main (in /home/kaz/test/a.out)
==26781==  Address 0x4324028 is 0 bytes inside a block of size 80 alloc'd
==26781==    at 0x402B454: operator new[](unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048488: main (in /home/kaz/test/a.out)

แน่นอนว่า Valgrind ไม่ได้ทำงานบนทุกแพลตฟอร์มและอาจไม่สามารถใช้งานได้จริงหรือเป็นไปได้ที่จะสร้างสถานการณ์การใช้งานทั้งหมดภายใต้เครื่องมือ


คุณบอกว่ากรณีเล็ก ๆ น้อย ๆ นี้สามารถตรวจพบได้ในเวลารวบรวม คุณช่วยบอกฉันหน่อยได้ไหมว่าคุณใช้คำสั่งการรวบรวมเพื่อให้บรรลุผลนั้น
SebGR

"สามารถตรวจพบได้ในเวลาคอมไพล์" นี่หมายความว่ามันง่ายที่จะนำไปใช้ในคอมไพเลอร์ไม่ใช่ g ++ ที่มี คอมไพเลอร์มีอายุการใช้งานทั้งหมดของตัวระบุในการเข้าใจเมื่อประมวลผลขอบเขตนั้นและสามารถเผยแพร่ข้อมูลการจัดสรรเช่นเดียวกับแอตทริบิวต์ความหมายที่เชื่อมโยงกับไวยากรณ์
Kaz

-3

ตัวอย่างเล็กน้อยของการตรวจจับที่รวบรวมเวลา / การวิเคราะห์แบบคงที่เวลา:

บนโฮสต์ RHEL7 ด้วย cppcheck 1.77 and 1.49

> cat test.cc
#include <memory>
int main(){char* buf = new char[10];delete buf;}

http://cppcheck.sourceforge.net/

> cppcheck -x c++ test.cc
Checking test.cc ...
[test.cc:2]: (error) Mismatching allocation and deallocation: buf

ด้วยclang++ 3.7.1บน RHEL7

> clang++ --analyze -x c++ test.cc
test.cc:2:37: warning: Memory allocated by 'new[]' should be deallocated by
'delete[]', not 'delete'
int main(){char* buf = new char[10];delete buf;}
                                    ^~~~~~~~~~
1 warning generated.

Clang Static Analyzer ยังสามารถตรวจจับได้เมื่อstd::unique_ptrไม่ผ่าน<char[]>

> cat test2.cc
#include <memory>
int main(){std::unique_ptr<char> buf(new char[10]);}

https://clang-analyzer.llvm.org/

> clang++ --analyze -x c++ -std=c++11 test2.cc
In file included from test2.cc:1:
In file included from /opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/memory:81:
/opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/bits/unique_ptr.h:76:2: 
warning: Memory allocated by
      'new[]' should be deallocated by 'delete[]', not 'delete'
        delete __ptr;
        ^~~~~~~~~~~~
1 warning generated.

อัปเดตด้านล่างด้วยลิงก์ไปยังงานที่เพิ่มสิ่งนี้ลงในเสียงดังกราว, การทดสอบและข้อบกพร่องหนึ่งข้อที่ฉันพบ

สิ่งนี้ถูกบันทึกอยู่ใน clang with reviews.llvm.org/D4661 - "ตรวจหา mismatching 'ใหม่' และการใช้ 'ลบ'"

การทดสอบอยู่ในการทดสอบ / วิเคราะห์ / MismatchedDeallocator-checker-test.mm

ฉันพบข้อบกพร่องที่เปิดนี้ - bugs.llvm.org/show_bug.cgi?id=24819


ไม่มีใครสงสัยว่าคุณสามารถหาวิเคราะห์แบบคงที่ตรวจพบหนึ่งที่เฉพาะเจาะจงการใช้งานที่ไม่ถูกต้องแทนหนึ่งที่ตรวจพบทั้งหมดประเพณีผิด (และหวังว่าข้อผิดพลาดที่ไม่มีสิทธิประเพณี)
Caleth
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.