Обнаружение неправильного использования delete [] и delete во время компиляции

19

Я хотел бы знать, возможно ли обнаружить deleteошибку, прокомментированную ниже во время компиляции? Особенно мне хотелось бы услышать о компиляторе g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
источник
7
Вы не должны звонить удалить вручную в любом случае.
Мартин Йорк
9
@LokiAstari Вы действительно думаете, что этот комментарий был полезным?
Джеймс
5
@ Джеймс: Да. Ключ «вручную».
Мартин Йорк,
Иногда, чтобы выполнить это, потребовалось бы переписать много унаследованного кода
Ник Кейли,
Используйте std::unique_ptr<ClassTypeA[]>и тогда вам не нужно.
user253751

Ответы:

6

Как правило, компилятор не может обнаружить такие ошибки. Пример: предположим, что конструктор для некоторого класса выделяет некоторый элемент данных, используя new TypeName[], но деструктор ошибочно использует deleteвместо delete[]. Если конструктор и деструктор определены в отдельных единицах компиляции, как компилятор должен знать при компиляции файла, который определяет деструктор, что использование несовместимо с тем, что используется в отдельно скомпилированном файле, который определяет конструктор?

Что касается компиляторов GNU, это не так. Как отмечено выше, это не может сделать это в общем случае. Компилятору не нужно обнаруживать такие несоответствующие новые / удалять ошибки, потому что это неопределенное поведение. UB - это карта компилятора, выпущенная «без выхода из тюрьмы».

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

Дэвид Хаммен
источник
Я использовал инструмент статического анализа Parasoft, в котором определенно есть правило для этого конкретного сценария. Он работает на всех файлах в конкретном проекте (если он был настроен правильно). При этом я не уверен, насколько хорошо он справляется со сценариями, такими как комментарий Пита Киркхема на ответ Килиана Фота.
Velociraptors
28

Вы можете использовать соответствующие классы RAII для delete. Это единственный безопасный способ сделать это, и эта ошибка - лишь одна из очень, очень многих, с которыми вы столкнетесь, называя deleteсебя.

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

Изменить: «Что если вы проверяете код и не можете его изменить?» Вы трахались

DeadMG
источник
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
@ Пит Киркхэм, @ Мейсон Уилер: Но, тем не менее, среда выполнения должна видеть, сколько объектов хранится по указанному адресу 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 работает не на всех платформах, и не всегда возможно или невозможно воспроизвести все ситуации во время выполнения с помощью инструмента.

Kaz
источник
Вы говорите, что этот тривиальный случай может быть обнаружен во время компиляции. Не могли бы вы сказать мне, какую команду компиляции вы используете для этого?
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, тесты и одну найденную ошибку.

Это было добавлено в clang с reviews.llvm.org/D4661 - «Обнаружить несоответствие« новых »и« удалить »использования» .

Тесты находятся в тесте / Анализ / MismatchedDeallocator-checker-test.mm

Я нашел эту открытую ошибку - bugs.llvm.org/show_bug.cgi?id=24819

thatsafunnyname
источник
Никто не сомневается, что вы можете найти статический анализатор, который обнаруживает одно конкретное неправильное использование, вместо этого тот, который обнаруживает все неправильное использование (и, мы надеемся, ошибается, что нет правильного использования)
Caleth