Виявлення неправильного використання delete [] проти видалення під час компіляції


19

Я хотів би знати, чи можна виявити deleteпомилку, прокоментовану нижче, під час компіляції? Особливо, я хотів би почути про компілятор g ++.

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

7
Видаляти видалення вручну не слід.
Мартін Йорк

9
@LokiAstari Ви насправді вважаєте, що коментар був корисним?
Джеймс

5
@James: Так. Ключ "вручну".
Мартін Йорк

Іноді для дотримання цього знадобиться переписати багато застарілого коду
Nick Keighley

Користуйтеся std::unique_ptr<ClassTypeA[]>і тоді вам не потрібно.
користувач253751

Відповіді:


6

Взагалі компілятор не може виявити подібні помилки. Приклад: Припустимо, конструктор для деякого класу виділяє деякий член даних, використовуючи new TypeName[], але деструктор помилково використовує deleteзамість delete[]. Якщо конструктор і деструктор визначені в окремих блоках компіляції, як компілятор повинен знати при компілюванні файлу, який визначає деструктор, що використання не відповідає використанню в окремо складеному файлі, що визначає конструктор?

Що стосується компіляторів GNU, то це не так. Як зазначалося вище, це не може зробити в загальному випадку. Компілятору не потрібно виявляти такі невідповідні нові помилки / нові видалення, оскільки це невизначена поведінка. UB - картка постачальника компілятора, "вийти з в'язниці".

Такі інструменти, як valgrind, можуть і виявляти подібні нові / видалення невідповідностей, але робити це під час виконання. Можливо, є інструмент статичного аналізу, який розглядає всі вихідні файли, які в кінцевому підсумку будуть скомпільовані для формування виконуваного файлу, але я не маю жодного такого інструмента статичного аналізу, який би виявляв подібні помилки.


Я використовував інструмент статичного аналізу під назвою Parasoft, який безумовно має правило для цього конкретного сценарію. Він працює у всіх файлах певного проекту (якщо він налаштований правильно). Незважаючи на це, я не впевнений, наскільки добре він справляється з такими сценаріями, як коментар Піта Кіркема до відповіді Кіліана Фота.
Велоцираптори

28

Ви можете використовувати відповідні класи RAII для delete. Це єдиний безпечний спосіб зробити це, і ця помилка є лише однією з дуже, дуже багатьох, з якими ви зіткнетесь, називаючи deleteсебе.

Завжди використовуйте класи для управління динамічними ресурсами протягом усього життя, і система типів забезпечить правильне знищення ресурсів.

Редагувати: "Що робити, якщо ви перевіряєте код і не можете його змінити?" Ви трахнули.


18
-1 тому, що це фактично не відповідає на питання.
Мейсон Уілер

2
Єдиний спосіб виявити невідповідність - використовувати систему типів, яка передбачає використання класів RAII.
DeadMG

9
... що має ще менше сенсу. Що стосується використання класів RAII - механізму виконання - пов’язаного із системною інформацією статичного типу, про яку компілятор знає під час компіляції?
Мейсон Уілер

6
@MasonWheeler див. Приклади boost :: shared_ptr та boost :: shared_array. Знищення shared_ptr видаляє об'єкт, знищуючи масив shared_array delete [] s. Ви не можете призначити shared_array для shared_ptr, так що - поки ви не побудуєте shared_ptr з масивом в першу чергу - система типів запобігає використанню неправильного видалення.
Піт Кіркхем

4
Зазвичай така відповідь є більш неприємною, ніж корисною. Однак у цьому випадку це насправді так. Він шукає виконання компілятора поширеної помилки, а використання RAII належним чином запобігає цьому стилю помилок, тим самим надаючи йому саме те, що він хоче. +1
річорк

10

Ця конкретна помилка - так. Такого роду помилка взагалі: на жаль, ні! Це передбачало б передбачити потік виконання без його фактичного виконання, а це неможливо для довільних програм. (Ось чому більшість компіляторів навіть не намагаються виявити такі прості випадки, як ваш приклад.)

Тому відповідь DeadMG відповідна: не намагайтеся виправити це правильно, звертаючи увагу - людська увага помилкова. Використовуйте мовні засоби та нехай комп'ютер зверне увагу.


Як це вимагає передбачити хід виконання? Це виглядає як суто статичне, час збирання для мене; система типу компілятора знає, що таке масив, а що ні.
Мейсон Уілер

Навіть за наявності ролей? Вибачте, якщо я помилився, я видалю відповідь.
Кіліан Фот

12
@MasonWheeler статичний тип abc_ptr ClassTypeA*такий, що ви можете вставити рядок між новим і видалити if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Ніщо в системі статичного типу не показує, чи abc_ptrвказує на масив чи на динамічний об'єкт чи частину шляху на інший об'єкт чи масив.
Піт Кіркхем

...о, так. Я настільки звик працювати з мовами з реальними типами масивів, що я постійно забуваю, наскільки це накручено в C-land. :(
Мейсон Уілер

1
@ Pete Kirkham, @ Mason Wheeler: Але все-таки під час виконання слід побачити, скільки об’єктів зберігається за адресою, на яку вказується abc_ptr, інакше як би можна було розмістити потрібний об'єм пам'яті? Тож час виконання знає, скільки об’єктів має бути розміщено.
Джорджіо

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 ++ має його. Компілятор має весь термін експлуатації ідентифікатора, який розуміє при обробці цього сфери, і може поширювати інформацію про розподіл як семантичний атрибут, прив'язаний до синтаксису.
Каз

-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.1RHEL7

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

Статичний аналізатор Кланг також може виявити, коли 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 з reviews.llvm.org/D4661 - "Виявити невідповідність" нового "та" видалити "використання" .

Тести проводяться в тесті / Аналіз / НевідповідністьDeallocator-checker-test.mm

Я знайшов цю відкриту помилку - bugs.llvm.org/show_bug.cgi?id=24819


Ніхто не сумнівається, що ви можете знайти статичний аналізатор, який виявляє одне конкретне неправильне використання, замість цього той, який виявляє всі неправильні звичаї (і, сподіваємось, помилки немає правильних звичаїв)
Caleth
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.