Pull to refresh

Comments 155

Ну так-то единороги все ещё существуют:


https://www.unisys.com/offerings/clearpath-forward/clearpath-forward-products/clearpath-forward-dorado-systems


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

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

Да я думаю вполне себе пишут. Вот тут:

unite.org/wp/wp-content/uploads/2014/08/OS3035.pdf

в списке языков для разработки на Dorado наряду с COBOL, FORTRAN, MASM и C есть даже Java и PHP.

Просто видимо считается что им на их век и более старых стандартов C хватит.
Просто видимо считается что им на их век и более старых стандартов C хватит.

Возможно.


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

IBM PowerS на платформе IBM i (бывш. AS/400) — мейнфрейм?

Там волне себе пишут и на С и на С++. Они входят в состав ILE (Integrated Language Environment — туда входят C/C++, CL, COBOL, RPG) и поддерживаются на уровне самой ОС (т.е. все библиотеки и компилятор входят в состав ОС) — компиляция производится командой CRTBNDC (команда системного языка CL). Для С++ — CRTBNDCPP.

Поддерживается там С11. Ну плюс некоторые специфические для платформы расширения типа поддержки типов с фиксированной десятичной точкой, поддержка специфических менеджеров памяти типа QuickPool, поддержка работы со специфическими типами объектов типа *SYSPTR… Расширение для работы со структурированными файлами (таблицы, индексы, дисплейные и принтерные файлы — RECIO).

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

Ну, такой Си это не вполне и Си даже, пожалуй :-) по крайней мере комитет их в расчёт не особо берет.

Почему? С11 там поддерживается в полном объеме. Плюс расширения, характерные для платформы (платформа очень специфическая во всех отношениях).

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

Что предлагается сейчас? Ставишь атрибут и получаешь на выходе кучу кода, который генерит компилятор за тебя. Все в угоду тем, кому слишком сложно думать о последствиях того, что они пишут. Ну сложно — есть много других, более высокоуровневых языков. Кто заставляет именно на С писать?

Я сейчас вполне свободно пишу на RPG потому что на нем проще писать многие вещи под AS/400. Но при этом всегда могу то, что требует эффективности и прозрачности С, написать на С. Та же работа с памятью, динамические списки и проч. Все то, чего мне нехватает в RPG, или реализуется там коряво, я просто напишу на С и вызову из RPGшной программы Сшную функцию (не говоря уже о том, что из RPG можно напрямую вызывать функции C RTL просто правильно прописав прототип). Да, при этом нужно понимать что такое передача параметра по ссылке или по значению, что такое манглинг и в каком порядке параметры функции передаются через стек. Может кому-то это сложно… Может кому-то само понятие указателя в голове не укладывается. Но зачем опускать С до уровня школьников? Ради дешевой популярности?

Плюсы изгадили, теперь до чистого С добраться решили?
(платформа очень специфическая во всех отношениях).

Точно. Стандартный Си на мейнфрейме — нечто инородное, и оттуда растут всякие действительно нестандартные расширения.


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

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


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

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


Наличие же атрибутов в синтаксисе современных диалектов Си — факт жизни, они есть во всех взрослых компиляторах.


Кто заставляет именно на С писать?

Жизнь. POSIX и иже с ним определены и написаны в терминах Си. Си и *никс вообще разделить трудно, их разрабатывали совместно, и параллельно стандартизировали. Поэтому если вы работаете с этими ОС, то от Си никуда не деться.


теперь до чистого С добраться решили?

Ага, кошмар! Отморозки удумали исправить — супераккуратно! — обработку ошибок. Да и каких нелюдям захотелось упростить высвобождение ресурсов?! goto это так удобно!


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

Точно. Стандартный Си на мейнфрейме — нечто инородное, и оттуда растут всякие действительно нестандартные расширения.


Ну, вообще-то AS/400 (точнее, OS/400, ныне называемая i5/OS) есть объектно-оринтированная ОС, написанная на С++ практически полностью (если почитать книгу одного из ее отцов-основателей Френка Солтиса «Основы AS/400»). Основная специфика платформы — концепция «все есть объект». Там нет файлов в привычном для Win и *nix понимании — там есть «объекты», характеризуемые именем и типом, каждый объект обладает своим набором свойств и с любым объектом можно производить только те действия, которые определены для него системой. Ну, к примеру, вы не можете взять, к примеру, программный объект и в HEX редакторе поправить в нем байтики — свойство редактирования для этого объекта системой не поддерживается.

Так что С и С++ там вполне себе как родные. Просто есть набор специфических расширений, типов и прагм сверх стандарта.

Больше скажу, я отдельные алгоритмы, не привязанные к платформе, вообще пишу и отлаживаю под Win на miGW в режиме C11. А потом переношу на ASку (там чтобы отладиться, надо сначала на локалке написать, потом забросить на сервер, там собрать и дебажить в эмуляторе терминала IBM5250, да и дома доступ до сервера AS появился только с переходом на удаленку, есть правда, публичный PUB400.COM, но там совсем неудобно работать — тот же gradle, который у нас настроен на наш сервер, туда не заточить).

А нестандартность расширений… Ну считайте это дополнительными библиотеками. Типа RECIO, позволяющей работать с физическими (таблицы), логическими (индексы) файлами. По идеологии что-то типа Paradox Engine (если кто помнит) для борландовской БД Paradox времен доса и вин 3.х.

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


Я не про оптимизацию. Я вот про такое:

Обратите внимание на атрибут oob_return_errno. Он означает, что из этой функции-шаблона будут сгенерированы следующие функции:

Возвращающая структуру с флагом ошибки и результатом работы функции (struct {T return_value; int exception_code}).
Возвращающая результат работы функции и игнорирующая возможные ошибки в аргументах, приводя к неопределённому поведению.
Завершающая выполнение в случае ошибки в аргументах.
Заменяющая errno, то есть обладающая привычным поведением.


Или вот такого:

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


На ASке, с ее концепцией групп активации (Activation Group) как некоторого контейнера внутри задания (job) это может вести себя очень странно.

Суть в том, что если ваша программ работает в своей группе активации, то когда вы ее вызовете несколько раз в рамках одного задания, значения статических и глобальных переменных сохраняются между вызовами. Понимание этого механизма позволяет существенно повышать эффективность за счет выноса всего чего можно в блок инициализации, который будет отрабатывать только при первом запуске программы в рамках задания.
Это относится к чтению настроечных *dtaara (достаточно «тяжелая» операция), подготовке динамических параметризированных sql запросов (операции declare/prepare) с тем, что потом их просто открываешь с параметрами (open… using ....) не тратя каждый раз ресурсы на построение и т.п.

И вот такого:

Роберт Сикорд (англ. Robert Seacord), автор «Effective C» и член комитета, признался, что работает над предложением в стиле ключевого слова defer из Go


Ну хочется вам чтобы было как в Go — может проще писать сразу на Go?

Мне вот не приходит в голову работать с типами с фиксированной точкой (packed, zoned) в С/С++. Хотя формально их поддержка есть, но неудобно. Все это прекрасно делается в RPG (зачем они вообще нужны? для коммерческих рассчетов — см. рекуррентное соотношение Мюллера).

Я просто наелся уже всех этих умностей со стороны компиляторов. На прошлом месте занимался разработкой распределенных систем с гарантированным временем отклика (на С++, а вообще на С начал писать в концу 80-х). И напрарывался на то, что когда начинаешь использовать stl со всеми ее шаблонами, программа в критических местах начинает тормозить (ну в нашем тогда понимании). Пришлось всю эту лабуду выкинуть и базовые алгоритмы прописать на чистом С руками. Именно потому, что там знаешь что оно сделает так, как написал. Особенно, если еще оптимизацию отключить (тоже напарывался что критичный по скорости код лучше не оптимизировать компилятором).

Так что моя позиция в том, что с плюсами делайте что хотите, а чистый С оставьте таким какой он есть. С минимальными изменениями в плане синтаксиса (типа добавить bool и подобные вещи).
Что-то ничего особо интересного, кроме разве что включения бинарников (для программирования МК — весьма полезная вещь) не вижу.
Большая часть «нововведений» уже давным-давно поддерживается gcc!
P.S. А что значит «слишком большая программа на С»? Больше, чем 2 объема ядра?

"слишком большая программа на С" — та, что больше нескольких десятков тыс. строк кода :-) Но это, конечно, одна из тех шуток, в которой есть доля шутки.

Я ядро не зря упомянул!
Пишут, что в современном ядре больше 24млн строк сишного кода!!! И это — нормально.
Сам я, понятное дело, столько ни в жизнь не напишу, но и у меня есть штуковины на пару-тройку десятков тысяч строк.
Главное — придерживаться иерархии: разбивать логические модули на файлы, да следить, чтобы в файле было не больше 500-1000 строк (иначе в нем даже при помощи IDE ориентироваться сложно). Вон, в libSOFA вообще пошли таким путем: каждой функции — свой файл. Правда, процентов на 80 каждый файл заполнен документацией, что очень здраво сделано.
Относительно других ЯП, единственный язык, который может хоть как-то конкурировать с С — это С++. Но, учитывая то, что в реальном мире крайне мало задач, которые требуют применения ООП, С остается «царем горы». И будет таковым очень долго. Хипстеры со всяким отстоем вроде go, rust, python помрут вместе со своими недоязычками, а С останется!

Эдик, ты что тут делаешь? Тебя на ЛОРе заждались.

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

Публику для общения.

Что-то ничего особо интересного, кроме разве что включения бинарников

Да некоторые компиляторы это тоже поддерживают, например вот код для ADSP-21369:

const float pm coeffs_fir[TAPS] = {
   #include "data/37500.dat"
};

Ну нет, #include — это стандартная команда препроцессора для текстового включения.


Этот способ работает на любом компиляторе, но не с бинарными файлами, а со специально подготовленными для такого включения.

И правда, ошибся, почему-то об обычных массивах подумал. В случае именно с бинарными файлами без #embed не обойтись. Там получается и выравнивание можно задать и ограничение по размеру, если кто-то захочет /dev/zero или /dev/urandom подключить.

Мало всем было include path, теперь ещё будет и embed path похоже. Но очень радует, что не нужно будет ручками/скриптами/системой сборки бинарные файлы в hex переводить для всяческих мк.


P.S.: а в плюсы embed не планируют завезти? А то ещё одна несовместимость будет и это печально.

Думаю, такие вещи будут унифицированы. Обычно WG21 старается хотя бы минимально синхронизироваться с WG14.


Но вообще давно уже надо переставать смотреть на С и С++ как на родственные языки. Это очень, ОЧЕНЬ разные языки и сообщества.

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


extern "C" {
    #embed char "blabla.txt"
}

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

Но, как мне кажется, основные компиляторы его всё равно (нестандартно, ясен пень) портируют

Т_Т

А как extern поможет, если данное ключевое слово обрабатывается позже, чем стадия препроцессора?

А вы про какое конкретно предложение?


Да и, признаться, я с нецелыми числами особо не работаю

Ну там во многих главах стандарта изменения, связанные с decimal появились. Как минимум, в февральском (2020) официальном драфте они уже есть. Само предложение — N1312 draft of ISO/IEC WDTR24732 — старое, но, видимо, сейчас его решили в стандарт включить. Хотя, gcc уже давно это поддерживает. За какой конкретно, proposal, проголосовали, сказать не могу, но вот, например, есть ссылка на gcc, где говорится, что decimal ISO включили в c2x https://gcc.gnu.org/legacy-ml/gcc-patches/2019-10/msg00866.html. Немного странным является то, что в драфтах c2x в списке изменений ничего про decimal не сказано, хотя в самом тексте драфта, изменения связанные с добавлением decimal, подчёркнуты синим, что говорит о том, что в C18 decimal ещё не было.

Включение двоичных файлов в исходный файл

Для этого есть objcopy. Причём этот вариант более гибкий, так как позволяет настраивать расположение двоичных данных.

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

С objcopy вставка будет на этапе линковки (если я ничего не путаю), а значит:


  • для доступа нужно делать не очень красивое разыменование линкерного символа
  • на этапе компиляции ничего не проверить — а теперь ведь будет static_assert

А расположение и тут можно будет настроить с помощью атрибута у массива.

Реформа обработки ошибок в стандартной библиотеке

Какие костыли лепят, лишь бы нормальные сумм-типы не вводить.

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

Хм, а почему бы просто не написать:
int main() {
    return 0;
}

или для красоты по старому стандарту:
int main(void) {
    return 0;
}

Да main писать легко, надоедает повторяться в других функциях. :-)

UFO just landed and posted this here

Вот и мне логика непонятна. В том же C++ возможность опускать имена аргументов привела к невозможности использования сокращённого синтаксиса лямбд (т.е. возможность не писать auto).


Плюс ещё один момент: велик соблазн опускать имена аргументов в forward declaration. То есть хочешь посмотреть, что делать функция, переходишь на её объявление, а там хренушки — три инта в аргументах и всё.


Как по мне, так лучше бы, наоброт, запретили опускать имена аргументов и в C, и в C++.

Ну будут значит писать int dot(int i0, int i1, int i2); делов-то.

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

А что мешает сделать игнорируемый идентификатор _E, например. Вроде, всё что начинается с _Большаябуква является системным. Ну и будут идентификаторы _E1, _E2 и т.д., на которые не будет предупреждений. Или это противоречит стандарту?
В дополнение к общей картине по современному C нужно сказать, что ведется и такая работа — A common C/C++ core specification, а так же упомянуть замечательную книгу от автора этого блога — Jens Gustedt, соредактора текста последнего стандарта C18 (ISO/IEC 9899:2018), — Modern C, которая свободно доступна.
Ну и собственно сам последний драфт C2x (working draft — February 5, 2020) — n2479, в котором можно посмотреть текст актуального C18(C17) 9899:2018.

Только, вряд ли, A common C/C++ core specification будет принята. Это, получается, некий новый язык, который не соответствует ни C, ни C++, да и ещё легаси ломает. Тут, вон, даже сравнение указателей абсолютно по-разному на C и C++ себя ведёт.

Да, Modern C — действительно очень редкая и толковая книга. Добавлю-ка ссылку на нее в статью!


Про С/C++ Core и связанные вопросы совместимость я сознательно опустил по идеологическим причинам. :-) По мне так этим двум языкам давно уже надо развязаться.

По мне так этим двум языкам давно уже надо развязаться.
Возможно так. Но наверно будет не совсем хорошо, если при развитии в C завезут фичи, которые уже есть в C++, а они будут синтаксически или лексически реализованы по-другому. Вот эта общая спецификация, я так понял, есть попытка как-то формализовать одинаковости/различия в свете данного аспекта. Если оба комитета договорятся и какие-то базовые вещи в обоих языках будут вести согласовано, будет хорошо.
UFO just landed and posted this here

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

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

Во-первых, есть, это типы, а во-вторых, чем это хорошо?

Включение двоичных данных из файлов в исполняемый файл

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

Для подобного есть куча разных приблуд вроде github.com/gobuffalo/packr Ходят разговоры, чтобы такое добавить даже официально.

Конечно есть. Только у всех этих решений есть одна проблема (такая же как и у моего): стоит вам взять файл размером в 5-7 мегабайт, преобразовать его в hex-последовательности (в слайсах байт) в исходном файле *.go, как у вас им начинает давиться — я точно не уверен, кто конкретно — лексер? парсер? компилятор? Но кому-то из этого списка точно плохо становится.

Оператор defer

Чудеса, неужели goto можно будет окончательно закопать.

Вызываю пояснительную бригаду!
"Если пример вам ни о чём не говорит, то загляните в документацию для Linux по адресу man 3 exec, там будет пояснение."
Читаем про эту функцию: The list of arguments must be terminated by a NULL pointer, and, since these are variadic functions, this pointer must be cast (char ) NULL
Но при этом известно, что "Константа нулевого указателя, приведённая к любому типу указателей, является нулевым указателем." Как так? Ну есть у нас функция с переменным количеством аргументов (как printf), да, к последнему именованному аргументу этой функции и всем неименованным применяется default argument promotion… Ну и что? Разве приведение к (char
) отменяют default argument promotion? Всё равно ведь отправится указатель не на 1 байт, а на sizeof(int) 4 байта, нет?

Тут предполагается, что существуют платформы, в которых внутреннее представление NULL указателя на разные типы различается? Но тогда непонятно, как поможет введение nullptr.

Забыл ссылку на предложение вставить в наш текст.


Между тем, там есть пояснение:


A NULL argument that is passed to a va_arg function that expects a pointer can have
severe consequences. On many architectures nowadays int and void* have different size,
and so if NULL is just 0, a wrongly sized arguments is passed to the function.

В переводе на русский:


Если NULL представлен 0 типа int(возможность этого — требование стандарта), и на заданной платформе указатели и int имеют разную размерность, то в случае va_args правильный размер аргументов для приведения (как происходит в случае обычных параметров функций) компилятор вывести не сможет… БА-БАМ-АМ! Поздравляю, вы, к примеру, только что передали на стеке два байта вместо положенных четырех.


А вот nullptr это всегда указатель эквивалентный ((void*) 0), и приведение к int для него запрещено.


Вообще, взрослые дядьки иногда даже рекомендуют переопределять NULL как ((void*) 0) для безопасности.

Вот как сейчас выглядит NULL в системных заголовочных файлах и в msvc, и в gcc в линуксе
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
Для С++ это актуально, да, при передаче NULL в variadic функцию действительно получим int 0. Но в С то это указатель.

Поговорим о платформе, где язык Си является абсолютным доминантом — ARM. ARM Ltd выпускает среду программирования для архитектуры ARM:
из файла
/ stddef.h: ANSI 'C' (X3J11 Oct 88) library header, section 4.1.4 /
/* Copyright © ARM Ltd., 1999
...


define NULL 0

Что ж, ARM Ltd желает удачной отладки, коллега!

defer, пожалуй единственное, на что вырвалось «вау». Сам люблю чистый C, хотя на работе давно пишу на плюсах. И вот что было бы реальной бомбой, имхо, это шаблоны. Перегрузка тоже сильно упростил бы код, т.е. написать, условно, numbertostr(int), numbertostr(unsigned int) вместо inttostr(int), uinttostr(unsigned int)…

Перегрузка, шаблоны… Вы же и так пишете на С++, нет? :-)

Пишу, и? Мне нравится C за чистоту, прогнозируемость, простоту… Перегрузка и шаблоны сделали бы код более чистым.

Согласен, мне тоже нравится C за простоту относительно того же безбрежного C++.


Но в конечном итоге C ведь тоже тот еще бардак, признаемся :-) Но ма-а-аленький и обозримый бардачок.

Да, ОБОЗРИМЫЙ бардачок, в плюсах его обозреть сложнее ;)
Вы можете писать в стиле С с перегрузкой и шаблонами, не таща весь «бардак» с++ и будет у вас аналогичная чистота/простота.
Безусловно, но собирать это придётся плюсовым компилятором, он будет в исполняемый файл тащить кучу лишнего. Если можно шаблоны и перегрузку, то можно и классы, exception-ы, «вот тут временно std::map, потом перепишу» и так далее.

В плюсах структуры – это классы и вроде как нехорошо делать memset(&object, 0, sizeof(object)) и так далее и тому подобное.

В итоге получится мусорка, был подобный опыт. А если этот кастрированный C++ потом кому-то поддерживать? Нет, мешать их нельзя имхо…
он будет в исполняемый файл тащить кучу лишнего
комитет с++ занимаетя выделением embedded в отдельное подмножество языка
В плюсах структуры – это классы и вроде как нехорошо делать memset(&object, 0, sizeof(object)) и так далее и тому подобное.
ну с standard layout структурами то можно так делать. А под это определенение попадают все структуры, которые валидны и в си.
UFO just landed and posted this here
UFO just landed and posted this here
А std::map вам чем не угодил, особенно если к его производительности претензий нет?

Очевидно, тем что у него внутри есть конструктор, деструктор и плюсовый бардак :-)

Перегрузка и шаблоны сделают код только грязным, малопрогнозируемым и сложным. Непонятно, зачем это тащить в относительно синтаксический простой язык как С. Особенно с его корявыми правилами приведения типов. У нас уже есть один С++.
Аргументы? Первые 4 функции мне кажутся чище последних 4-х:

void append_to_str(str_t *s, int n);
void append_to_str(str_t *s, unsigned int n);
void append_to_str(str_t *s, long n);
void append_to_str(str_t *s, unsigned long n);

void append_to_str_int(str_t *s, int n);
void append_to_str_uint(str_t *s, unsigned int n);
void append_to_str_long(str_t *s, long n);
void append_to_str_ulong(str_t *s, unsigned long n);


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

Шаблоны МОГУТ выглядеть чище макросов, но часто оказываются настолько же сложными и невозможными в отладке. Я думаю здесь стоит смотреть куда угодно, но не в сторону С++, чтобы черпать вдохновение.
Возможно что-то из обобщённого, да. Хочется перевести портянки подобных функций во что-то более элегантное.

Ровно до того момента, пока вы не решите экспортировать эти функции.

_Generic уже в C11 ввели, он как раз под данные задачи подходит. Единственное, он не умеет работать с динамическими типами, но их в C++ нет. Type Generic математические функции ещё с C99 в стандарте есть.

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

Только вот, в случае, C подхода можно точно сказать какие символы будут внешними. А вот при обычном использовании шаблонов — нет.

Лучше бы они «параметром» defer сделали выражение, как в Go:
defer fclose(file1);

Еще не факт, что вообще что-то появится, Р.Сикорд еще даже предложение не написал. Это мой личный синтетический пример.

Вот так, через парочку итераций стандарта, C наконец-то станет точным подмножеством C++.

Пока тенденция обратная: языки расходятся все больше. Настолько, что предлагается обсудить хотя бы общее ядро.

Пока тенденция обратная: языки расходятся все больше. Настолько, что предлагается обсудить хотя бы общее ядро.
больше половины изменений явно позаимствованы из с++, кроме strdup/strndup, обработки ошибок, defer и embed. В с++20 тем временем приняли designated initializers и хотят сделать embed (последний будет по-другому реализован). Я бы сказал что языки расползаются не больше чем сходятся, а общее подмножество соответственно растет.

Даа, а designated initializers в С++ выглядят так же, но работают немножко иначе.

Обработка ошибок и embed тоже вначале появились в качестве пропозалов в C++, и только затем были предложены в C.

UFO just landed and posted this here

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

Пытались уже. Бьёрн Страуструп «Дизайн и эволюция C++», глава 2. Аппетит приходит во время еды.
Функции strdup и strndup

Функции содержат неявное выделение памяти в куче. Для программирования под МК это прям печалька, особенно когда нельзя скопипастить кусок кода без переделки вот таких моментов. И еще одна проблема — как использовать нестандартную кучу?

Безымянные параметры функций

Ох и бардак же начнется.

Включение двоичных файлов в исходный файл

ИМХО: единственное реально ценное нововведение.

Прощай, NULL, или nullptr на низком старте

Странно это все, почему бы не заставить работать NULL так, как от него ожидается (да, привести тип к указателю)? Что именно это может сломать?

Оператор defer

Забавная штука, но отложеные вызовы не совсем в духе C (кусок кода накапливается по ходу программы, а потом выполняется). Кода нет, а его выполнение есть — в C так не носят, понятно что революция, но вот надо ли оно… Лучше бы дали возможность написать такое макросами.

Лучше бы дали возможность написать такое макросами.

Чтобы был не стандартный defer, а свой, родной и кривой?

Ну не обязательно кривой, хотя тут кому как нравится. Если просто разрешить накапливать код в макроопределении, то это откроет возможность реализации defer и других подобных инструментов, но не будет так явно выделяться. К примеру, есть #include, он позволяет просто воткнуть один файл в другой. Для предотвращения повторного включения используется конструкция из #ifndef #define, все хорошо, но можно было сделать какой-нибудь require_once. Инструмент получился бы хороший, но не гибкий, нужны и перечисленные выше. Получается раздувание языка, а C вроде как хорош своей компактностью и логичностью.
Для предотвращения повторного включения используется конструкция из #ifndef #define, все хорошо, но можно было сделать какой-нибудь require_once.

Уже давно есть #pragma once.

В стандарте?
Если рассуждать не о внесении в стандарт, а о расширении компилятора, то любая вменяемо проработанная функциональность приветствуется.
Кода нет

В смысле нет? Блоки defer все видны явно. Накапливаться оно будет или в block scope или function scope. Оно очень надо, потому что без него работа с ошибками и чистка ресурсов настоящая пытка в С и единственная причина, почему goto в этом языке все еще полезно и широко используется.
Блоки видны явно, но распределены по коду и находятся не в том месте, где будут выполнены. На мой взгляд, это несколько усложняет чтение кода, особенно при пошаговой отладке. Это нормально, но как-то не в духе языка.
По опыту Go, оно того стоит. Это чрезвычайно мощный инструмент. И не помню, чтобы из-за этого сложно было код читать. Хотя тут важное замечание, если писать нормальные функции, а не портянки по несколько тысяч строк, где конечно в defer запутаешься.
Угу. Инструмент классный, но нет ли способа его как-то упихнуть в более сишный вид. Я уже предлагал вариант с накоплением в макроопределении. Сразу скажу, согласен с тем, что это не решает проблему с выполнением кода при выходе за пределы области, придется перед выходом явно вызывать. Но такой подход позволяет «нагрузить» на это выполнение дополнительный функционал. Возможно, имеет смысл рассмотреть (в рамках текущего мысленного эксперимента) создание аж целых двух новых инструментов: дополняемых макроопределений и привязки исполнения «функции» при выходе из скоупа, причем записывать вызов этой «функции» в конце скоупа (чтобы логику не ломать, после условного return перемещаемся в конец скоупа, а там нас ждет кусок накопленного кода, это уже можно представить и визуализировать). В таком случае можно не только получить полноценный функционал defer, но и расширить его для сложных случаев.
Хорошо, в чем я заблуждаюсь?
Что именно это может сломать?
весь код который использует NULL как 0, а такого явно много
Оно понятно, но у меня не хватает фантазии такое представить. Использовать NULL как константу в арифметике — это странно, даже в случае с арифметикой указателей. Вот и спрашиваю, вдруг кто видел подходящий пример.

По поводу strdup: данная функция использует malloc для выделения памяти. Если вы как-то по другому выделяете память (на стеке или mmap, или ещё как-то), то просто воспользуйтесь strcpy. Если вам нужно просто создать строку где-то вызывайте strdup, (на Linux, например, malloc может выделить память как в heap, так и напрямую вызвав mmap) если вам нужно конкретно где-то выделить память, то выделите её, а затем скопируйте туда строку (strcpy)

Я воспринимаю C как язык, код на котором должен легко переноситься между платформами. Существует множество платформ, на которых динамическая память почти не существует. И если брать кусок кода, написанный по стандарту, то в нем нужно искать только *alloc и думать что с ним делать, а теперь добавляется еще 2 функции, которые могут выделять память и даже не содержат в имени подстроку «alloc». И эти 2 функции легко реализуются через уже существующие, так что я не понимаю зачем их тащить в стандарт.
Согласен, это можно переписать на strcpy, memcpy или другой вариант копирования, но с точки зрения портирования кода это дополнительные действия.
UFO just landed and posted this here
Как раз затем, чтобы не реализовывать их каждый раз :)

Да, не реализовывать каждый раз — это хорошо, но мне это видится путем к стандартной библиотеке C++ со всеми ее минусами и плюсами.
А чем не вариант написать свои malloc()/calloc()/free()?

Можно, но не всегда, и далеко не в каждом случае это оправдано, особенно при малом объеме памяти, там беда с фрагментацией кучи наступает. Иногда проще поправить код и перевести все в статически выделенную память.
UFO just landed and posted this here
Поэтому пополнения стандартной библиотеки C очень даже приветствуются.

Да, вот бы все нужное включили в стандартную библиотеку и не пришлось бы это таскать за собой :D! Но вот будет с точностью до наоборот :(, в типовом коде для ПК, который надо портировать эти функции будут, а на нужной платформе — нет. И придется кроме своих костылей таскать еще и стандартно-библиотечные, так и перенапрячься не долго :). Ну или как предлагалось выше — переписывать каждое вхождение подобных функций на решение для конкретной платформы, поскольку ну вот не получается так на раз-два перенести strdup в таком виде на статическое выделение памяти.
Вот тут я не совсем понял. Зачем править код, если можно изначально обходиться без той же strndup()? Как-то жили ведь до этого.

Жили. И дальше будем жить без этого. Вот только появится больше кода, который использует эти функции. И с этим будем жить, просто портировать код с «больших» платформ станет чуть сложнее.
UFO just landed and posted this here
Чем и занимаемся сейчас…

Занимались и будем продолжать, «работа такой».
Хе, а для нас они «маленькие» :-)

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

ИМХО: юнитов надо немного, только проверить параметры-результат в десяток-полтора вариантов. А как этот код выше юнитов может завалить тест? За примеры кода — человеческое спасибо, хоть посмотрел как бывает в других областях.
Не всё мне тут нравится, но я за определённость. OK, синтаксис K&R убрали, но… Или уже POSIX, или ну в пень эти виляния.

Согласен, с POSIX можно будет работать, по крайней мере «отфильтровать» функционал по платформам и дальше разбираться как с этим жить. Тут, конечно, начинает попахивать расколом (как в C++), но куда деваться.
P.S. Прекрасно понимаю ваши проблемы. Но и у нас их тоже до фига. А стандарт… Он для всех (читай для большинства) :)
Начинаю понимать Ваши. Вот бы стандарт был не для «большинства», а для всех. Но это так, поныть в такт.
Оператор defer

И как эта штука в плане дебага?

В C его нет, существует он только в виде мысленного эксперимента. По моему воображению — не очень, поскольку рушит последовательность выполнения по строчкам. С другой стороны, если его введут, то станет чуть проще ориентироваться по командам освобождения ресурсов, сложнее будет забыть сделать free перед return и подобное. Тогда дебажить надо будет меньше. Подождем, может появится в виде расширения в каком-нибудь компиляторе, можно будет потрогать, тогда и оценивать.
А в чем проблема сделать поддержку в дебагерах? Прыгай себе в defer да и все, прям как с исключениями в плюсах. goto и так до этого «ломал» последовательность выполнения, а defer заменит как раз goto по большей части.
Ну вот не кажутся мне исключения в плюсах совсем логичными. Они нормальны для плюсов и многих других высокоуровневых языков, но мы то о C, тут или нормально или совсем goto, без полумер.
Меньшее зло оно такое… притягательное :). Избавиться от goto — это точно верное направление, но вот является ли defer лучшим (в рамках осознаваемого) вариантом — это вопрос открытый, во всяком случае, для меня. Получается, меняем явный goto(очень плохо, вплоть до запрета на использование) на неявный многошаговый «goto» или «сборник команд»(чуть лучше). Я не за запрет подобных решений, я за вдумчивый и последовательный подход к развитию языка. Давайте попробуем придумать другие варианты решения проблемы, так явно не ломающих последовательность выполнения. Давайте подумаем о «навеске» выполняемого кода на выход из скоупа, ведь это давно просится.
Чуть поясню для наблюдателей: на входе в блок или функцию воткнуть дополнительный код просто, поскольку точка входа(совсем почти всегда) одна, а вот выходов может быть много, каждый выход надо отдельно контролировать, это одна из главных проблем освобождения ресурсов.

goto хорошо накладывается на asm, а defer? defer накапливает вызовы в стеке, при этом defer вы используете для освобождения ресурсов. Что будет при переполнении стека? Кстати одной из проблем Си является невозможность функции видеть собственный стек...

goto хорошо накладывается на asm

А какое это имеет значение? С не ассеблер, далеко не ассемблер.

defer накапливает вызовы в стеке

Очевидно, есть множество вариантов реализации. Если defer становится слишком много (ведь до тех пор достаточно битовой маски маленькой), то можно и на хип начинать выносить структуру, куда складывается список вызовов defer. А так, переполнить стек можно и простой рекурсией и как-то программисты с этим живут. Переживут и тут. Переполнить стек с defer это значит сделать что-то совсем кривое. Вроде defer внутри очень длинного цикла.
то можно и на хип начинать выносить структуру, куда складывается список вызовов defer.

Не путайте C со всякими скриптовыми и околоскриптовыми языками.


А какое это имеет значение? С не ассемблер, далеко не ассемблер.

Имеет конечно, C хорош как раз тем что он двусторонне интуитивно понятно транслируется в ассемблер и назад из ассемблера в C (с незначительными оговорками). Если вы будете добавлять в него всяческое неявное поведение то он потеряет свою главную фичу.


Впрочем, то, что написал выше DungeonLords — ерунда.


defer нормально в asm превращается, не сильно сложнее чем механика передачи аргументов в функцию. И так же как и аргументы может сделать переполнение, если программист написал бажный код. Хорошие программисты бажный код стараются не писать, а когда обнаруживают — исправляют. Роль компилятора (C, а не какой-то полускриптовой фигни) тут максимум в том, чтобы выдать варнинг на подозрительное место.


Кстати одной из проблем Си является невозможность функции видеть собственный стек...

Ну и это просто напросто враньё. Речь конечно не про сферический C в вакууме а про реальный. Только надо понимать, что стек в C (как и все остальные структуры данных) — низкоуровневая структура, и его просмотр вам мало чем поможет, если вы не знаете как он устроен.

"Кстати одной из проблем Си является невозможность функции видеть собственный стек...


Ну и это просто напросто враньё. Речь конечно не про сферический C в вакууме а про реальный. Только надо понимать, что стек в C (как и все остальные структуры данных) — низкоуровневая структура, и его просмотр вам мало чем поможет, если вы не знаете как он устроен."


Поясните пожалуйста как всё-таки функция может увидеть свой стек. Может быть зря во FreeRTOS для этого понаписали asm вставки...

Это бессмысленный вопрос без контекста. Ну, какой вопрос — такой и ответ, вот вам пример:


void func(unsigned int a) {
  unsigned int * p = &a;
  printf("stack: %08X %08X %08X %08X\n", *(p-1), *(p), *(p+1), *(p+2));
}

Выведет на 32-битной платформе адрес возврата из функции, следующее за ним слово — аргумент a и ещё два слова с непонятно чем, использовавшиеся вызывающим кодом. Поскольку контекст вопроса непонятен, этим и ограничимся, ну а так вы можете дальше заняться интерпретацией этих данных по своему усмотрению. Отладчики (gdb), например, умеют даже строить из этих чисел обратный трейс вызовов.


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

Выведет на 32-битной платформе адрес возврата из функции, следующее за ним слово — аргумент a и ещё два слова с непонятно чем

Ну-ну! Конечно, есть такие 32-бит платформы, но вот обобщать не стоит.

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

А почему компилятор не может реализовать defer как ваш static_defer?


И что мешает использовать static_defer в циклах?

Погодите, а откуда вообще взялась идея что defer использует стек?

Увы и ах. Я вот жалею, что на avr-gcc _Atomic не описан толком как работает… На Cortex'ах бы тоже _Atomic пригодился. Причём и для использования с прерываниями, и когда у тебя ОСРВ.
Кто-нибудь знает что-то о работе _Atomic в микроконтроллерах?


Про адекватный printf fixed point кто-нибудь сможет прокомментировать?

AVR. Очень интересный вопрос, надо будет поковырять. Самый очевидный путь — посмотреть во что оно там компилируется. Скорее всего — будет запрет прерываний.
ARM. Еще более интересный вопрос, особенно в случае многоядерности. Хотя с многоядерностью можно пойти на volatile, блокировку контроллера памяти и запрет прерываний, но это совсем не точно.
Некоторые типы являются атомарными (на чтение или запись) напрямую, например volatile uint8_t в AVR. В ARM таких типов больше, где-то встречал утверждение, что uint8_t, uint16_t, uint32_t с volatile атомарны. Про uint32_t понятно, а вот с остальными все будет зависеть от логики работы контроллера памяти, что не очевидно.

В fixed point я никак, так что будем вместе ждать знающего комментатора. Есть еще ссылки на эту тему?

volatile uint8_t однозначно не является атомарным на avr. На avr есть от производителя компилятор XC8, к нему есть описание MPLAB XC8 C Compiler User’s Guide for AVR® MCU На тему атомарности единственно что есть — отсылка к какому-то atomic.h.


По теме работы fixed point в реальных компиляторах ожидается пояснительная бригада!

Согласен, ошибаюсь, все не так просто.
Мысли в порядок:
В
MPLAB XC8 C Compiler User’s Guide for AVR® MCU
явно сказано:
The volatile qualifier does not guarantee that any access will be atomic, which is often not the case, since the 8-bit AVR architecture can typically access only 1 byte of data per instruction.
Even when objects are marked as volatile, the compiler cannot guarantee that they will be accessed atomically. This is particularly true of operations on multi-byte objects.
С учетом переупорядочивания последовательности выполнения команд, описанного в статье по приведенной Вами ссылке действительно можно утверждать, что volatile uint8_t не является атомарным для записи, кроме того, может быть изменена последовательность выполнения, что приведет к бардаку.
Если мое представление о мироздании имеет отношение к реальности, то на 8ми битной архитектуре можно безопасно читать память длиной в 1 байт любых переменных (volatile или нет — не имеет решающего значения). Писать так не получится, теперь понятно почему.
При попытке читать память длиной более 1 байта могут быть сложности, например:
— начинаем читать uint32_t в основном коде
— прочитали 2 байта
— в этот момент вызывается обработчик прерывания и меняет значение
— прочитали еще 2 байта
получили смесь двух значений, это плохо. Для решения этой проблемы можно при чтении использовать обычный вариант из cli… sei, для переменной стоит воткнуть volatile, поскольку она меняется в обработчике прерывания и не должна «кэшироваться». При записи имеет смысл отключать прерывания:
— в основном коде при записи в переменную, которая используется в обработчиках прерываний;
— в контексте обработчика прерываний на xmega для переменных, которые используются в других обработчиках прерываний (в xmega обработчики прерываний могут вытесняться другими обработчиками прерываний).
Для всего этого хорошо подходит ATOMIC_BLOCK, тут про использование, тут про магию, которая удивительно похожа на defer, кстати, пока писал ответ появилась статья на эту тему.
Если я правильно понял из «MPLAB XC8 C Compiler User’s Guide for AVR® MCU» вот это:
Interrupts should be disabled around any main-line code that modifies an object that is used by interrupt functions, unless you can guarantee that the access is atomic. Macros are provided in <avr/atomic.h> to assist you access these objects.
то макросы в <avr/atomic.h> только помогают получить атомарный доступ, но не позволяют создать атомарные типы. Эти помогающие макросы, скорее всего, обертки над запретом/разрешением прерываний.
Допустим, с AVR понятно, разрядность архитектуры равна октету, это все упрощает.
Теперь краткие соображения про ARM(32).
По аналогии, можно считать, что чтение памяти ведется по 4 байта(32бита), таким образом кажется, что можно безопасно читать переменные размером в 4 байта, но только в «общем случае в вакууме». В упакованных (packed) структурах все может работать по другому. Самый интересный для меня момент тут — как будут работать packed структуры при наличии не выравненного volatile поля при записи в него из обработчика прерывания и чтении из основного кода. С одной стороны — volatile исключает оптимизацию, что не противоречит упаковке структуры, с другой — это может нарушить выравнивание, что помешает чтению поля за одну инструкцию. Получается, на процессоре любой разрядности невозможно гарантировать атомарное неблокирующее чтение памяти размером более 1 байта, но я не очень-то в этом уверен.

Что-то я отвлекся, теперь про _Atomic. По результатам моих поисков все сводится к compare-and-swap в цикле «до тех пор, пока не получилось». Без этой инструкции процессора реализация всего _Atomic возможна только через блокировку прерываний (и блокировку контроллера памяти для многоядерных/многопроцессорных систем). Это, в теории, не противоречит неблокирующему выполнению, но на самом деле ведет к остановке выполнения других потоков, хоть они этого и не заметят. Такой подход все еще противоречит моему представлению об упакованных структурах, тут я вообще не понимаю как оно должно работать с памятью длиной более 1 байта.

Близкое по теме:
Про реализацию test-and-set на xmega тут.
Про укаковку структур тут.

Пойду изучать fixed point…
А разве опускание имен аргументов не было возможно раньше?
Бог создал труд и обезьяну;
Чтоб получился человек.
«Буханку» же господь не трогал;
Та сразу вышла хорошо.

В контексте бородатой картинки про троллейбус смотрится особенно хорошо.

Ну стишок немного не про ту буханку, если я правильно понял, про какую вы картинку, но в целом таки да, «но зачем?»
Ибо «по фану».
> Оператор typeof

Что-то я не улавливаю, зачем плодить сущности, когда в «близком» С++ уже есть стандартизованный decltype? Зачем разводить языки ещё больше только ради того, чтобы развести?

Ну так в си в любом случае или _Typeof, или _Decltype будет. В ключевые слова decltype, как и typeof не внесут. Но будет заголовочный файл, где будет какой-нибудь макрос. А там, уже не особо важно decltype или typeof. В си даже bool, atomic и complex в ключевых словах нет, зато есть _Bool, _Atomic и _Complex.

Ну в статье
После долгого, до-о-олгого переходного периода комитет, наконец, решил больше не придуриваться и принять в язык, эм, «новые» ключевые слова: true, false, alignas, alignof, bool, static_assert и другие. Заголовки вроде <stdbool.h> можно будет, наконец, почистить.
Хотя typeof в разделе «слухов», мб будет как раз пока _Typeof, или _Decltype, в C30 будет decltype.

Это уже 4 версия данного документа. Стандарт сказал, что да, мы хотим, чтобы данные слова объявлялись в одном месте, но против того, чтобы они были first class. В итоге, автор предложения сейчас предлагает дать возможность компиляторам самим решать будут эти слова first class или нет. Кстати, true и false были убраны из этого предложения. Думаю, что максимум, что примет стандарт — введение нового заголовочного файла, где и будут перечислены данные слова. Вряд ли, они будут введены как first class.

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

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


Во-первых, актуальный C это тот что поддерживается компиляторами, а не тот что прописан с Сxx стандартах. Ну, то есть один раз они сделали полезное дело — собрали воедино и подвели итог всему тому, что было в 80-х и раньше, получившийся документ теперь известен как C89 и его можно считать более-менее стандартной основой для всех вариаций языка.


А вот C99 и последующие их документы — весьма спорно можно считать общепринятыми. Например, конструкции языка, добавленные в C99 (например VLA), в итоге никто так толком и не принял, и в C11 их фактически отменили. Что же касается RTL, то, на мой взгляд, тут POSIX намного более авторитетный стандарт чем различные Cxx. Так же не забываем про всякие SUS, X.Open, BSD и что там ещё — все эти спецификации в итоге поддерживаются большинством платформ, кроме винды (штатные компиляторы которой относятся к C99+ ещё намного более наплевательски, чем gcc).


strdup из статьи прекрасная иллюстрация вышесказанному — несмотря на её отсутствие в Cxx, она по факту стандартная — практически все C-программисты пишут код, зная что на целевой платформе она есть, и все разработчики компиляторов/rtl знают, что их не поймут, если они вдруг её не реализуют под предлогом отсутствия в мало кому интересном стандарте.


ещё комментарии:
1) атрибуты и новые ключевые слова для них — хорошая идея, хотя кажется это уже есть в компиляторах и так;
2) K&R-синтаксис — тоже его не люблю, но без него zlib не соберётся; а вот пустые скобки для функций без аргументов — плохая идея;
3) дополнительный код — по факту ничего не меняется, в том числе и то, что для железа, где представление знаковых чисел другое, всё так же будут писать на C с не дополнительным кодом; может возникнуть путаница;
4) с NULL переняли чушь из C++; настоящий NULL это и есть (void*)0, таким его и надо было оставить

По пункту 4. Как правильно и разъяснили paluke VlK, (void)0 можно кастовать ещё куда-то
(uint64_t)(int)(void
)0 — пожалуйста, лишь бы псевдоним не прибежал на -O3 Причём кастоваться он может неявно — вот в этом вся боль. А nullptr нельзя закастовать никуда...

Вместо (void)0, вы, наверное, хотели написать (void *)0. (uint64_t)(int)(void *)0 + плохая конструкция. Не стоит кастовать указатель к int, также как и к uint64_t. В данном случае, конечное, сработает, ибо у нас 0, но warning, наверное, будет. Указатели стоит кастовать к uintptr_t. Про strict alliasing непонятно. Данное выражение не содержит strict alliasing. При приведение данного выражения к условному float * тоже не даст проблем. Повторное кастование данного float * к условному int * тоже не даст проблем, ибо разыменоание делать, в любом случае, нельзя. Арифметику с нулевым указателем тоже, вроде, нельзя делать.

Что значит vla никто не поддерживает?! Gcc и clang давно поддерживают. Gcc даже принятые изменения C2x уже поддерживает. Что Gcc из C стандарта, кроме _Imaginary, не поддерживает?!


  1. Единого синтаксиса и namespace не было. теперь можно писать, что-то типо [[gnu::cleanup(free), yourcompiler::defer(free)]]
  2. Обещали(предлагали) safe int ещё завести.
  3. NULL может быть 0, чтобы один хедер и к C, и к C++ коду подключать и проблем не было. Чтобы проблем с обратной совместимостью не было, введут nullptr, который везде (void *)0.
«новые» ключевые слова: true, false, alignas, alignof, bool, static_assert и другие.

Я джва пять лет этого ждал.


const int music[] = {
   #embed int "music.wav"
};

Правильно ли я понимаю, что это все в массив байтов раскладывается или как?

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


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

Там есть не совсем это. include_bytes ограничен u8, тогда как предлагаемый #embed такого ограничения не имеет.

Что значит «ограничен»? u8 это беззнаковый 8-битный тип. Куда еще свободнее то?
Ну так этот u8 потом еще предстоит превратить в то что тебе нужно, он же наверняка как-то структурирован. А здесь пользовательские типы вполне могут поддерживаться искаропки при нормальной реализации, синтаксис #embed же позволяет сразу указать тип.
Сомнительное ограничение. Как правило, подобные вещи используют, когда нужно просто засунуть какой-то файл в бинарник, а не тащить рядом. На то они и ресурсы, что это просто потоки байтов. И если уж встретится редкий случай, когда надо как-то преобразовать, в рантайме распихать в какие нужно типы дело считанных нано и микросекунд.

Вообще, с учетом того, как у С все весело с размерами типов и тем более пользовательскими типами (вот будет весело с неупакованными структурами), сомнительной кажется вообще идея разрешать включать бинарные данные какого-либо типа, кроме байтовых массивов. А еще лучше void*, но тут вопрос с определением длины.
А memcpy'ить потом в рантайме куски этих байтовых массивов в такие же по недосмотру не упакованные структуры чем-то принципиально лучше что ли? Как-то я в этом не уверен. Хрен редьки не слаще, а тут лишних действий меньше. К тому же компилятор по крайней мере теоретически еще на этапе компиляции сможет проверить хотя бы соответствие размера бинарных данных и типа, в виде которого их хотят представить.
Мне кажется, что лучше. Чисто визуально, семантически. void* или uint8_t* массив с этими бинарными данными сразу говорит человеку, что это сырые данные. Их надо осторожно воспринимать, согласно размерам типов платформы, правилам выравнивания, порядку байтов процессорной архитектуры. Давать же возможность просто сериализовать произвольные байты в какой угодно тип это верный путь к хитрым проблемам, которые будут проявляться на пересечении разных платформ и компиляторов. Просто потому, что человек, увидев такую конструкцию, может ожидать, что компилятор за него все сделает и обо всех хитростях подумает.

Так же здесь есть серьезные вопрос по поводу безопасности. Какие файлы разрешено включать, что с ними разрешено сделать. Включать что угодно куда угодно это верный путь к получению уязвимостей. Особенно в таком языке как С.
Знаете, если программист решит взять неизвестно откуда сырые данные и, скажем, выполнить их by design, то вы ему вряд ли помешаете. Будь то пользовательский тип, char* или void*. Точно так же вы не помешаете ему сделать memcpy в упакованную структуру, а потом взять ссылку на невыровненный элемент этой структуры, попытаться через нее что-нибудь сделать и получить UB. В том числе в safe подмножестве раста. Программист должен в любом случае четко понимать, что он делает, без этого никак. Но, по моему мнению, в данном случае лучше все-таки иметь возможность представить бинарные данные в виде определенной структуры хотя бы с какими-то compile time проверками, чем вообще без таковых. И это все-таки не произвольные данные откуда-то из сети, а включаемые на этапе компиляции.
Раст тут не в тему, он таки это рассматривает как проблему и это скорее всего будет исправлено. Как видно, уже есть RFC, чтобы пометить это unsafe. Да и UB в расте, и UB в С это разного рода опасность все же.

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

А про включение, речь о том, что всегда есть опасность включать что-то, что не стоит. /etc/shadow какой-нить, например, SSH ключи. Это все реальная опасность и с этим надо будет как-то считаться. Не говоря уже о вероятности выполнения произвольного кода. Это наверное не проблема стандарта, но явно ляжет на плечи компиляторов.
UB всегда UB, хоть в C, хоть в расте. Последствия UB и там, и там совершенно непредсказуемы. Желание пометить это дело unsafe совершенно понятно, но это все те же старые добрые попытки закрыть паллетами дизельный генератор, а для любителей формального подхода «чем меньше unsafe, тем безопаснее код» существуют даже специальные крейты — как говорится, «удачной отладки» :) Желание «защитить программиста» совершенно понятно, но в моем представлении это желание как-то слабо вяжется с любовью к void* и memcpy() налево-направо вместо хотя бы минимальных compile-time проверок, особенно от человека «из мира более безопасных языков». А если программист целенаправленно захочет включить исполняемый код (и выполнить его), или /etc/shadow, или ssh-ключи, то он всегда найдет способ (напишет скрипт для генерации initializer list для массива из /etc/shadow с детской обфускацией через XOR, да что угодно), и компилятор ему тем более никак не помешает :) Наивно надеяться, что защита от этого как-то «ляжет на плечи компиляторов» и они всегда и везде будут думать за программиста.
UB всегда UB, хоть в C, хоть в расте. Последствия UB и там, и там совершенно непредсказуемы

Нет уж, последствия UB могут быть очень разными. В расте было UB (уже пофиксили похоже) с кастом float в int. Много проблем это могло доставить? Разве что логические ошибки. Это теоретическая проблема, которую неплохо было исправить и ее исправили. UB в С/С++ это обычно прямой путь к эксплоитам. Поэтому разница в последствиях очень важна. Тоже самое с гонками. В какой-нить джаве даже data race разве что логическую ошибку даст. В safe расте data races невозможны. В С/С++ дофига эксплоитов, которые за счет гонок исполнение кода получают.

но это все те же старые добрые попытки закрыть паллетами дизельный генератор,

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

хотя бы минимальных compile-time проверок

В данном случае компилятор не может ничего сделать полезного кроме как посчитать длину на этапе компиляции. Для этих целей вместо void* можно char[] или uint8_t[] сделать. Как я уже говорил, это больше визуальная фишка. Показатель программисту, что эти данные не надо интерпретировать, пока сам их не переведешь в то, что тебе нужно. Так, как ты считаешь нужным, а не как решил компилятор какой-то платформы. Ну или если хочешь приключений, то сам кастуй к массиву int*. Для char* (и вроде void*) это разрешено. Это и спецификацию языка упростит, и в возможностях не ограничит.

А если программист

Да при чем тут программист? Это сделает злоумышленник. Сколько уже было подобных уязвимостей, когда что-то где-то компилятор подхватывает без проверок всяких. Это всегда острая проблема, когда поведение компилятора модифицируется какими-то внешними файлами и влияет на генерацию кода. Было бы не очень хорошо, если бы компиляция какого-то чужого кода позволила стырить системные файлы с моего компьютера, а с включением произвольных файлов в код это становится возможным. Еще хуже, если компиляторы разрешат симлинки.
Нет уж, последствия UB могут быть очень разными. В расте было UB (уже пофиксили похоже) с кастом float в int. Много проблем это могло доставить?

Вы, я вижу, слабовато представляете себе суть UB. Она именно в том, что в совокупности с оптимизатором UB может доставить любые проблемы. В том числе абсолютно непредсказуемые. На то он и undefined. И нет, этот UB не пофиксили. Ввели ключ компилятора, который добавляет определенные проверки при конверсиях такого рода, без этого ключа все то же самое.

P.S. А, мои сведения слегка устарели. Я так понял, в апреле они сделали эти проверки при as обязательными, но добавили unchecked конверсии, которые отметили как unsafe. Еще немного паллет в общем.

Вот и упомянутую проблему тоже уберут в unsafe

Но сама по себе она от этого никуда не исчезнет. Больше unsafe богу unsafe. А код нужно всегда читать внимательно, вне зависимости от того, safe он или unsafe. Там и кроме проблем с лайфтаймом или ссылками может быть ооочень много всего.

В данном случае компилятор не может ничего сделать полезного кроме как посчитать длину на этапе компиляции.

Для начала это уже лучше, чем совсем ничего. Только char[] сам по себе бесполезен в этом плане, было бы неплохо, если бы компилятор сразу проверял на соответствие размера той структуре, в которую я это потом буду запихивать. А если я этого не хочу — то я напишу char[]. Но я бы все-таки хотел иметь в этом плане выбор, а не бесполезную «визуальную фишку». void*'ом в C и так никого не удивить.

Да при чем тут программист? Это сделает злоумышленник.

Если злоумышленник имеет доступ к системе сборки, то он уже победил. Он может что-нибудь этакое добавить в Makefile, в какой-нибудь из скриптов сборки (в тот же build.rs), да во что угодно. #embed по сравнению с этим — детский лепет.
UFO just landed and posted this here

Конкретно этот встроен, но можно написать свой с аналогичным функционалом.

Пример с использованием __attribute__((cleanup (...) )) неправильный. Запуск такого кода завершится падением программы. Будет даже warning: attempt to free a non-heap object

Вот правильный пример:
#include <stdlib.h>

void myfree(void *ptr)
{
  free(*(void **)ptr);
}

int main(void)
{
  __attribute__((cleanup(myfree))) char *s = malloc(sizeof(*s));
  return 0;
}


Почему так — это описано в документации GCC: gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html см. cleanup (cleanup_function)

Точно, поправлю.


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

/* объявление функции без аргументов */
int no_args();

/* тоже объявление функции без аргументов */
int no_args(void);

Первый вариант означает, что тип и кол-во аргументов не определены (можно писать no_args(1,2,3)), второй вариант означает, что аргументов у функции нет. Первый вариант можно отнести к быдлокоду, он ещё жив, полагаю, для совместимости...

Блин, 50 лет языку, а поддержку корутин, генераторов, продолжений на уровне синтаксиса так и не завезли. Сколько программистов до сих пор извращается с огромными свичами и protothreads?

Си — язык консервативный. Даже в момент создания он был, прямо скажем, не последним словом языкостроения. Корутины же, хотя и были придуманы лет 40 назад, но широкую популярность обрели только в последние лет десять. Включены в альтернативные языки для системного программирования они были 1-2 года назад.


С осторожным оптимизмом можно прикинуть, что в Си что-то подобное появится лет через 10 :-)

Sign up to leave a comment.