Комментарии 44
великодушный пожизненный диктаторЧто ни говорите, а вдумчивость во взгляде и стать приходят только с возрастом, достаточно одного
![«великодушный пожизненный диктатор»](https://habrastorage.org/getpro/habr/comment_images/e44/210/f1f/e44210f1f55138319af7ddd5038fd1fd.jpg)
![Уолтер Брайт](https://habrastorage.org/getpro/habr/comment_images/ffd/0da/c4d/ffd0dac4d35d4b346c29f06022c60845.jpg)
![Гвидо ван Россум](https://habrastorage.org/getpro/habr/comment_images/260/d75/b36/260d75b36ad8699b71e03c7f5cad309f.jpg)
![Линус Торвальдс](https://habrastorage.org/getpro/habr/comment_images/1ec/95d/4ce/1ec95d4ce30264ad4c17d034cd3d71b7.jpg)
Для повышения образованности.
У Яндекса отличный поиск по картинкам: https://yandex.ru/images/touch/search?cbir_id=2948381%2FjYGJ_BBWNkon5zthK--4JA&cbird=3&rpt=imageview
$ gcc -fsanitize=address -fsanitize=undefined test.c
$ ./a.out
=================================================================
==2193597==ERROR: AddressSanitizer: global-buffer-overflow on address 0x556c25d33128 at pc 0x556c25d30279 bp 0x7ffd00ade590 sp 0x7ffd00ade580
READ of size 4 at 0x556c25d33128 thread T0
#0 0x556c25d30278 in sumArray (/tmp/a.out+0x1278)
#1 0x556c25d302e6 in main (/tmp/a.out+0x12e6)
#2 0x7f64a6712cfa in __libc_start_main ../csu/libc-start.c:308
#3 0x556c25d300f9 in _start (/tmp/a.out+0x10f9)
0x556c25d33128 is located 0 bytes to the right of global variable 'values' defined in 'test.c:14:16' (0x556c25d33100) of size 40
0x556c25d33128 is located 56 bytes to the left of global variable '*.Lubsan_data1' defined in 'test.c' (0x556c25d33160) of size 16
SUMMARY: AddressSanitizer: global-buffer-overflow (/tmp/a.out+0x1278) in sumArray
Shadow bytes around the buggy address:
0x0aae04b9e5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aae04b9e5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aae04b9e5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aae04b9e600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aae04b9e610: 00 02 f9 f9 f9 f9 f9 f9 00 02 f9 f9 f9 f9 f9 f9
=>0x0aae04b9e620: 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9 00 00 f9 f9
0x0aae04b9e630: f9 f9 f9 f9 00 00 00 00 f9 f9 f9 f9 00 00 00 f9
0x0aae04b9e640: f9 f9 f9 f9 00 00 00 f9 f9 f9 f9 f9 00 00 00 00
0x0aae04b9e650: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 f9 f9 f9 f9
0x0aae04b9e660: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0aae04b9e670: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
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
Shadow gap: cc
==2193597==ABORTING
Unit-тесты с ASan'ом помогут избежать большинства подобных ошибок, и изучать другой язык не понадобится.
Проще использовать range for или другими словами for each синтаксис.
С тем, что в высокоуровневых языках можно гарантировать, что программа никогда не крашится при ошибках какого-то рода (или при любых ошибках) — с этим никто не спорит. Но чуда все-таки нет, это может достигаться только добавлением проверок во время выполнения… которые никто не запрещает делать и в низкоуровневых языках (только там их надо делать руками), и логическую ошибку в программе это не исправляет (только делает слегка проще ее обнаружение).
P.S. Чтобы прояснить — я ведь вовсе не против использования высокоуровневых языков, и совсем не отрицаю, что они действительно упрощают процесс программирования. Все, что я хотел сказать — использование такого языка не является «серебряной пулей», которая магически решит все проблемы, следить за логикой использования индексов в программе все равно придется. И тесты для этого все равно писать надо, никуда не деться.
Да, это именно то, о чем мы спорим — с моей точки зрения, в общем случае компилятор просто не в состоянии «следить за логикой», и никто не в состоянии, эта задача алгоритмически неразрешима. Если размер массива и/или индекс определяется динамически по некоторому алгоритму, то в принципе невозможно гарантировать отсутствие ошибок индексации — все, что можно сделать, это гарантировать что программа на падает в результате такой ошибки. Но ошибка-то все равно есть — результат работы не тот, что ожидался, — и найти ее без тестирования
То, что язык автоматически добавляет проверки индексов — это хорошо — если мы не стараемся сэкономить наносекунды во внутреннем цикле, конечно ;) — но эти проверки помогут только тогда, когда этот код хоть раз выполнится, а это и означает, что тесты все равно нужны.
На самом деле, я полностью согласен, что если цель — избежать ошибок индексации (или каких других) любой ценой, то язык уровня C для этого плохо приспособлен (по крайней мере, без дополнительного пре-процессора и еще каких-то инструментов). Вопрос тут скорее в том, какой ценой этого можно достичь — да, «достаточно выразительный язык» (кстати, я так понимаю, вы D к ним не относите?) может предоставить более или менее удобные инструменты для этого, но писать доказательства все равно придется программисту, и это увеличивает затраты на написание программы очень значительно. Иногда это оправдано (если цена ошибки очень велика), но для большинства приложений тестирование просто обходится дешевле.
> А для большинства приложений, увы, прокатывает «упало — да и хрен с ним, перезапустим».
Почему сразу «хрен с ним»? правильный подход — это «упало — это, конечно, плохо, мы извиняемся за причиненные неудобства и дадим вам новую версию с исправленной ошибкой завтра, больше падать не будет». К сожалению (тут я с «увы» согласен), такой подход значительно дешевле на практике, чем попытки написать идеальную программу.
Тут мы их даже реализовывали: https://habr.com/ru/company/piter/blog/432416/#comment_19498952
Без поддержки компилятора работать с ними, конечно, не очень удобно.
А вот все проверки, которые могут быть вычислены в компайл-тайме будут именно тогда и вычислены, CTFE в D мощное — даже делали расчет рейтресинга в компайл-тайме.
Если кратко, то можно абстрактно сформулировать так:
Доказательство что индекс корректный вы можете провести у себя в голове. Или другими словами этот факт можно доказать математически. Математичекские доказательства (навреное не все но многие) компьютер умеет проверять сам — на хабре обсуждалось. Следовательно существует язык (например Idris — тоже на хабре мелькал) который может заставить вас всё это доказывать, а потом компилятор будет это проверять, и в итоге гарантировать что индекс никогда не выходит за границы массива.
Практическая польза всего этого, это отдельная тема ;)
Ну не выстрелил D, не выстрелил. Хорошие идеи погребены под эффектом второй системы.
Так ведь проблема решается просто:
#include <array>
#define MAX 10
template <size_t ArraySize>
int sumArray(std::array<int, ArraySize> p) {
int sum = 0;
int i;
for (i = 0; i <= ArraySize; ++i)
sum += p.at(i);
return sum;
}
int main() {
static std::array<int, MAX> values = { 7,10,58,62,93,100,8,17,77,17 };
printf("sum = %d\n", sumArray(values));
return 0;
}
А если не нравятся шаблоны, то можно написать свой класс-обёртку с явной передачей размера массива. Было бы желание.
Кажется вы путаете C с другим языком с похожим названием.
А чего define
, а не constexpr
?
За́мок или замóк?
Несколько раз пытался использовать D в первые девять лет после его появления. В каждом случае переход на D обещал что-то хорошее, но всегда перевешивали затраты на переход (обучение разработчиков и переписывание), плюс еще какие-нибудь сложности. Всегда чего-то недоставало: то производительности, то привязки к железу и low-level контролю, то гибкости и библиотек, и т.д. Всегда оказывалось, что рациональнее использовать Python, TCL, Ruby, C# или просто остаться на C/C++.
Сейчас, после появления: Go, Rust, C++ 11/14/17/20, ASAN, Wasm и JS-наводнения — уже нет сомнений, что "D не взлетит". Поэтому остался лишь около-академический интерес разбора причин "почему не взлетело".
Python, TCL, Ruby, C#, C/C++ 11/14/17/20, Go, Rust, ASAN, Wasm, JS
Просто остался на C/C++ в обнимку с ASAN ;)
На всякий, "в обнимку с ASAN" — это шутка на 50% ;)
C tsan у меня отношения не сложились, примерно как с drd и helgrind — либо не требуется, либо прилично фалсит (много false-positives) и приходиться разгребать руками:
- Свой код уже автопилотно получается с минимизацией contention и кол-ва примитивов синхронизации, либо lock/wait-free если нужно.
- Для чужого запутанного (особенно legacy) кода эти инструменты могут прилично фалсить. И вот после ReOpenLDAP в 2015 мне что-либо по-крупному чинить не приходилось (тьфу-тьфу).
Доступность санитайзеров и их дружба друг-с-другом зависит от платформы, версии софта и т.п. Например, если не ошибаюсь, в gcc 10.x на x86_64 можно включить UndefinedBehaviorSanitizer (ubsan) одновременно с AddressSanitizer (asan) или с MemorySanitizer. А при выключенном asan можно всегда включить отдельно LeakSanitizer c любым другим санитайзером. Но лучше смотреть https://github.com/google/sanitizers и доку по конкретному компилятору.
- Для памяти: LeakSanitizer по-умолчанию включается одновременно с AddressSanitizer. Использование AddressSanitizer + MemorySanitizer (т.е. две отдельные сборки и два прогона) примерно соответствуют прогону под Valgrind с трекингом аллокаций.
- Для гонок: ThreadSanitizer, но я им почти не пользуюсь (писал выше).
- UndefinedBehaviorSanitizer: отлавливает массу всякой фигни, особенно в легаси-коде писанном для x86. Мне много раз помогал с unaligned access.
Но в целом, это RTFM.
Баги, которые разрушили ваш замок