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

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

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

C++ отлично умеет работать без исключений, если хочется. пример: Chrome. нет никакой "непредсказуемости". Исключения возникают только там, где кто-то написал throw

Есть флаг компилятора -fno-exceptions который полностью убирает механизм исключений из C++

После этого вы начинаете писать на чем угодно, но не на С++) Ни один из стандартов С++ не подразумевает отключение исключений.

Стандарт Це++ много чего не предусматривает, зато предусматривает кучу всякого бреда, особенно в плане UB, хотя 95% этих UB вполне себе DB на любой платформе.

Unreal Engine работает на C++ уже 20 лет, сейчас на 17 стандарте, исключения отключены, полет нормальный

Это канцеляризм. Нет исключений - не C++. Де-юре это так. Де-факто всем пофиг, особенно в embedded: отключается rtti, исключения и, возможно, как в моём случае, стандартный рантайм.

Это канцеляризм. Нет исключений - не C++. Де-юре это так.

а где в стандарте С++ написано что программист С++ вот прям обязан применять исключения в своем коде? Исключения это одна из возможностей которую определяет стандарт, если программист использует пол сотни других возможностей, но не использует какую-то одну то его работа не соответствует стандарту, так что ли? Где логика?

На чистом С предшественником исключений является longjump(), получается если нет лонгджампов в Си-коде это не Си код что ли? Много мы Си кода найдем с лонгджампами?

А как запретить? много ведь разработчиков ядра. Ну допустим на уровне компиляции. Но Линукс же еще и на наследование ругается и на оператор new. Если и их запретить, то что останется от плюсов?

а чем new то плох? его реализацию просто нужно правильную. в Numega new был свой типа new(NonPagedPool) new(PagedPool) и там были закопаны функции выделения памяти для ядра...

Проблема не только в том, что в ядре обычный malloc не работает - это действительно лечится соответствующим аллокатором - а в том, что при обычном использовании C++ (с контейнерами, умными указателями и т.п.) память может перевыделяться несколько раз в одной строчке кода типа a[i] = b + c, причём чтобы это понять надо слазить в реализацию классов всех объектов, которые в этой строчке упомянуты. В C всё явно - для выделения памяти надо позвать какую нибудь функцию - и это дисциплинирует. А если запретить переаллокацию в операторах и при преобразовании типов - то от C++ остаётся ещё меньше.

ну это лечится и техническими средствами и стилем программирования. Как пример вот есть Arduino String - живут с ним. есть гайды как правильно его использовать чтобы меньше алокаций и меньше фрагментация. Есть улучшенные реализации. И это С++ в мелких микроконтроллерах. Для линукса ну тоже гайд нужен, профилирование и code review. В конечном итоге продвинутые альтернативные драйвера могут лежать в отдельной папке и использоваться по желанию пользователя, а дальше время рассудит.

Вопрос, можно ли это всё ещё называть C++. Да, всё ещё можно пользоваться шаблонами (осторожно, чтобы код влез в прошивку) и обычными классами с наследованием (конечно, без RTTI) - но в целом для прикладного программиста, пишущего на современном C++, это практически другой язык.

ну тут кому как. все вот молятся на исключения в С++, а для меня это только лишний гемор всегда был. Ну вот пишу я библиотеку в которой использую библиотеку работающую с исключениями... так для меня главная задача все эти исключения перехватить и дальше не выпустить тк пользователь в LabView например совсем не ожидает получить исключение, а скорей errcode от вызова функции. С++ тем и хорош что многогранен в своем применении и при этом совместим между ними.

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

ну так их надо не забыть в нужном месте.

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

С исключениями это может выглядеть так:


void WriteFile()
{
   throw CFileExc(…..); //наследует так или иначе std::exception
}

void CompressData()
{
  try
  {
    ..........
    WriteFile();
    ..........
  }
  catch(std::exception& exc)
  {
     AddInfoAndRethrow(exc, "CompressData: Failed to compress data and bla bla.");
  }
 
}

void SaveData()
{
  try
  {
    ..........
    CompressData();
    ..........
  }
  catch(std::exception& exc)
  {
     AddInfoAndRethrow(exc, "SaveData: Failed to save data and bla bla.");
  }
 
}

в итоге в логе будет будет информация типа:

Can't open file {some file} access denied.

CompressData: Failed to compress data and bla bla.

SaveData: Failed to save data and bla bla.

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

Почти всем можно пользоваться, просто голову нужно держать включённой -- а у ЛТ она как раз напрочь отключена, поэтому дичь и несёт. В частности, никто не притащит в ядро STL, Boost и прочее, если ты не притащишь это сам -- ну так не используй те средства, которые в коде ядра неуместны, в чём проблема-то?

Что же до прикладных программистов, то написание системного кода было для них сродни чёрной магии и 40 лет назад, причём совершенно независимо от используемого языка: мало того что ты остаёшься один на один с железом, так ещё и должен соблюдать изрядное количество неочевидных правил, чтобы что-нибудь не развалить из-за нарушения порядка использования внутриядерных структур данных и т.п. (на прикладном-то уровне такие проблемы возникают редко и на 95% разруливаются через ОС).

С++ без STL - это хуже (в плане разницы между "урезанным" языком и стандартным), чем C без libc. Не изобретать велосипедов и не переписывать STL - первое правило программиста на C++. Причём какие то части и в ядре ничего не должны сломать (какой нибудь бинарный поиск на массиве), но отделить безопасное от небезопасного - та ещё задачка.

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

Говна лопатой можно на любом языке принести. На крестиках конечно проще. С очень лаконичный язык, плюсы очень раздутый. На гигантском опенсорсе, окей, но притворяться что на плюсах не пишут код без исключений, когда теже ошибки прокидывать в разы удобней (реализуешь result из rust и радуешься) и безопасней не стоит. Особенно когда есть какой-нибудь гугл, у которых в стайл гайде прописано "мы не используем исключения ".

Есть языковые механизмы, которые полагаются на исключения вне зависимости от желания программиста. Например new бросает bad_alloc, dynamic_cast бросает bad_cast, стандартные контейнеры бросают исключения. То есть в целом недостаточно просто не использовать исключения, нужно еще знать какие языковые средства их используют и исключить их из кода тоже.

dynamic_cast нужен в весьма специфичных случаях, а применительно к ядру -- реально никогда. Вместо обычного new можно использовать new (std::nothrow) -- и он будет возвращать nullptr при отсутствии памяти, а не кидаться исключениями.

Так что знать эти вещи, конечно, нужно, но не находите, что программист вообще всегда должен понимать, что он делает и как работает используемый им инструмент?

Речь же не об этом. Напомню контекст:
> Нет исключений - не C++. Де-юре это так.
> если программист использует пол сотни других возможностей, но не использует какую-то одну ...
А мой комментарий о том, что за отключением исключений потянется еще, никак не одна, возможность.

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

А не они ли для этого написали свой компилятор, свой стандарт С++ и свою STL, из которого кроме исключений выпилили почти все UB?

То что получилось, уже никак нельзя называть языком С++.

Они не писали ни свой компилятор, ни свой стандарт C++.

Что значит "тем не менее"? Я где-то говорил, что без исключений нельзя?

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

> Нет исключений - не C++. Де-юре это так.

на сколько я это понимаю С++ есть смысл отличать только от чистого Си. Потому что если мы говорим что это не С++ то обычно подразумевается что это Си. Ни о чем другом смысла говорить нет так как ничего другого не существует.

Также на сколько я это понимаю принципиальное отличие С++ от Си это наличие классов, интерфейсов и наследования классов.

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

Действительно можно говорить что отключая исключения мы отключаем ветку возможностей, а не одну возможность. Но корневой веткой которая делает из чистого Си -> С++ является наличие классов, но ни как не исключений.

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

Cи с классами!

Ну так сейчас проще явно сделать такой отдельный язык со своим стандартом и frontend'ами в clang/gcc, чем отрезать лишнее от актуальных версий C++.

на сколько я это понимаю С++ есть смысл отличать только от чистого Си.

Конечно нет. C++ есть смысл отличать от его диалектов и, отключая исключения, мы переключаемся на диалект (который, к слову, по-разному будет реализован в разных компиляторах - и вот тут уже становится критично важным это различие, если мы хотим обеспечивать переносимость). Откройте любую документацию по любому компилятору, и вы увидите в ней описание поддерживаемых диалектов. Я ниже по ветке уже привел пример различий с диалектом GNU, у MS тоже есть в документации секции, описывающие диалект VC++.

Но корневой веткой которая делает из чистого Си -> С++ является наличие классов, но ни как не исключений.

Любой язык программирования - это формальный язык. Два формальных языка эквивалентны, если они содержат одинаковое множество слов. Так что когда вам говорят, что формально C++ перестает быть C++, если мы убираем из него исключения, то это как раз об этом.

возникает справедливый вопрос на который нет ответа: "Если это не С++ то что это?".

Это диалект.

-----

В общем и целом я нисколько не удивлен такой реакции от вас. Ниже по ветке тоже об этом писал.

В контексте коммитов в ядро вопросы вполне практические - как именно формально специфицировать разрешённое для использования подмножество языка, как автоматически проверять соответствие кода этому подмножеству и реально ли всё это сделать с учётом технических и социальных причин?

Что тогда де-юре С++ без исключений, не подскажете?

Диалект.
Собственно это не единичный пример. Вот когда человек берет и запускает компилятор, ну, скажем, G++, с параметрами по-умолчанию, он тоже нифига не С++ использует, а его диалект GNU. И чтобы получить C++, нужно еще поднастроить параметры сборки, добавив ключей.

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

Ну зря вы иронизируете. В контексте темы статьи это может быть и не важно, но когда речь заходит о переносимости приходится возвращаться к этим вопросам. Например, ведь GNU-диалект разрешает, как известно, массивы с размерностью времени исполнения, а C++ нет. И если мы уж используем эту фичу, то должны понимать, что это диалектная фича и работать она в других компиляторах не обязана.

то есть вы утверждаете, что стандарт С++ требует наличия throw/catch в коде? 😏

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

Если отвечать реально - зависит от кода. Если не использовать STL и везде писать new с std::nothrow, то в целом будет С++ код без исключений.

В целом, в подобных дискуссиях всегда наблюдается некоторое недопонимание, ведь обычные программисты юридически мыслить в контексте используемых языков почти не умеют. Для них если компилируется, значит C++, а попытки разъяснить юридическую сторону вопроса чаще всего просто воспринимаются в штыки. Проще всего (для сохранения конструктива) просто быть на их волне и не поднимать этих тем.

Так я это :) Числюсь в штате разработчиком) Тот самый обычный программист) Занудный немного, но самый обычный)

Кто сказал?

Как я понял из статьи, Торвальдс заедается вопросом: Если в С++ можно обойтись без исключений, как и в С, тогда зачем он в принципе нужен по отношению к написанию ядра…

тщательно продуманным 30 миллионам строк кода, составляющим ядро Linux

Интересно что имел ввиду автор объявляя 30 миллионов строк тщательно продуманными, это такой тонкий троллинг? Для кого?

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

Любопытно одно - за развитие ядра агитируют люди, которые в подавляющем большинстве своем для ядра ничего не писали. Линус сказал Rust'у быть - значит быть. По большому счету это повод пошевелить ряд подсистем, которые другими методами подвинуть ну никак не получается. А уж что там будет - это будет видно. Когда-то и на Go драйвера писали. И вроде бы тоже кричали, что "быстрее и безопаснее одновременно".

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

Монолит vs микросервисы или Авианосцы против "москитного флота". Плохо лишь то, что Linux не Hurd или QNX. И при этом каждый свою нишу занял. Статья-то собственно ровно об этом.

А что бывает при использовании плюсов для драйверов - это надо поспрашивать у Apple и Hackinoth'езаводчиков. Там плюсы для ядра - это как раз mainstream. По собственному пользовательскому опыту все довольно неплохо. Но и зоопарк железа (даже с учетом Hackintosh'ей) там несравнимо меньше. Как и количество желающих портировать драйвера из того же Linux. Если сеть еще как-то, то звук и видео - совсем глухо.

Хотите напомнить Линусу про Minix?

Текст значительными местами выглядит так, во всяком случае в переводе, что как-будто он написан генеративным ИИ, а не человеком.

Потому что наверняка перевод делался с использованием нейросети.

Там прямо с первого абзаца ИИ начало корежить: то C++ - это другой язык, то он надстройка над C, то расширение C, то объектно-ориентированная версия C. Холиварные тексты лучше с помощью LLM не писать, он пытается упаковать все интернет-срачи в заимоисключающие параграфы

пока доля Раст в коде (даже новом) очень мала

ядро windows nt вроде как тоже на С, но оно написано так что не блокирует использование компилятора C++. Это позволило в свое время компании Numega создать набор классов DriverStudio для более легкого написания драйверов для windows на С++ и все прекрасно работало. Более того в то время для ядра линукс был патч который позволял использовать С++, но понятное дело его никуда не приняли. так что-то тут в линуксе диктатура какая-то - сказали С и Rust и все... остальные свободны.Так-то никто не претендует на код ядра и основных подсистем, но почему нельзя драйвера писать на других языках непонятно - если они фиговые будут их не примут пользователи и перепишут на С там или Rust.

Потому что эта статья - реклама курсов.

Заглянул в Windows-driver-samples в исходники драйвера виртуальной звуковой карты windows. Написан на С++. И совершенно не увидел там ничего более легкого. Там всё в классах кроме extern "C" NTSTATUS DriverEntry() и extern "C" void DriverUnload().

portio на с++ через numega гораздо красивей и проще был. wdf что-то среднее по итогу.

Да почему нельзя? Можно. Я сам писал. Другое дело, что это был проприетарный код. Но в целом, никто не мешает написать модуль ядра и open source, просто собираться он будет out of tree

это сильно заморочиться с билд системой надо...

В самом деле? Минимальный makefile для out of tree LKM состоит из одной строчки. Реально, а не фигурально: obj-m += mylkm.o

так это драйвер на С соберется... мои драйвера все out of tree были. а С++ так не пойдет.

хотя я возможно отстал. и можно с++ собрать.

Нет. Штатная функция. Много модулей есть out of tree, дрова nvidia самые известные представители

в Linux драйверы являются частью ядра операционной системы и, видимо, их нестабильность может порушить систему с большей вероятностью чем в винде

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

Правда, в Винде не ломают постоянно внутриядерные интерфейсы, поэтому корректно написанный под какую-нить Вынь2000 драйвер соберётся и будет работать и в 11. Есть исключения, главное из которых -- видеодрова, поскольку при переходе от 2003 на Вислу полностью поменяли драйверную модель для видюх, но практически всё остальное вполне себе переносимо.

А вот в Линухе попробуй возьми драйвер, написанный для 2-й версии, и заставь его собраться для 5-й. (Поэтому часто во всяких там встраиваемых системах и используют жутко древние версии линуховых ядер: драйверы только под них, переписывать -- то ещё удовольствие, даже если документация на железо имеется...)

Переписывать действительно боль. Только итеративный процесс: у меня был драйвер в сопровождении, собирался и работал от 2.6.27 до 4.19. С пятой версии ядра, я, емнип, уже не работал с ним. Был слой совместимости написан. Но автор исходной версии очень заморочился. Как результат, 90% кода без ifdef работало в драйвере для windows, linux и mac (потом был заброшен), остальные 10% как раз реализовали некий os-специфичный интерфейс и туда же попали ifdef по версиям ядра.

ЗЫ pcie и usb грабберы Epiphan.

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

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

Кстати говоря, в модульности ядра ничего особо нового нет. Скажем, в RSX-11 -- бабке Винды, появившейся ещё в первой половине 1970-х -- драйверы могли (и обычно делались) загружаемыми, и можно было по мере необходимости их загружать/выгружать. Ну а в OS/360 (ещё чуть ли не на 10 лет раньше) вообще 80% ядра загружалась в память по мере необходимости, причём пользователь мог писать собственные модули (и работало сие без всякой виртуальной памяти, поддержки которой тогда на уровне железа ещё не было).

И, кстати, в RSX-11 драйверы файловых систем всегда были задачами (процессами) режима пользователя, а не драйверами режима ядра -- в ядре крутились драйверы, прямо работающие с железом (и то, строго говоря, это не было совсем уж обязательным -- можно было написать "железный" драйвер, работающий как задача). Так что, когда в Винде стали выносить драйверы в пространство пользователя, отчасти вернулись к идеям, реализованным ещё в её бабке :) Хотя не в том объёме и на другом уровне -- но история движется больше по спирали, чем по кругу.

Тот C++, на котором можно было (и есть сейчас) писать в ядре Windows, аналогично ограничен, как обсуждаемый выше "C++ без исключений без RTTI без переопределений операторов" (без ещё чего-то).
И Microsoft добавляет своих неиспользований того, что GCC и Clang гонят в хвост и гриву.

Я вот думаю так, если ты хочешь учить технологию, то учи её и пиши на C. Если хочешь уметь программировать на C++, то учись программировать на нём. В итоге будет выигрывать тот, кто знает технологию, а не тот, кто много времени потратил на то, как правильно пользоваться языком, тем более, что существует риск неправильной архитектуры приложения в сложных проектах. Не знаю как на C++, но один стример рассказывал, что работал java программистом и у них контора обонкротилась из-за того, что архитектор сделал неправильную архитектуру, то-есть сначала всё шло нормально, а потом посыпалось. Хотя я и не знаю, насколько это отличается от C кода, так как совсем больших и сложных приложений я не писал. Единственное, над чем сейчас работаю, это эмуляторы NES и i386, ну и плюс игровой движок и компиляторы, но всё это на C. В NES, приходилось подолгу вписывать команды, но не думаю, что здесь сложная архитектура. Сложная наверное будет, когда мой эмулятор i386 начнет обрастать всеми нужными модулями или приложения для реверса, которое должно не только отображать код, но и анализировать и декомпилировать.

В целом, мне нравиться, что у Линуса такой принцип. Хоть, кто-то говорит, что на C++ не стал бы писать редактор для рисования, я всё же сделал его. Только там сейчас сегфолтится, но я пока не хочу исправлять. А так, он работал несколько месяцев без нареканий. https://flathub.org/apps/io.github.xverizex.RetroSpriteEditor

В чём суть претензии к RAII так и не понял.

Это другое. RAII непредсказуемо, поэтому вот вам managed-api. Предсказуемо (нет. Точнее ровно как и raii)

Требуются конструкторы/деструкторы с нетривиальным поведением - которые, соответственно, могут натыкаться на ошибки и должны их как то репортить, для этого напрашиваются исключения...и как то не хочется тянуть всё это в ядро.

Исключения плохо комбинируются с деструкторами. Поэтому репортинг ошибок из деструкторов делается не через них.

Угу, с этим ещё хуже и RAII уже становится не таким простым и прозрачным.

Стало модно порочить С++ и восхвалять переход на Rust. Такие себе Ит евангелисты хреновы. И раст будет заброшен.

А к чему столько ненависти?

Действительно...

Архитектура ядра — самая чистая из всех ядер операционных систем в истории человечества

В этом месте я бы поперхнулся бутербродом, если бы он у меня был.

Что, правда, что ли? Вот прямо самая чистая из всех-всех-всех?

Использование кода Rust в ядре Linux добавляет гораздо больше сложностей, чем это сделал бы C++, поэтому ответ на этот вопрос вполне очевиден — «нет».

Не угадали. https://docs.kernel.org/rust/index.html

Когда завтра ваш коллега предложит реализовать какую‑то функцию вашего Go‑приложения на Bython, вы можете рассмотреть подход Линуса.

По сравнению с Робертом Пайком, который на протяжении многих лет руководил разработкой языка Go, Линус - так, молодая шпана в дырявых трениках.

За всё время разработки Go, начиная с релиза 1.0, обратная совместимость компилятора не была сломана НИ РАЗУ. Т.е., код, который соблюдает спецификацию языка и компилируется некоторой версией компилятора Go, будет компилироваться и работать при использовании более новых версий компилятора (ну ОК, пару раз по мелочи сломали, но перед тем очень тщательно проанализировали всю доступную базу кода, чтобы убедиться в том, что реальный ущерб не велик).

А сколько там Пайк думал прежде, чем включить в Go дженерики? Лет, кажется, пять, да?

В следующий раз, когда вам предстоит проходить МРТ в больнице, подумайте о том, хотите ли вы, чтобы томограф мог пробрасывать исключения

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

Бутерброд товарищу в третьем ряду

Пусть поперхнется

Так

обратная совместимость компилятора не была сломана НИ РАЗУ

или

ну ОК, пару раз по мелочи сломали

?

По сравнению с Робертом Пайком, который на протяжении многих лет руководил разработкой языка Go, Линус - так, молодая шпана в дырявых трениках.

Go релизнулся в 2009-м. Линукс в 1991-м. То что Пайк старше Торвальдса не делает его более (или менее) профессиональным. Как говорил Михаил Михайлович: "мудрость приходит с возрастом, но иногда возраст приходит один."

За всё время разработки Go, начиная с релиза 1.0, обратная совместимость компилятора не была сломана НИ РАЗУ

Ну так и в Линуксе действует известное правило: "WE DO NOT BREAK USERSPACE!"

Ну так и в Линуксе действует известное правило: "WE DO NOT BREAK USERSPACE!"

А они не сломали потоки при переходе от 2.4 к 2.6?

А сетевой API (advanced его часть, а не просто сокеты) не сломались при переходе к новому стеку TCP/IP (не помню в точности, в какой версии ядра это произошло)?

Что-то мне подсказывает, что если хорошо покопать, этими двумя примерами список несовместимых изменений не закончится...

А они не сломали потоки при переходе от 2.4 к 2.6?

На сколько мне известно, поменялась реализация внутри ядра. Пользовательские приложения не ломались. Во всяком случае, я про такое не слышал.

А сетевой API (advanced его часть, а не просто сокеты) не сломались при переходе к новому стеку TCP/IP (не помню в точности, в какой версии ядра это произошло)?

Опять же, хорошо бы указать какой конкретно API поломался. И в каком приложении. А то трудно ответить. Во всяком случае, с тех пор как я активно пользуюсь линуксом (~25 лет) ничего подобного не происходило. ifconfig от 2001-го года всё ещё собирается и работает.

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

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

Понятно, критика была в 2004, но выпад на RAII сейчас при наличии managed-api, типа devm_kzalloc() выглядит как двойные стандарты. По мне, только из-за RAII и можно уже использовать плюсы в embedded/ядрах.

Я со статьей согласен, но в ней не хватает одной фразы. Что виртуальные функции C++ можно реализовать с помощью указателей на функции в Си.

А чем так плоха обработка исключений в С++ по сравнению с возвратом ошибок из функций в С? Что в ней опасного? И там и там программа завершится если что-то критическое произойдёт

Если не написать try/catch, исключение улетит выше, вплоть до main, что равносильно падению. Проверяемые коды возврата тоже проблематичны, но они хоть не роняют программу.

А минусы будут?

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

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

Выглядит так что нужно просто писать try/catch, причем исключения то можно перехватывать, дополнять информацию и пробрасывать дальше, тем самым собирая более информативную ошибку и стек вызовов и исключения нельзя игнорировать в отличии от кодов возврата, что потенциально быстрее позволяет обнаружить баг, хотя конечно можно написать catch(...) но даже это быстрее ведет к обнаружению баг.

так это хорошо, программа упадет на первом же тесте и придется добавить catch

И там и там программа завершится

Подумайте, что будет, если эта программа - драйвер какого нибудь устройства или планировщик процессов.

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

- «Вся обработка исключений в C++ фундаментально сломана, особенно в контексте ядер».
- Внедрение обработки исключений в ядро Linux неизбежно сделает его более нестабильным.
- Однако, исключения могут возникать в любой части кода, что делает код, генерирующий их, в некоторой степени непредсказуемым.

много опасностей, но в чём конкретно так и не понятно. Если всё делать аналогично аккуратно как и с возвратами ошибок, то по сути исключения сделают всю ту же самую работу и никаких ращличий тут не видно.

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

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

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

возникла такая ошибка что приводит к падению этой программы

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

Гарантия очень простая - ловите сами и наверх ничего не уйдет.

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

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

Никто не мешает поймать и наверх передать уже код ошибки.

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

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

Там ещё есть проблемы с производительностью и оптимизаторами

Ничем не плоха. Более того, она лучше тем, что быстрее работает, чем куча проверок результирующего кода в каждом стек-фрейме (привет из Go)

Линусу надо завлекать людей на бесплатный труд, но проблема в том, что у желающих поработать бесплатно как правило не все в порядке с головой (как у ганса рейзера). Поэтому инструмент разработки должен соответствовать сообществу. Мягкотелый Страуструп со своим с++ нашитым из лоскутов не подходит. Тут нужда работа "гениев". Людей с горящими глазами которые без всякой рефлексии будут рассказывать о "близости" к железу, битве за наносекунду, параллельно изобретая сишные строки, препроцессор и прочие крутые и гениальные решения. Поэтому градус неадеквата надо повышать. Иначе все неадекватные разбегутся, а других-то и нет.

Линус в своей критике не одинок. Все его аргументы схожи с теми, что называют другие неосиляторы C++.

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

Если бы он в 2004 году узнал, что в C++ добавят лямбды, он бы еще не такую тираду завернул.

А что с нагромождениями Шаблонов в C++, или их уже починили?

Как все меняется... Помню, в 90-х в одном глянцевом IT журнале была статья про разработчика, который накодил на ассемблере с макросами графический редактор под Windows 95. Человек был доволен, что получилась компактная и быстрая программа, а сам этот его псевдоязык для него был не сложнее Си.

Сейчас же обосновывают необходимость такого тяжеловесного монстра как Си++ даже в ядре

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

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

Компакнее иногда, быстрее - скорее нет чем да

Всё таки современный C++ - это далеко не "С с классами".

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

Hosted реализация будет подключать кроме неизбежного рантайма C ещё и рантайм C++, начиная с iostreams - а это объём.

Freestanding может быть урезана, но как именно - надо долго точить конкретный компилятор.

Меня, кстати, поражает, что ядро Linux компилируется в hosted. Не до конца понимаю, как это вообще может работать;\

Ну, я вот под МК пишу на C++, и от неча делать как-то обошёлся вообще без всей стандартной библиотеки (не считая заголовков, нужных для типов вроде std::uint32_t). Абсолютно весь необходимый код библиотеки времени выполнения написал сам на ассемблере, ориентируясь на ругань компоновщика -- причём этого кода оказалось очень мало (грубо говоря, memcpy и ещё несколько подобных функций). Когда добавил статические экземпляры классов, потребовалось дописать вызовы их конструкторов перед вызовом main и вызовы деструкторов после возврата (последнее, понятное дело, фикция в данном случае). В общем, ничего реально тяжеловесного компилятору не требуется. Вот библиотекам -- там да, очень много что может быть, но это уже дело программиста -- использовать стандартные библиотеки или писать своё.

Это, конечно, freestanding в терминологии стандарта, но и ядро ОС -- тоже не прикладная программа и, прямо скажем, принципиальных отличий от прошивки микроконтроллера не имеет (не считая объёма и сложности -- но не "взаимоотношений" со средствами разработки и с аппаратурой), и там тоже не можно, а нужно использовать именно freestanding.

Ядро томографа написано на яве и выбрасывает исключения. :)

другое дело, что в нем "обратная" архитектура, и от ядра ничего критичного не зависит.

Тема шаблонов в статье была тактично обойдена, хотя их упомянули

Что дальше, Rust, Go или даже Java?!

Dlang, естественно!

Не очень понятна проблема вообще. Хочется ядра с крестами и джавой, так форкните и разрешайте. Если сообщество (как пользователей, так и разработчиков) к вам потянется, так и славно, вы победили.

Добавка к C таких возможностей C++, как пространства имён и модификаторы видимости членов классов, как раз очень бы помогла.
А вот дальнейшее развитие - уже ХЗ, как уже говорили в комментариях, уже начиная с переопределения операторов, когда ты не знаешь, что сделает конкретный a+b, становится стрёмным.
И виртуальные функции это уже где-то на грани, надо тщательно подумать, прежде чем допускать.

Виртуальные функции ведь неотъемлемая часть ООП?

Ну, ручные аналоги виртуальных функций в коде на чистом C используют постоянно (скажем, всякие там библиотеки периферии "от производителя", будь то STM, Microchip или кто ещё, набиты структурами, содержащими указатели на функции, и ты должен правильно заполнить такие структуры и передавать их вызываемому библиотечному коду). Почему бы не использовать язык, где всё это делается автоматически?

Переопределённые операторы ничем не отличаются от функций. Я ровно настолько же не знаю, что делает конкретный a+b, насколько не знаю, что делает конкретная add(a, b).

Если + или / не может значить ничего, кроме простого сложения-деления-итд двух примитивных типов, значительно легче следить за происходящим - что у этого нет побочных эффектов. Максимум, что есть, если, например, складываются или делятся два int64_t, int128_t на 32-битной платформе, и это сделано через функцию - но таких функций всего несколько на всю программу. А вот если они могут значить что угодно и надо следить за типами - это усложняет чтение, вплоть до потери следа.

Да, граница не абсолютна, но она есть. Я бы не допускал такое просто так без учёта, где именно что сложно потерять контекст.

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

Тогда нужно отказываться от перегрузок как таковых вообще. Никаких больше add, только add8_8, add8_16, add_16_8 ,и так далее все комбинации.

И что значит надо следить за типами, вы не отдаёте себе, в момент написания a+b, отчёт, что вы делаете?

Я ровно настолько же не знаю, что делает конкретный a+b

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

Пример в статье со структурами очень плох: в каждом экземпляре структуры будет содержаться ссылка на функцию, так ещё скорее всего будет добавлен паддинг между полями, что с лёгкостью увеличит размер экземпляра на 4 + 8 = 12 байт.

ООП это не только "функции у класса", но и, например, инкапсуляция, которая возможна и в Си:

#pragma once

// incomplete/opaque type
typedef struct Account Account;

Account* account_new(void);
void account_delete(Account* this);
int account_get_balance(const Account* const this);
#include "account.h"

struct Account {
    int balance;
};

Account* account_new(void) {
    Account* this = malloc(sizeof(Account));
    this->balance = 0;
    return this;
}

void account_delete(Account* this) {
    free(this);
}

int account_get_balance(const Account* const this) {
    return this->balance;
}

У вас не инкапсуляция, а простое сокрытие реализации.

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

Ну да, обычно делают через промежуточный указатель (в Unix мире он обычно зовётся softc).

Но метод с явными указателями в каждом экземпляре применён, например, в Berkeley DB.

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

Вообще, в классическом Паскале объектов не было -- были только записи (record), которые технически ничем от классических сишных структур не отличаются. Объекты же появились в объектно-ориентированном расширении Паскаля от Борланд, в версии 5, если склероз не изменяет.

Но по существу вопроса согласен: делать ООП на C, используя обычные структуры, вместо того чтобы взять C++ -- ну, это такое.

Очередной RUST enjoyer что ли? Ага да достать пожилую пасту 2007 года и разгонять в контексте текущего времени. И да естественно не упомянуть обсуждение в 2024 с одним из популярных мейнтейнеров ядра который говорит что паходу нынешнему С++ в ядре быть а вот RUST-у нет. Ну раз автор не упомянул сделаю это за него: https://www.phoronix.com/news/CPP-Linux-Kernel-2024-Discuss.

Тоже не понимаю, зачем обсуждать аргументы 2004 года. Очевидно, что за 20 лет ситуация поменялась.

Но по вашей же ссылке можно прочесть:

It was only in 2022 meanwhile that the Linux kernel began moving from C89 to C11. Especially if there is consensus to permit a subset of C++14/C++20 programming in the kernel, it may still be some time before it's adopted to allow for broader compiler support to roll out before raising the base compiler requirements and even if receiving the miraculous endorsement of Torvalds it's not a decision to be taken lightly.

Если стандарты С в ядре внедряются так медленно, то что же будет с С++? Так что вы выдаёте желаемое за действительное.

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