Как стать автором
Обновить

Комментарии 44

великодушный пожизненный диктатор
Что ни говорите, а вдумчивость во взгляде и стать приходят только с возрастом, достаточно одного
взгляда
«великодушный пожизненный диктатор»

Уолтер БрайтГвидо ван РоссумЛинус Торвальдс
А можно подписать, кто есть кто?

Для повышения образованности.
Бенито Мусолини, Уолтер Брайт(создатель D), Гвидо ван Россум(создатель Python), Линус Торвльдс(создатель Linux).
Спс, а то лица все знакомые, но никак не признаю. Муссолини вот только не знаком был )
А sanitizer же ловит эту ошибку без проблем:

$ 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'ом помогут избежать большинства подобных ошибок, и изучать другой язык не понадобится.
Ничуть не умаляю мощь санитайзеров/valgrind, но они ловят только то, что произошло. А для этого надо написать тесты на все-все случаи (что невозможно, но опыт помогает покрыть большинство пограничных случаев).
В случае, если размер массива — константа (как в статье), static code analysis эту ошибку легко находит. Ну а если размер динамический, то и для D надо писать тесты, чудес-то не бывает.

Проще использовать range for или другими словами for each синтаксис.

НЛО прилетело и опубликовало эту надпись здесь
Не совсем так — все, что могут гарантировать «более выразительные языки» (т.е. языки более высокого уровня чем C), это то, что ошибка выхода за границы массива приведет не к испорченной памяти, а к прерыванию во времени исполнения. Но ошибка то никуда не делась, и «тщательно построенный вами замок» по-прежнему разрушен (т.е. программа работает не так, как предполагалось).

С тем, что в высокоуровневых языках можно гарантировать, что программа никогда не крашится при ошибках какого-то рода (или при любых ошибках) — с этим никто не спорит. Но чуда все-таки нет, это может достигаться только добавлением проверок во время выполнения… которые никто не запрещает делать и в низкоуровневых языках (только там их надо делать руками), и логическую ошибку в программе это не исправляет (только делает слегка проще ее обнаружение).
НЛО прилетело и опубликовало эту надпись здесь
Ну так я примерно про это и говорю — либо доказательство правильности существует (и тогда умный static code analysis может найти его и в низкоуровневом языке), либо его нет (что бывает чаще), и тогда проверки времени выполнения неизбежны и это ответственность программиста обработать такую ситуацию, в любом языке.

P.S. Чтобы прояснить — я ведь вовсе не против использования высокоуровневых языков, и совсем не отрицаю, что они действительно упрощают процесс программирования. Все, что я хотел сказать — использование такого языка не является «серебряной пулей», которая магически решит все проблемы, следить за логикой использования индексов в программе все равно придется. И тесты для этого все равно писать надо, никуда не деться.
НЛО прилетело и опубликовало эту надпись здесь
> Если за логикой использования индексов следит компилятор, то и тесты не нужны. Раз вы говорите о статических анализаторах, то представьте себе такой, который гарантирует, что если он не ругнулся на вашу программу, то ошибок доступа за границы там просто нет как класс.

Да, это именно то, о чем мы спорим — с моей точки зрения, в общем случае компилятор просто не в состоянии «следить за логикой», и никто не в состоянии, эта задача алгоритмически неразрешима. Если размер массива и/или индекс определяется динамически по некоторому алгоритму, то в принципе невозможно гарантировать отсутствие ошибок индексации — все, что можно сделать, это гарантировать что программа на падает в результате такой ошибки. Но ошибка-то все равно есть — результат работы не тот, что ожидался, — и найти ее без тестирования в принципе на практике невозможно.

То, что язык автоматически добавляет проверки индексов — это хорошо — если мы не стараемся сэкономить наносекунды во внутреннем цикле, конечно ;) — но эти проверки помогут только тогда, когда этот код хоть раз выполнится, а это и означает, что тесты все равно нужны.
НЛО прилетело и опубликовало эту надпись здесь
С этим я не спорю — заметьте, я в конце первого абзаца «в принципе» зачеркнул и заменил на «на практике» ;).

На самом деле, я полностью согласен, что если цель — избежать ошибок индексации (или каких других) любой ценой, то язык уровня C для этого плохо приспособлен (по крайней мере, без дополнительного пре-процессора и еще каких-то инструментов). Вопрос тут скорее в том, какой ценой этого можно достичь — да, «достаточно выразительный язык» (кстати, я так понимаю, вы D к ним не относите?) может предоставить более или менее удобные инструменты для этого, но писать доказательства все равно придется программисту, и это увеличивает затраты на написание программы очень значительно. Иногда это оправдано (если цена ошибки очень велика), но для большинства приложений тестирование просто обходится дешевле.
НЛО прилетело и опубликовало эту надпись здесь
Вроде бы, мы почти пришли к согласию… за исключением:

> А для большинства приложений, увы, прокатывает «упало — да и хрен с ним, перезапустим».

Почему сразу «хрен с ним»? правильный подход — это «упало — это, конечно, плохо, мы извиняемся за причиненные неудобства и дадим вам новую версию с исправленной ошибкой завтра, больше падать не будет». К сожалению (тут я с «увы» согласен), такой подход значительно дешевле на практике, чем попытки написать идеальную программу.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Юниттесты — подразумевают отдельный ран.

А вот все проверки, которые могут быть вычислены в компайл-тайме будут именно тогда и вычислены, CTFE в D мощное — даже делали расчет рейтресинга в компайл-тайме.

Если кратко, то можно абстрактно сформулировать так:


Доказательство что индекс корректный вы можете провести у себя в голове. Или другими словами этот факт можно доказать математически. Математичекские доказательства (навреное не все но многие) компьютер умеет проверять сам — на хабре обсуждалось. Следовательно существует язык (например Idris — тоже на хабре мелькал) который может заставить вас всё это доказывать, а потом компилятор будет это проверять, и в итоге гарантировать что индекс никогда не выходит за границы массива.


Практическая польза всего этого, это отдельная тема ;)

Так я же 100% практик, я только с этой точки зрения и смотрю ;)

Если бы он "не выстрелил", то не было бы этого.


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

Я бы полностью согласился с Вами, если бы товарищ А не начал масштабные работы по его переделке, которые предали забвению эту самую вторую версию до того, как он толком выстрелил. Итог: он сейчас далеко не мейнстримовый.

Так ведь проблема решается просто:


#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 с другим языком с похожим названием.

Шаблоны, классы, мы точно о C говорим, или уже вляпались в ООП и плюсы со всякими шарпами?

Во-первых, обычно D сравнивают с C++, а не C.
А во-вторых, я посмотрел в тэги, увидел C++.

Цикл статей о BetterC
Это сильно урезанное подмножество D.

А чего 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 и доку по конкретному компилятору.


  1. Для памяти: LeakSanitizer по-умолчанию включается одновременно с AddressSanitizer. Использование AddressSanitizer + MemorySanitizer (т.е. две отдельные сборки и два прогона) примерно соответствуют прогону под Valgrind с трекингом аллокаций.
  2. Для гонок: ThreadSanitizer, но я им почти не пользуюсь (писал выше).
  3. UndefinedBehaviorSanitizer: отлавливает массу всякой фигни, особенно в легаси-коде писанном для x86. Мне много раз помогал с unaligned access.

Но в целом, это RTFM.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории