Есть ли компилятор C++, который может выдавать предупреждение за висячую ссылку?

Учитывая следующий код, где x это висящий const reference исчезнувшему объекту, и, следовательно, неопределенное поведение.

auto get_vec() { return std::vector<int>{1,2,3,4,5}; }
const auto& x = get_vec().back();

Похоже, что ни GCC 7.3, ни Clang 6.0, ни MSVC не могут выдавать предупреждение, даже если все предупреждения включены. Кто-нибудь знает, есть ли способ выдать предупреждение в этих случаях? Есть ли разница между const auto& а также auto&& в этих случаях?

Обратите внимание, если back() возвратил бы значение, это не было бы неопределенным поведением, поскольку временный объект x времени жизни расширен до функции scoop.

Длинная история: у меня есть кодовая база, где const auto& используется в качестве способа инициализации переменных по умолчанию, и по какой-то странной причине эти случаи выполняются корректно с использованием MSVC, но при компиляции с Clang для android каждое вхождение приводит к неправильно назначенному значению. На данный момент решение, кажется, исследует каждый const auto& во всей кодовой базе. Кроме того, во многих случаях const auto& относится к тяжелому объекту, возвращенному по ссылке, поэтому просто удаляя & это не решение

Еще одна вещь, я несу ответственность за неправильное использование const auto& :)

4 ответа

Решение

Единственное, что я могу придумать сейчас, это использовать CLANG с -fsanitize=address. Но, конечно, это поможет только во время выполнения, но тогда вы получите что-то хорошее, как это:

==102554==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000020 at pc 0x00000050db71 bp 0x7ffdd3a5b770 sp 0x7ffdd3a5b768
READ of size 4 at 0x603000000020 thread T0
    #0 0x50db70 in main (/home/user/testDang+0x50db70)
    #1 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
    #2 0x41a019 in _start (/home/user/testDang+0x41a019)

0x603000000020 is located 16 bytes inside of 20-byte region [0x603000000010,0x603000000024)
freed by thread T0 here:
    #0 0x50a290 in operator delete(void*) (/home/user/testDang+0x50a290)
    #1 0x50eccf in __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (/home/user/testDang+0x50eccf)
    #2 0x50ec9f in std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>&, int*, unsigned long) (/home/user/testDang+0x50ec9f)
    #3 0x50ec2a in std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (/home/user/testDang+0x50ec2a)
    #4 0x50e577 in std::_Vector_base<int, std::allocator<int> >::~_Vector_base() (/home/user/testDang+0x50e577)
    #5 0x50e210 in std::vector<int, std::allocator<int> >::~vector() (/home/user/testDang+0x50e210)
    #6 0x50db16 in main (/home/user/testDang+0x50db16)
    #7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)

previously allocated by thread T0 here:
    #0 0x509590 in operator new(unsigned long) (/home/user/testDang+0x509590)
    #1 0x50e9ab in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/home/user/testDang+0x50e9ab)
    #2 0x50e94b in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/home/user/testDang+0x50e94b)
    #3 0x50e872 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/home/user/testDang+0x50e872)
    #4 0x50e2ff in void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (/home/user/testDang+0x50e2ff)
    #5 0x50deb7 in std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (/home/user/testDang+0x50deb7)
    #6 0x50dafb in main (/home/user/testDang+0x50dafb)
    #7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)

SUMMARY: AddressSanitizer: heap-use-after-free (/home/user/testDang+0x50db70) in main
Shadow bytes around the buggy address:
  0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa fd fd[fd]fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

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

Почти наверняка нет способа предупредить об этом. Компилятор не знает, возвращен ли ссылочный объект back() переживет линию или нет, и если это произойдет, проблем не возникнет (хотя мне было бы трудно представить реалистичную ситуацию, когда нестатическая функция-член, вызываемая для временного объекта, возвращает ссылку на объект, который переживает временный объект).

Похоже, тот, кто написал этот код, прочитал о самом важном const и извлек из него совершенно неверный урок.

У меня есть база кода, где const auto& используется как способ инициализации переменных по умолчанию

Уч.:(

по какой-то странной причине эти случаи выполняются корректно с использованием MSVC, но при компиляции с Clang для android каждое вхождение приводит к неправильно назначенному значению

UB - это UB innit.

На данный момент решение, похоже, исследует все const auto& во всей базе кода.

Да.

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

Если бы он всегда имел доступ к полному определению каждой функции, он мог бы предупредить вас в некоторых случаях (и такие инструменты анализа, как -fsanitize=address будет делать все возможное с этим), но не существует общего решения для компилятора для обнаружения висячих ссылок во время выполнения.

Также поздравления с выплатой зарплаты, которую вы можете получить сейчас, когда виновные сотрудники (автор и рецензент) были уволены, верно?:)

Очевидно, что для приведенного выше примера можно написать что-то вроде:

std::vector<int> xx{1,2,3,4,5};
const auto& x = xx.back();

Не имеет смысла создавать целый вектор, чтобы сохранить только его последний элемент. И если у вас есть выражение, подобное приведенному выше, и вы хотите использовать одно выражение, то вы почти никогда не должны использовать auto & начать с.

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

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

Как правило, я бы сказал, что вы не должны использовать auto & если вы не уверены, что хотите ссылку на возвращаемый товар. Самый распространенный случай, когда я бы использовал auto & или же const auto & будет для цикла на основе диапазона. Например, с указанным выше вектором xxЯ бы вообще написал:

for (auto & item : xx) …

если только я не знаю, что он возвращает тривиальные типы.

Другие вопросы по тегам