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

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

В отличие от языка 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++.

Сможете собрать UE проект без тулз UBT и UHT? Они буквально собрали свой компилятор поверх компилятора. Примерно также как в Qt.

По мне так товарища совершенно незаслуженно заминусовали. Unreal Engine C++ это совершенно отдельный подвид C++.
И если вы знаете С++ это не значит что вы знаете UE C++. И в обратную сторону это тоже верно.

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

"лишь генерируют необходимые макросы"
это ложное высказывание. Генерируется огромное количество кода за пределами определения макросов.
И да, безусловно, после обработки UHT это всё компилируется обычным компилятором. Сделано это чтобы не заниматься поддержкой базовых фич компилятора.

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

А Qt C++ - не отдельный язык, потому что там отдельная надстройка новые ключевые слова конвертирует в С++ код.

Действительно, ведь slots, signals - типичные поля С++ класса.

Тот же ООП из С++ вполне могли реализовать без отдельного компилятора. Просто нагенерив кучу Си кода для обработки ООП задач. Разве способ обработки кода определяет отдельный это язык или нет?

По всем параметрам UE C++ - имеет достаточно большое количество отличий чтобы иметь право считать его отдельным подмножеством C++.

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

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

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

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

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

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

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

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

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

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

Под плейстейшен или свитч? Совсем не проще.

Извиняюсь, перепутал ветки обсуждения.

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

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

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

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

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

Это диалект.

-----

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

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

Не, конкретно это решается без проблем. Как минимум предоставить тот же рантайм без RTTI и раскрутки стека. Далее запретить исключений типа -fnoexception и даже если где-то случится throw у тебя просто не соберётся код. Плюс регламенты и стили. И нормально получится жить на таком подмножестве, если другие инструменты вполне себе годные, а годноты в плюсах и без этого достаточно.

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

Если же что-то выключено, то это уже "программа на неком подмножестве или надмножестве языка C++". Другим вариантом: использование UB, как DB в некоторых условиях. Правда тут совсем может плохо оказаться, например при смене компилятора... В этом месте с проблемами сталкивался и я.

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

Вопрос ещё, скажем, в выделении памяти в конструкторах - формально с соответствующим аллокатором это можно делать в ядре, но с точки зрения поддержки лучше явно видеть аллокации, чтобы не думать, почему код типа Something a, b, c; a = b + c; делает что то странное с памятью. Не вижу, как запретить такое ключами.

Если же что-то выключено, то это уже "программа на неком подмножестве или надмножестве языка C++"

Это буквоедство. "Программа на С++" это вообще какого рода свойство-то? А если я сначала пишу с выключенными исключениями, тестирую, все дела. А потом ставлю сборку того же кода, но без флажка, и получившийся бинарь в прод. Вот, в проде будет "программа на с++", или нет?

Это диалект.

Это диалект чего? А почему не "вернакуляр", например?

Пишите тогда до конца:

это диалект языка С++

Это диалект чего?

Я написал чего. В первом предложении:

C++ есть смысл отличать от его диалектов...

А почему не "вернакуляр", например?

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

я в принципе не против названия диалект, и не против того что это устоявшийся термин.

Но мне все равно кажется что переход от заявления:

Нет исключений - не C++

претендующего на всеобщность и идентификацию языка С++ через исключения к скромному :

С++ без исключений это диалект языка С++

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

Но мне все равно кажется что переход от заявления:

> Нет исключений - не C++

претендующего на всеобщность и идентификацию языка С++ через исключения к скромному :

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

> Это как утверждать что американский язык это не английский язык.
Аналогии с естественным языком и бытовыми примерами не работают в этом аспекте.

а где в стандарте С++ написано что программист С++ вот прям обязан применять исключения в своем коде?

Парирую: а где в стандарте написано, что программист должен использовать C++?

Прочитайте мой пост внимательно. С точки зрения стандарта, исключения и RTTI - неотъемлеммая часть языка, поэтому та же STL на это полагается и использует. Тот же new тебе не nullptr вернёт без дополнительных телодвижений, а вполне себе бросит исключение. Повторюсь: дополнительных телодвижений. Но все прекрасно понимают, что есть ситуации и условия, когда эти средства языка или не применимы или избыточны. Именно поэтому я написал: де-юре. Де-факто разработчики того же STL не зовут throw напрямую, а через слабые обёртки, типа __throw_XXX, которую в коде без исключений можно реализовать самостоятельно, добавив туда нужное поведение: логирование, перезагрузка, паника и т.п.

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

Ещё раз, вы путаете "юридическую" сторону вопроса и практическую. А конкретно в этом месте вообще пошли в другую степь. Нет, если вы что-то не используете на уровне языка это не делает вашу программу несоответствующей стандарту. Но если вы что-то выключаете/включаете на уровне компилятора и далее в коде полагаетесь на это, используете GNU расширения и иже с ними (включая ассемблерные вставки), то "юридически" вы уже используете не C++, а C++-like язык. Взять тот же Wiring из мира Arduino, который вполне себе C++-like, но в версии для AVR8 там нет STL, а во всех остальных (но это не точно) случаях нет RTTI и исключений.

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

см выше.

И ещё раз: я сам использую в некоторых проекта язык без RTTI, исключений и кучи всего. Потому как минимум шаблоны и RAII мне куда больше нужны. Просто узрите разность между "юридической" и практической стороной вопроса: если в стандарте что-то описано, вы попытаетесь это использовать, а вам скажут - нет (компилятор), то это уже не соответствует стадарту.

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

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

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

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

Естественно, но никто не заставляет при компиляции с флагами по умолчанию использовать расширения. В спорных вопросах лучше ходить на cppreference, а не просто проверять, что код компилируется.

Мне кажется не совсем верно понят контекст. Здесь вообще не шло речи о том, что кто-то чего-то заставляет или не зставляет. Также не шло никакой речи о том, как делать можно и как нельзя. Речь лишь шла о том, почему полезно различать C++ и его диалекты не только с формальной точки зрения, но и с практической.

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

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

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

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

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

Отвечая на вопрос выше: нет, стандарт C++ не требует наличия throw\catсh в коде, но стандарт требует наличия обеспечения этих возможностей в компиляторе. Если компилятор не поддерживает эти возможности (или вы\мы их насильно отключили), то этот компилятор (или его текущая конфигурация) перестает считаться компилятором С++. Формально. Но тут есть нюанс. Весь этот формализм начинает стрелять по ногам во вполне реальных ситуациях.
Допустим мы отключили исключения в своем коде и вызываем некую библиотечную функцию (не нашу), которая написана на С++. И эта функция бросает исключение. Стандарт С++ описывает что должно произойти, если в наш код попадёт это исключение, что произойдет если мы его поймаем и что произойдет, если не поймаем. Но наш код скомпилирован без поддержки исключений вообще. Что произойдет? Неизвестно. По крайней мере стандарт С++ тут нам уже точно не помощник. Так к чему же обращаться, как разъяснить вопрос? Правильно, мы обращаемся с документации компилятора и там скорее всего будет написано, что произойдет в этом случае. Но документация - это не стандарт С++. То есть буквально у нас теперь на руках код, который похож на С++, но часть поведения этого кода не описывается стандартом С++ и стандарт С++ даже не ссылается на какой-то implementation-defined источник, как это бывает в других случаях.

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

Нет. Но если вы напишете их, то это должно собраться и правильно работать.

оно точно так же должно собираться и работать без них. Где проблема?

Вы действительно не понимаете?

Проблема в том, что для работы пресловутых try/catch и rtti нужна поддержка со стороны рантайма C++. Вы писали рантайм для плюсов? Я - да. Образца C++11, но всё же. Реализация этого рантайма будет существенно отличаться от пользовательского в режиме ядра. Более того, если будет наложено ограничение на использование в ядре на исключения, то резонно, эту часть рантайма не реализовывать. Но, внезапно, стандарт языка требует, что бы поддержка была и ваш код свалится если не на компиляции, то на линковке, если будут прямые try/catch/throw или косвенные (new, STL) вызовы выброса исключений. Поэтому к компилятору прикручены ручки, типа -fno-exceptions, -fno-rtti, -nostartfiles, что бы и этого избежать. Логично, что в гипотетической сборке кода под ядро на C++ эти параметры будут присутствовать.

Так вот, если у вас будет ВАЛИДНЫЙ с точки зрения стандарта C++ код с искключениями, то он у вас не соберётся. А значит, в данном случае у вас уже не совсем валидный C++.

Я потому и делаю упор (немного откорректирую акценты): де-юре, если у вас не собирается код в котором есть или возможны вызовы try/catch/throw, это это у вас уже не C++. Повторюсь: с точки зрения стандарта. В стандарте нет опциональных вещей. И тут спорить не о чем. Просто жить. Всё.

По факту ("де-факто"), в "разговоре" естественно никто не будет делать различий между 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 были. а С++ так не пойдет.

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

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

Но это делается один раз, отлаживается за пару деньков)

Нет. Штатная функция. Много модулей есть 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 драйверы файловых систем всегда были задачами (процессами) режима пользователя, а не драйверами режима ядра -- в ядре крутились драйверы, прямо работающие с железом (и то, строго говоря, это не было совсем уж обязательным -- можно было написать "железный" драйвер, работающий как задача). Так что, когда в Винде стали выносить драйверы в пространство пользователя, отчасти вернулись к идеям, реализованным ещё в её бабке :) Хотя не в том объёме и на другом уровне -- но история движется больше по спирали, чем по кругу.

В плане FS есть fuse, но скорость ему это не добавляет. Скорее всего тут имеет место компромисс между скоростью и безопасностью. Я как-то пытался делать... гм... драйвер для устройства по I2C: hdmi свитч. Будучи в пользовательском пространстве работало плохо, но как стартовая точка оказалось норм. Плюс иногда нужны достаточно быстрые реакции на прерывания. В user-space я что-то слабо представляю как это сможет работать. Но я так себе теоретик ОС.

 Скорее всего тут имеет место компромисс между скоростью и безопасностью.

Поэтому итог спора между Торвальдсом и Танненбаумом таков, каков есть.

Я как-то пытался делать... гм... драйвер для устройства по I2C: hdmi свитч.

Вот кстати вопрос по I2C (на данный момент знания практически нулевые) - там есть стандартная энумерация всех устройств (т.е. если ОС хоть что-то знает про I2C - скорее всего до нужного устройства долезть можно, может не хватать реализации протокола обмена данными) или нужен отдельный драйвер, чтобы просто достучаться до нужного устройства? Пытаюсь понять, насколько сложно запилить I2C HID в 9front - сейчас там на эту тему что-то такое.

там есть стандартная энумерация всех устройств

я имел дело на системах без BIOS, через DTS, там устройство описывалось через DTS собственно указывалось родительское устройство контроллера i2c и адрес на шине, через compatibility выбирался и загружался драйвер и передавались необходимые параметры. Если очень грубо: то автоматической энумерации нет, ты явно должен сказать, какому адресу на какой шине какой драйвер соответствует, дальше запустится probe, если он уже сможет корректно идентифицировать устройство, то оно появится в работе, если нет, то нет.

нужен отдельный драйвер, чтобы просто достучаться до нужного устройства?

есть, условно, универсальный: i2cdev, который представит шину как /dev/i2cNUM. Дальше через ioctl можно работать с устройствами на этой шине. Но у меня случались зависания шины при работе из user-space. Правда там обмен был крайне активным. Конкурентный доступ при этом вполне разруливается ядром.

Спасибо, выглядит так что всё не особо просто (причём в данный момент в 9front код для i2c в обычное x86-64 ядро не влинковывается, так что он может быть заточен для других железок).

Тот 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 в ядре 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 тоже известно.

Где выделяется память на пробрасываемое исключение и кем? Плюс даже если ничего не делать без пометки noexcept компилятор этого не знает и мусора все равно накинет, будет искать catch блоки где их в принципе нет. Плюс можно поймать то, что не для тебя летело. Строгая иерархия спасает, но без них жить и проще и быстрее.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Некоторым "осиляторам C++" для начала бы стать осилятором здравого смысла.

Как-то неосиляторы C++ резко перешли в класс осиляторов Rust. Интересно, как же так.

нет цирка с молчаливым преобразованием разных типов указателей

Зато цирк с bool b = 2.5 никуда не делся.

А что с нагромождениями Шаблонов в 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.

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

Протестую, компилятор C++ очень даже генерирует. Например, конструкторы копирования всем и каждому. И его надо уговаривать не генерировать, через написание дополнительного кода.

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

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

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

Что дальше, 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.

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

Я тоже мечтатель получается. Ну да. Понятно что С++ до сих пор до ядра линукса как до луны. Но тут и тема такая.

Вот интервью, свежее (месяц): https://www.youtube.com/watch?v=7WbREHtc5sU.

В нем нет ничего, где второе лицо ядра Linux подтверждает ваш вброс. Как раз таки наоборот - Rust есть и будет. А вот про сипипи всякие никто даже не вспоминал.

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

Автор статьи не понимает ни того, что имел ввиду Линус, ни самой концепции ООП на классах.

Чтобы понять, почему модульность и повторное использование с классами становятся только хуже, советую почитать статью https://habr.com/ru/articles/885980/.

Кто знает, что будет дальше: Go, C# или даже Java

Запрет С++ но разрешить Java и C#, это по мнению автора логичное предположение.. мне кажется это уже диагноз.

Невозможность писать ядро Линукс на С++ это есть критика С++? Ядро линукс ещё невозможно писать на SQL, Python, XML, Wolfram и на многом другом. Читаю: Линус Торвальдс: Критика C++ — Комплексный анализ. И думаю что будет критика С++. Читаю ради интереса, и тут описывается почему нельзя писать ядро Линукс на С++. Вы серьезно? И вообще, кто пишет ядра Линукс? Кого должны заботить проблемы использование С++ в написание ядра? В практическом смысле никого. Есть те кто пишет ядра, но Торвальдс поименно знает каждого, и лично контролирует каждый пуш. Многие скажут что писали свои OS, но на самом деле в лучшем случае написали boot loader и какой-то элементарный текстовый или гравический UI для ввода комадны Exit

Глянул реализацию "ООП" из статьи на языке Си, и мне, как начинающему разрабу на крестах, кажется дикостью. Как будто берёшь и изобретаешь велосипед, ещё и нечитаемо + небезопасно, так как выделяется получается память в куче, а не стеке. То есть надо каждый раз вручную освобождать память, хотя уже есть умные указатели в С++ и сами по себе классы, если не выделять их намеренно в куче, будут сами уничтожаться, выходя из стека. Если ошибся где-то, поправьте.

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

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