Pull to refresh

Comments 29

Ну и как обычно это бывает с большими текстами, обо всех «косяках», ошибках и прочей ереси, которую вы, как специалист, могли заметить в простыне выше, прошу сообщать в ЛС, буду стараться оперативно править по мере сил и возможностей. Уверен, что-то ускользнуло. Спасибо.
В 2016 году я бы сказал так:
Не беритесь за программирование на С, если вы не хотите брать на себя ответственность за то, что является заботой компилятора.
И изучите наконец Rust.
Постараюсь обосновать свое утверждение, чтобы мой комментарий не казался таким голословным:
Активно защищайте код
Rust просто не позволит вам просто так упустить ошибку. Вам придется либо обработать ее, либо пробросить ее наверх вызывающему, либо явно указать, что ошибка должна привести к завершению потока.
Забудьте о глобальных переменных
В Rust нет проблем с глобальным переменными. Любое глобальное значение является неизменяемым и доступным из любого количества потоков. Изменяемым оно может быть только внутри unsafe кода.
Немного ООП
В Rust есть типажи и параметрический полиморфизм, которые дают очень широкие возможности по написании абстрактного кода, и все это работает без каких-либо накладных расходов во время выполнения.
Блокируйте небезопасные функции
В Rust есть строгое разделение на безопасные и unsafe-функции. Последние можно вызывать только внутри unsafe-блоков.
Долой странный код
У Rust есть rustfmt. Более того, проверки по именованию констант/переменных/типов встроены в качестве предупреждений в сам компилятор.
Будущее за многоядерными процессорами
Полностью поддерживаю. Вот только С никаким образом не помогает писать писать параллельный код. В отличии от Rust, который будет дотошно следить за использованием переменных и передачей данных между вашими потоками. И, опять же, все это работает без каких-либо накладных расходов.
Забудьте о true/false для success/failure
Используйте вместо этого тип-суммы:
enum Result<T, E> {
    Ok(T),
    Err(E)
}

Немного о целых величинах
В Rust есть строгое разделение на числа определенного размера (u8, u16, u32, u64, i8, i16, i32, i64) и системно-зависимые числа (usize, isize).
Используйте статический и динамический анализ
А еще лучше, когда он происходит сразу на этапе компиляции, как в Rust.
Эти ужасные зависимости
Вовсе не ужасны, если у вас есть нормальный менеджер пакетов с поддержкой зависимостей и семантического версионирования, например Cargo.
Разберитесь с неопределенными переменным на C
Rust всегда проверяет арифметические переполнения, если только вы не попросите его явно этого не делать.
Я заинтересовался Rust еще до его релиза, но только сейчас начал писать что-то в рамках его изучения, и нахожусь в самом начале этого пути.

Основная проблема для меня оказалась в его непривычности. Какие-то вещи, как например связные списки вообще внезапно оказались не так «просты» как в С++ или С.

Но и другая проблема — огромное количество Legacy, которое переводить на какой-то другой язык просто нерентабельно.
Переписывать все на Rust действительно не имеет особого смысла, хотя некоторые бросаются даже в такие крайности (видимо от безысходности).

Но что можно делать уже сегодня, так это писать на Rust, используя уже существующие наработки на C. Разработчики Rust приложили очень много усилий для того, чтобы была возможность по максимуму использовать существующее наследие, так что связка Rust-C работает очень хорошо.
Во встраиваемых системах, например, Си всё еще на коне (хотя «плюсы» в последние годы понемногу растут, но до доли Си им еще очень далеко).
И у Rust есть очень неплохие шансы начать его оттуда потихоньку вытеснять. К встраиваемым системам обычно предоставляются достаточно серьезные требования не только по производительности, но и по надежности. Поскольку ни С, ни С++ никак особо не помогают удовлетворять такие требования, переход Rust рано или поздно станет просто вопросом коммерческой выгоды.
Поживём-увидим. Много кто обещал похоронить Си. На вскидку вспоминаются Ada, Forth и Дракон, наверняка их было гораздо больше. Но воз и ныне там.
Си умрет только тогда, когда умрет последний Си-программист.
Ни Ада, ни Форт ни Дракон не решали фундаментальных проблем — они решали то, что считалось проблемой на тот момент. Работа с памятью же оставалась по сути той же. Раст — решает, причем не сдвигом парадигмы (как делает ФП), а грамотными контрактами. Во-вторых, он не берет платы от программиста за сахарок.
Торжественно обещаю Хабру попробовать написать DXE-драйвер на Rust, несмотря на пару неудачных попыток ранее. Язык выглядит действительно вкусно для написания компонентов прошивки, не являющихся критически важными, но при этом имеющими большую и запутанную кодовую базу, к примеру — драйверов PCIe и USB, которым по 5 лет уже, а баги в них до сих пор вылезают постоянно, и конца этому процессу не видно.
К знатокам такой вопрос: можно писать на Rust без стека? А без кучи? А есть у вас все содержимое памяти — read-only, кроме стека (так происходит, если исполнять PEI-модули прямо из SPI-чипа, не копируя в оперативную память, которой пока еще нет, а стек выделять из кэша второго уровня в Non-Eviction Mode)? На С — можно, если можно и на Rust — на нем можно будет написать UEFI-совместимую прошивку, минимально разбавив ассемблером. Если нет — значит, только некоторые ее компоненты.
Без кучи — точно можно, без стека — не уверен. Также думаю, что если вся память кроме стека read-only, тоже будет нормально.
Писать на Rust без кучи можно – на нем уже сейчас пишут операционные системы, во время запуска которых никакой кучи еще вообще нет. Более того, любое выделение памяти в куче происходит явно, так что у вас не получится случайно попытаться это сделать.

По-поводу стека не совсем уверен, что правильно вас понял. Как вы вообще представляете себе программирование без стека? Где хранить значения переменных? Стек – это вроде бы единственная вещь, от которой в Rust не получится отказаться. Все остальное – куча, раскрутка исключений, потоки, libstd – можно отключить. Но стек у вас будет работать хоть на микроволновке.
Без стека можно писать на ассемблере, используя для хранения переменных регистры, а для передачи управления — ближние и дальние переходы. На С писать по настоящему без стека нельзя, конечно, но можно минимизировать его использование, тоже передавая параметры через регистры и заменяя вызовы через call на переходы, ядро PEI и некоторые ранние модули так и работают. Конечно, это уже не совсем C, но я готов и на «не совсем Rust», была бы возможность. Если нет — тоже не беда, весь этот кусок можно писать как раньше, кода там совсем немного.
В Rust есть возможность делать ассемблерные вставки, думаю вам это пригодится.

Rust построен на базе LLVM, который сам по себе является виртуальной машиной с бесконечным количеством регистров. Так что я думаю что у вас не получится, как и в С, контролировать марринг значений в регистры.

В аналогичном вопросе по-поводу gcc предлагают произвести базовую инициализацию стека на ассемблере, и сразу передать управление в «настоящую» функцию. Возможно в вашем случае такое решение будет приемлемо.
Вставки — это хорошо, но решение MS не поддерживать ключевое слово __asm в 64-битном режиме компиляции в MSVC и требования совместимости отучили от вставок и разработчиков, поэтому сейчас я весь код на ассемблере выношу в отдельные файлы .asm/.s и вызываю как обыкновенные функции с соглашением MS x64.

LLVM — ничего страшного, если вставки работают правильно, то их хватит.

Про стек — я так и думал, и меня такой ответ вполне устраивает. Большое спасибо.
Немного черной раст-магии специально для вас :)

Загрузка из UEFI в раст код без операционной системы и виртуальной памяти:
blog.theincredibleholk.org/blog/2013/11/18/booting-to-rust

Думаю, вам будет интересно.

P.S.: Статья написана аж в 2013-м году. Я подозреваю, что с тех пор все стало сильно лучше.
Магия там, как раз, вполне себе белая, только вот с тех пор лучше не стало, к сожалению. С проблемой с UCS2 добрые люди вроде defuz уже помогли, осталось найти достаточно времени, чтобы сесть и написать какой-нибудь минимально полезный DXE-драйвер на Rust, не увлекшись при этом переписыванием чего-нибудь адски сложного, вроде драйвера для PCIe или USB. Статью уже обещал, теперь не отвертеться. :)
Набо трюизмов по сути. 2016 год — работать без отладчика ассертов или отладки как таковой… orly? Нет, я знал и студентов, которые писали на С и на С++, не пользуясь отладчиком вообще, а потом узнавали с удивлением, что такая штука есть. Но…

Да, у вас ссылки на статьи/переводы перепутались — дважды ссылка на один и тот же перевод статьи про «С в 2016» и ни одной на оригинал. А, да, оригинал есть внизу статьи, просто странно получается с 3 ссылками.
важды ссылка на один и тот же перевод статьи про «С в 2016» и ни одной на оригинал.


Ссылка на оригинал этой статьи — в подвале, где и должна быть: Перевод: Robert Graham. В тексте: две ссылки на оригинал How to C in 2016 и две на перевод этой же статьи на Хабре, все правильно.
А, уже поправили, сначала «сети была опубликована статья «Программирование на С в 2016 году» с» вела туда же, на перевод предыдущей.

Да, мне кажется вот этот момент
Действительный результат в этом случае 5. А все потому, что С не задает действия для x, если данная переменная получает максимальное целое значение и вы прибавляете 1, что приводит к ее переполнению.

требует прояснения.
По поводу работы без отладчика — половина реального процесса BIOS development / board bring-up проходит без отладчика, т.к. подключение текстового вывода в UART, не говоря уже об отладке на уровне исходного кода, сильно замедляет загрузку и сбивает все тайминги. Иногда это вполне терпимо, но в других случаях — совершенно неприемлимо. Если вмешиваться в процесс загрузки релизной прошивки нельзя, а отлаживать её надо, единственным доступным средством является включение декодирования порта CPU IO 0x80 на LPC или PCI и сбор последовательности POST-кодов.
Спору нет — для этих случаев (железо) разговор особый, и отладчика может не быть.
Так в нынешнее время, Си в основном в embedded и юзается, что затрудняет его отладку там, где стоит IDE.
Правомерность использования слов «в основном» сомнительна.
Если есть посыл «используйте отладчик», значит речь идет о том программировании, где его использование принципиально возможно.
Забудьте о true/false для success/failure

Читая его, я ни за что не разберусь, что здесь success, а что failure. Вот вам и разнообразие стандартов. Лучше пишите так:

Я считаю, что вместо всех этих рудиментов достаточно взять за правило строить условия таким образом, чтобы блок if всегда соответствовал true/success, а else всегда соответствовал false/failure. В таком случае недоразумения исключены.

P.S. Не-С программист
UFO just landed and posted this here
Да, но 0 (false) ка успех как раз нелогично, и именно для таких функций стоит прописывать сравнение явно, чтобы подчеркнуть «что-то здесь не так». Для тех, где успех — это ожидаемое true это просто лишнее нагромождение.
UFO just landed and posted this here
Sign up to leave a comment.

Articles