Pull to refresh
2
0.3
Кривенков Роман @qwerty19106

Embedded

Send message

А вы всегда отвечаете вопросом на вопрос?

Я на ваши вопросы отвечал, пусть ответы вас не устроили. Предлагаю ради разнообразия хоть раз ответить на мой вопрос.

Я считаю что ответил. И я правда не понимаю что вы ещё от меня хотите.

Напишите свою версию определения, может понятнее будет.

Да бог с ним с UB

Это вы так признали свою неправоту?)

Тогда я перефразирую тот комментарий:

Тут надо рассматривать 3 случая:

1) На этапе компиляции известно что есть UB (например выход за пределы массива с известным индексом). Тогда код изначально не является кодом на С++.

2) На этапе компиляции известно что нет UB (все потенциальные места обвешаны проверками). Тогда код является кодом на С++.

3) На этапе компиляции нельзя доказать что нет UB, но есть места, потенциально содержащие UB (в них недостаточно проверок). Тогда нельзя сказать заранее, является ли кодом на С++.
Если при запуске такого кода происходит UB (например входные данные, которые не функция не ожидает), то в этот момент он перестает быть кодом на С++.

Термин "программа на С++" ввели вы в этом комментарии. Это я бы должен спрашивать что это xD.

Но я на это повелся, не заметил даже. Но везде имел ввиду "код на С++". Про него я подробно уже ответил вот в этом комментарии.

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

А вот код с UB (который при некоторых входных данных вызывает UB) может выдать разный результат при каждом запуске. Я выше вам привел пример с ARM.

Или например, выход за границы массива позволяет читать "соседнюю" память, и при каждом запуске этого кода (даже в пределах одного запуска программы) может быть разный результат, т.к. другие части программы поменяли эту "соседнюю" память.

Многократно эксплуатируя эту уязвимости, можно прочитать ключ шифрования например. А вы говорите:

Программа, это результат работы компилятора. Она материальный объект и не может находится в неопределенном состоянии

Не ужели вы не видите тут ошибку в своей логике??

Конечно. Вся статья о том, как "писать правильно и не писать неправильно". Что же в этом плохого, раз компилятор не может этот момент проверить за программиста?

Но чем этот совет и способ предотвращения ошибок в программах не подходит, например, для С++?

Подходит, о чем я и писал на 4 комментария выше.

Прочитайте наконец обсуждаемую ссылку дальше заголовка. Там написано, что утечки памяти это плохо, и как их предотвратить:

Compared to a real-world program, the consequences of creating a reference cycle in this example aren’t very dire: right after we create the reference cycle, the program ends. However, if a more complex program allocated lots of memory in a cycle and held onto it for a long time, the program would use more memory than it needed and might overwhelm the system, causing it to run out of available memory.

Creating a reference cycle would be a logic bug in your program that you should use automated tests, code reviews, and other software development practices to minimize.

Вы выдираете слова из контекста, и этим гордитесь почему-то.

Ассемблерные инструкции в скомпилированной программе конечно определены. Но одна из проблем UB в том, что при перекомпиляции они могут быть уже другими. И даже без изменения исходного кода. А при полной перекомпиляции может быть 3-й вариант.

Не говоря уже о разных версиях компилятора, целевого процессора, ОС компилятора и целевой ОС и т.д.

не может находится в неопределенном состоянии

Вообще-то может. Приведу простой пример:
Процессор ARM может (и постоянно делает) переупорядочивать на лету ассемблерные инструкции. И при многопоточном доступе может возникнуть data race, если не выставлены правильные барьеры памяти.

В результате один и тот же код (одинаковые инструкции ассемблера) на одном процессоре с одинаковыми входными данными выдаст разный результат. Скомпилированная программа находится в неопределенном состоянии, и это тоже UB.

Если честно, то это вы придираетесь к словам, а не к сути.

Я понимаю вашу претензию к автору статьи, он не прав про утечки памяти в С++. Но и вы не правы, они абсолютно одинаковые в С++ и Rust. И способы их избежать одинаковые: слабые ссылки.

Тут надо рассматривать 3 случая:

1) На этапе компиляции известно что null, и мы его разименовываем. Тогда изначально не являлась программой на С++.

2) На этапе компиляции известно что не null, и мы его разименовываем. Тогда UB нет.

3) На этапе компиляции не известно что лежит null / не null, и мы его разъименовываем без проверки. Тогда программа потенциально содержит UB, а фактически содержит UB в момент фактического разъименования null (и в этот момент перестает быть программой на С++).

Но изначальный мой посыл был что Undefined behavior гораздо опаснее unspecified behavior, а вы пытаетесь их приравнять.

Она safe в смысле что не вызывает UB, и может произойти без блока unsafe. Это просто описание фактического положения дел.

Другие языки не называют утечки памяти "безопасными"

Но и не называют их "небезопасными". Вот если в другом языке будет написано что утечка памяти unsafe или unsound, тогда будет о чем говорить. Можете привести такой пример?

Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a memory leak). Preventing memory leaks entirely is not one of Rust’s guarantees, meaning memory leaks are memory safe in Rust.

Так тут и написано, что утечку памяти невозможно выявить на этапе компиляции. По этому Rust не дает этих гарантий. И другие языки не дают.

По-этому вы не смогли обосновать что это "фича", ведь другие языки имеют точно такую же "фичу".

Циклические ссылки в любом языке могу привести к утечкам памяти. Даже GC не всегда может их заметить. Почему вы считаете это проблемой только Rust?

поэтому они считаются не ошибками, а фичами

Обоснуйте. Пока что это просто ваша фантазия, которую вы приписываете разработчикам языка Rust.

Тогда как UB в С++ (англ. undefined behavior) именно неопределенное поведение, т.е. это не зафиксировано в стандарте С++ и конкретная реализация оставлена на усмотрение разработчиков компилятора.

Вы категорически не правы (я очень надеюсь что просто заблуждаетесь). Undefined behavior не равно unspecified behavior, и в стандарте напрямую написано "код, содержащий UB, не является кодом на С++". В отличие от unspecified behavior.

Так оно не уменьшает количество кода. Давно известна шуточная поговорка: если переписать любую программу на ООП, она станет в 2 раза больше.

Даже в С++ уменьшает, несмотря на разделение на .cpp и .h. Вы, я вижу, ничего не слышали о таких принципах ООП как наследование и композиция, которые придуманы именно для уменьшения дублирования кода.

И как бы эти мелочи влияли на размер кода в разы?.. Кстати, enum даже в Си есть.

Это просто феерическое невежество! Enum в С это надстройка над int, а enum в Rust это тип-сумма (Sum Type), и он может внутри себя содержать другие типы. И это сильно уменьшает объявление структур например. Зачем вы обсуждаете язык Rust, о котором не знаете практически ничего?

Может быть, в среде конкретно Жабы и нормально считать, что "наша программа не содержит Data Race и также не содержит Race Condition" тупым volatile, но у нас в ядре за такое будут бить по рукам, назвав race condition.

Volatile в Java и Volatile в С или С++ это разные вещи, и они дают разные гарантии. Но вы как обычно не в курсе..

Ровно наоборот. Принципиальным является понимание что происходит, с выбором соответствующих мер, но это как раз в Rust исходят из религиозно-фанатичной позиции "что проверил компилятор, то безошибочно, давайте всё перепишем на раст", за что их и надо бить. Мы же знаем, что ошибаются все, поэтому ревьювить и тестировать надо всё.

Вы не понимаете разницы между кодом библиотек и кодом, использующим эти библиотеки. Для библиотек с примитивами синхронизации в любом случае нужно ревью, тесты и т.д.

Для кода, использующего эти библиотеки, компилятор С и С++ не может проверить, что библиотеки используются правильно. Да и вообще используются, можно забыть вызвать mutex, и код скомпилируется. По-этому приходится обмазываться ubsan, статическими анализаторами и т.д.

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

Коим боком увеличение числа тредов в драйвере, совсем другой вопрос обсуждения, "замыливает" вопросы синхронизации? Ведите дискуссию корректно, не путайте ветки, ошиблись про число тредов в драйвере - не обвиняйте собеседника в вопросе, который обсуждается в цитате выше.

Не выдирайте слова из контекста. Обычно в драйвере 1 поток, а смысл был в том что кроме него есть прерывания и DMA. То что там в некоторых специфичных драйверах не один поток, ничего не меняет.

Так вполне очевидно из приводимых мной примеров в комментариях - это прежде всего код в ядре. А ядро - оно отнюдь не только из драйверов состоит.

Посмотрите исходники Redox OS например, и убедитесь наконец что количество unsafe в ядре ОС - это лично ваш стереотип.

Сравнивалось небось с кодом, в котором нет соответствующий библиотечных функций, которыми уже обеспечили в Расте. Сам язык не выглядит более выразительным, чем С++ (и более того, выглядит более verbose).

Вы сравнили С и Java, по-этому я написал про С. И библиотеки тут не при чем. Разве в С есть классы, итераторы, трейты/интерфейсы и прочее ООП? А в С++ сильно не хватает тип-суммы (Sum Type), в Rust это Enum. Ну и pattern matching, который завезли даже в С# уже.

Общепринятым термином является race condition. А "гонка данных" режет русское ухо, напоминая "добрый морнинг, господа телевотчеры" и т.п. кальку.

Race Condition (состояние гонки) и Data Race (гонка данных) это разные вещи, что должен бы знать каждый системный программист. Впрочем уже очевидно, что вы им не являетесь.
А перевод на русский не я придумал, он общепринятый.

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

В ассемблере будет +- одно и то же. Но вопрос именно в том, чтобы код с Data Race не мог скомпилироваться. Особенно это важно в ядре Linux, по-этому Торвальдс старается продвинуть Rust в ядро.

А вы исходите из позиции: я идеальный программист и никогда не ошибаюсь.

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

Вопрос стоит как предотвратить Data Race, а вы его замыливаете.

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

Микроконтроллеры и драйверы - это тот самый код, который (по общему убеждению) весь построен на unsafe. Но практика показывает, что это не так.

А весь остальной код почти не требует unsafe, т.к. выполняется поверх ОС. Вы уж сначала определитесь, что такое для вас системное программирование. Ато это переобувание в полете выглядит так себе..

к выразительным высокоуровневым языкам Раст не относится

От 2 до 4 раз меньше кода, чем в С.

Не знаю, я не пишу на всех перечисленных. И само словосочетание "гонка данных" по-русски режет ухо - что именно имеется в виду? В каждый момент структуру может borrow только один тред? Так это ж сразу "привет" производительности в ряде случае - никаких атомиков, только разрешенные компилятором мьютексы (а не любые), и т.д.

Что же вы не знаете общепринятые термины в системном программировании, про которое так смело рассуждаете? Гонки данных (data race), это один из видов состояния гонки. Приводит к багам, которые проявляются случайным образом, и которые очень трудно найти и отладить.

В Rust это решается трейтами Send и Sync, которые работают поверх концепции владения (borrow). И неверно написанный код не вызовет гонку данных, а просто не скомпилируется. В отличие от тех языков, на которых вы не пишете.

P.S. В драйвере или микроконтроллерах всего один поток. Но гонку данных вызвать очень легко из-за прерываний и DMA. И это большая проблема при разработке ядра Linux например.

Разве под словами "Так не проще ли взять любой современный язык с GC и JIT?" не предполагается именно "переписать"?

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

Я имел ввиду новый код, думал что это очевидно.

Предложенная техника защиты походит на концепцию владения и заимствования из языка Rust, но реализована на базе сильных и слабых ссылок (стандартных механизмов С++ shared_ptr и weak_ptr). Любые операции с данными для переменной по ссылке возможны только после её захвата, т. е. после преобразования слабой ссылки (weak_ptr) в сильную (shared_ptr).

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

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

Если язык позволяет и поощряет писать неправильно, будут писать неправильно. И не будут использовать "правильные классы", ведь без них код "как бы проще и короче". Об этом вам уже написали выше в этом комментарии.

"Вся ценность Rust свелась к одному заголовочному файлу для C++", это цитата комментария с opennet и об этом я явно написал в самом комментарии

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

Borrow Checker в Rust решает кучу задач и это только одна из них.

В Rust платность заключается в необходимости переписывать тонны существующего кода и это никак не связано с рантайм проверками.

Вы это сами только что придумали? Приведите цитату, где я предлагал что-то переписать на Rust!

Поэтому некоторые проверки всегда будут выполнятся в рантайме не зависимо от языка программирования.

Вот именно что некоторые проверки, причем довольно редкий случай для прикладного кода. А все прочие вы предлагаете сделать в рантайме вместо compile-time.

И я заметил как вы аккуратно забыли тему гонок данных и других UB, которые никак не предотвращает Memsafe, но предотвращает Borrow Checker, который вам так не нравится. Ведь вы изначально утверждали так:

Вся ценность Rust свелась к одному заголовочному файлу для C++

В Rust небесплатность заключается в том, что нужно указывать явно время жизни когда компилятор не может его вывести автоматически. Я вот сейчас посчитал, в текущем проекте на 50+KLOC таких строчек ровно 71 (0,14%), и это Embedded (микроконтроллеры STM32). Так что на практике отладка и поддержка не страдают.

Вообще я думал, что в С++ стараются избегать рантайм проверок (из-за чего в языке существуют многочисленные и неочевидные UB), а тут предлагается прямо противоположное.

Вероятно этот подход со слабыми ссылками будет по производительности даже хуже современного GC с аренами. Так не проще ли взять любой современный язык с GC и JIT?

P.S. В любом случае для С, С++ и Rust zero-cost проверки должны быть приоритетнее рантайм проверок, но мой взгляд.

Надеюсь вы понимаете, что слабые ссылки не бесплатные. И должно быть минимум 2 вида слабых ссылок: однопоточные и многопоточные. Первые почти бесполезные, вторые внутри реализованы через бесконечный цикл с atomic::compare_exchange_weak.

В Rust же концепция владения полностью zero-cost. Кроме того сильные/слабые ссылки сами по себе не предотвращают гонки данных.

1
23 ...

Information

Rating
2,562-nd
Location
Ижевск, Удмуртия, Россия
Date of birth
Registered
Activity