company_banner

C2x: будущий стандарт C


    Я ловлю в далёком отголоске,
    Что случится на моём веку.
    («Гамлет», Борис Пастернак)

    Признаться, пишу на чистом C я не так уж и часто и за развитием языка уже давно не слежу. Но тут произошло два неожиданных события: С вернул себе звание популярнейшего языка программирования по версии TIOBE и случился анонс первой за долгие годы действительно интересной книги, посвящённой этому языку. Поэтому я провёл несколько вечеров за изучением материалов о C2x — следующей версии C.


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


    Комитет, стандарт и всё-всё-всё


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


    В 1989 году популярнейший язык программирования C перешёл на новую ступень развития — стал американским национальным (ANSI) и международным (ISO) стандартом. Эту версию C негласно назвали C89, или ANSI C — в противовес многочисленным диалектам, существовавшим до этого.


    Новая редакция стандарта языка выходит примерно раз в десять лет, на текущий момент существует четыре редакции: оригинальный C89, C99, C11, C18. Год публикации следующей версии неизвестен, поэтому документ в работе называют C2x.


    Вносит изменения в стандарт специальная рабочая группа — WG14, в которую входят заинтересованные представители индустрии из разных стран. В англоязычной профильной литературе эту группу часто называют “Committee”, а я для её обозначения буду использовать слово «комитет».


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


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


    Эту статью я разбил на три части. Очерёдность установил по степени вероятности внесения изменений в стандарт. В первой части я перечислю те предложения, которые уже принял комитет. Во второй оказались предложения, которые были восприняты положительно, но возвращены авторам на доработку. В последней же части я собрал самое «вкусное» — слухи о неопубликованных предложениях, обсуждаемых «в кулуарах» членами комитета.


    Согласованные комитетом предложения


    Функции strdup и strndup


    Я, вероятно, покажусь невеждой, если скажу, что не знал об отсутствии этих функций в стандартной библиотеке C. Казалось бы, что может быть очевиднее и проще копирования строк? Но нет, C не такой, это вам не POSIX.


    Итак, с опозданием лет на 20 к нам приходят функции strdup и strndup!


    #include <string.h>
    
    char *strdup (const char *s);
    char *strndup (const char *s, size_t size);

    Приятно осознавать, что комитет иногда всё же принимает неизбежное.


    Атрибуты


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


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


    [[attr1]] struct [[attr2]] S { } [[attr3]] s1 [[attr4]], s2 [[attr5]];

    Здесь attr1 относится к s1 и s2, attr2 относится к определению struct S, attr3 — к типу struct s1, attr4 — к идентификатору s1, attr5 — к идентификатору s2.


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


    1. Атрибут deprecated позволяет пометить объявление как устаревшее. При использовании таких объявлений компиляторам предлагается предупреждать программиста.
    2. Атрибутом fallthrough можно явно пометить места в ветвлениях switch, где поток исполнения должен попасть в следующую ветвь тоже.
    3. С помощью атрибута nodiscard можно явно указать необходимость обработки возвращаемого функцией значения.
    4. Если переменная или функция осознанно не используется, можно пометить её атрибутом maybe_unused (а не почти уже идиоматичным (void) unused_var).
    5. Атрибутом noreturn можно пометить функцию, которая не вернётся в место вызова.

    Традиционный способ указания параметров функций (K&R)


    Устаревший ещё в 1989 году такой способ объявления параметров функции, как «объявление в стиле K&R» (он же — «когда типы после скобочек указываются», он же — «я не понимаю старый код на C»), будет, наконец, сожжён на костре, а я смогу расслабиться и не следить за своими void-ами.


    Другими словами, больше нельзя будет делать так:


    long maxl (a, b)
        long a, b;
    {
        return a > b ? a: b;
    }

    Эпоха Просвещения приходит в код на C! Объявления функций будут делать именно то, что приличные люди от них ожидают:


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

    Представление знаковых целых чисел


    Бесконечная, казалось бы, история близка к завершению. Комитет смирился с тем, что единорогов и сказочных архитектур не существует, а программисты на C имеют дело с дополнительным кодом (англ. two's complement) для представления знаковых целых чисел.


    В текущем виде это уточнение немного упростит стандарт, но в перспективе поможет (!) избавиться от «любимого» программистами неопределённого поведения языка — переполнения знакового целого числа.


    Предложения в работе


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


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


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


    Реализация одного из положительно оценённых комитетом предложений позволит не указывать лишний раз имена параметров в определениях функций:


    int main(int, char *[])
    {
        /* И никакой перхоти! */
        return 0;
    }

    Мелочь, но какая приятная!


    Старые новые ключевые слова


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


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


    Включение двоичных данных из файлов в исполняемый файл — невероятно полезная для всех игроделов возможность :


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

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


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


    Кажется, на смену проблемному макросу NULL приходит ключевое слово nullptr, которое будет эквивалентно выражению ((void*)0) и при приведении типов должно оставаться типом-указателем. Любое использование NULL предлагается сопровождать предупреждением компилятора:


    /* Вы же никогда не пишете просто NULL? Я вот до сих пор затылок чешу. */
    int execl(path, arg1, arg2, (char  *) NULL);
    
    /* Но счастье близко */
    int execl(path, arg1, arg2, nullptr);

    Если пример вам ни о чём не говорит, то загляните в документацию для Linux по адресу man 3 exec, там будет пояснение.


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


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


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


    [[ oob_return_errno ]] int myabs (int x) {
        if(x == INT_MIN ) {
            oob_return_errno ( ERANGE , INT_MIN ) ;
        }
        return (x < 0) ? -x : x;
    }

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


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

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


    bool flag;
    int result = oob_capture(&flag , myabs , input) ;
    if (flag) {
        abort ();

    Здесь корректность выполнения функции показывается флагом flag, причём errno не затрагивается. Аналогично выглядят, например, вызовы функций с сохранением кода ошибки в переменную.


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


    Слухи


    Автор «Effective C» совместно с другими членами комитета отвечали на вопросы участников англоязычного сообщества Hacker News. Вопросов и ответов по ссылке много, многое пересекается с отмеченными выше вещами. Но есть пара важных для программистов пунктов, которые не оформлены как предложения, но члены комитета намекают на то, что какая-то работа в этих направлениях уже ведётся.


    Оператор typeof


    Ключевое слово typeof уже давно реализовано в компиляторах и позволяет не повторяться при написании кода. Канонический пример:


    #define max(a,b)                                \
        ({ typeof (a) _a = (a);                     \
        typeof (b) _b = (b);                        \
        _a > _b ? _a : _b; })

    Мартин Себор (Martin Sebor), ведущий разработчик из Red Hat и участник Комитета, утверждает, что соответствующее предложение уже находится в работе и почти наверняка будет одобрено.


    Держу пальцы скрещенными.


    Оператор defer


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


    В чистом C нет и никогда не было такой возможности, но компиляторы уже давно реализуют атрибут cleanup(<cleanup function>):


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

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


    int do_something(void) {
        FILE *file1, *file2;
        object_t *obj;
        file1 = fopen("a_file", "w");
        if (file1 == NULL) {
          return -1;
        }
        defer(fclose, file1);
    
        file2 = fopen("another_file", "w");
        if (file2 == NULL) {
          return -1;
        }
        defer(fclose, file2);
    
        /* ... */
    
        return 0;
      }
    

    В приведённом примере функция fclose будет вызвана с аргументами file1 и file2 при любом выходе из тела функции do_something.


    Близится революция!


    Выводы


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


    Последние неудачные изменения в C случились десять лет назад. А последний качественный скачок в разработке на языке произошёл более 20 лет назад. И, судя по всему, в новой итерации стандарта члены комитета решили всё-таки подумать над поступательным движением вперёд.


    В общем, пользуйтесь статическими анализаторами, почаще запускайте Valgrind и старайтесь не писать слишком больших программ на C!


    PS Пожалуй, я немного загнул насчет "единственной действительно интересной книги". Пользователь mikeus подсказал другую книгу, и тоже от члена комитета, и она тоже стоит того однозначно: Modern C.

    Badoo
    Big Dating

    Comments 148

      +2

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


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


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

        0

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

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

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

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

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

            Возможно.


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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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


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


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

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


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

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


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

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


                    Ну, вообще-то 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 и подобные вещи).
          +1
          Что-то ничего особо интересного, кроме разве что включения бинарников (для программирования МК — весьма полезная вещь) не вижу.
          Большая часть «нововведений» уже давным-давно поддерживается gcc!
          P.S. А что значит «слишком большая программа на С»? Больше, чем 2 объема ядра?
            +4

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

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

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

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

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

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

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

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

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


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

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

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


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

                +4

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


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

                  0

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

                    0

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


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

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

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

                      Т_Т

                        0

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

                    0

                    А про принятые _Decimal почему ни слова?

                      0

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


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

                        0

                        Ну там во многих главах стандарта изменения, связанные с 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 ещё не было.

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

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

                        +3

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

                          +1

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


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

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

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

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

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

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

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

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

                                +2
                                А так ли много других функций с неиспользуемыми агрументами? Если много, то что-то здесь не так…

                                P.S. Афигеть как мы опередили стандарты. Буквально неделю назад strndup() вошла в состав RTL VMS :) Но, с другой стороны, язык и библиотека — вещи разные, да. Ещё про alloca() можно вспомнить, или про va_copy…
                                  +8

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


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


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

                                    0

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

                              +2
                              В дополнение к общей картине по современному 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.
                                +1

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

                                  0

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


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

                                    0
                                    По мне так этим двум языкам давно уже надо развязаться.
                                    Возможно так. Но наверно будет не совсем хорошо, если при развитии в C завезут фичи, которые уже есть в C++, а они будут синтаксически или лексически реализованы по-другому. Вот эта общая спецификация, я так понял, есть попытка как-то формализовать одинаковости/различия в свете данного аспекта. Если оба комитета договорятся и какие-то базовые вещи в обоих языках будут вести согласовано, будет хорошо.
                                  +1
                                  Что-то си постепенно превращается в С++. nullptr, falltrough, какой-то изврат с обработкой ошибок.
                                    –2

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

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

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

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

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

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

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

                                      +5
                                      Оператор defer

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

                                        Вызываю пояснительную бригаду!
                                        "Если пример вам ни о чём не говорит, то загляните в документацию для 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 байта, нет?

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

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


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


                                            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) для безопасности.

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

                                                Поговорим о платформе, где язык Си является абсолютным доминантом — 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 желает удачной отладки, коллега!

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

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

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

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


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

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

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

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

                                                          Нельзя.

                                                          +1
                                                          он будет в исполняемый файл тащить кучу лишнего

                                                          Например? Особенно с -fno-rtti -fno-exceptions?


                                                          вот тут временно std::map, потом перепишу

                                                          А std::map вам чем не угодил, особенно если к его производительности претензий нет?


                                                          и так далее и тому подобное

                                                          Этого «и так далее» не так много.

                                                            0
                                                            А std::map вам чем не угодил, особенно если к его производительности претензий нет?

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

                                                        +3
                                                        Перегрузка и шаблоны сделают код только грязным, малопрогнозируемым и сложным. Непонятно, зачем это тащить в относительно синтаксический простой язык как С. Особенно с его корявыми правилами приведения типов. У нас уже есть один С++.
                                                          0
                                                          Аргументы? Первые 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);
                                                          


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

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

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

                                                                +2

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

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

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

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

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

                                                            +1

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

                                                              +1

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

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

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

                                                                    0

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

                                                                +3
                                                                А давайте введем классы, чтобы был C с классами! Только классы и больше ничего.
                                                                  +1

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

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

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

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

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

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

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

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

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

                                                                    Оператор defer

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

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

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

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

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

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

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

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

                                                                              +2
                                                                              Я воспринимаю C как язык, код на котором должен легко переноситься между платформами. Существует множество платформ, на которых динамическая память почти не существует. И если брать кусок кода, написанный по стандарту, то в нем нужно искать только *alloc и думать что с ним делать, а теперь добавляется еще 2 функции, которые могут выделять память и даже не содержат в имени подстроку «alloc». И эти 2 функции легко реализуются через уже существующие, так что я не понимаю зачем их тащить в стандарт.
                                                                              Согласен, это можно переписать на strcpy, memcpy или другой вариант копирования, но с точки зрения портирования кода это дополнительные действия.
                                                                                0
                                                                                И эти 2 функции легко реализуются через уже существующие, так что я не понимаю зачем их тащить в стандарт.

                                                                                Как раз затем, чтобы не реализовывать их каждый раз :) Мы, например, постоянно сталкиваемся с утилитами или библиотеками, где много чего вроде такий функций вовсю используется (_GNU_SOURCE, _XOPEN_SOURCE, _BSD_SOURCE, _POSIX_C_SOURCE etc). Приходится поддерживать (и регулярно пополнять) отдельную RTL со всем добром. А это немало человеко-часов, unit-тесты и прочие затраты ресурсов и времени.

                                                                                с точки зрения портирования кода это дополнительные действия.

                                                                                А чем не вариант написать свои malloc()/calloc()/free()? IMHO, в этом случае такой подход может быть оправдан и не так затратен.
                                                                                  0
                                                                                  Как раз затем, чтобы не реализовывать их каждый раз :)

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

                                                                                  Можно, но не всегда, и далеко не в каждом случае это оправдано, особенно при малом объеме памяти, там беда с фрагментацией кучи наступает. Иногда проще поправить код и перевести все в статически выделенную память.
                                                                                    0
                                                                                    мне это видится путем к стандартной библиотеке C++ со всеми ее минусами и плюсами.

                                                                                    Ну нет у нас плюсов, и не предвидится :) Точнее, есть немного, но только для небольшого количества портируемых программ, да и то их напильником приходится допиливать, чтобы
                                                                                    компилятор справлялся
                                                                                    $ cxx/version
                                                                                    VSI C++ V7.4-008 for OpenVMS Alpha V8.4-2L2



                                                                                    А объёмы очень нехилые. Поэтому пополнения стандартной библиотеки C очень даже приветствуются.

                                                                                    Иногда проще поправить код и перевести все в статически выделенную память

                                                                                    Вот тут я не совсем понял. Зачем править код, если можно изначально обходиться без той же strndup()? Как-то жили ведь до этого.
                                                                                      +1
                                                                                      Поэтому пополнения стандартной библиотеки C очень даже приветствуются.

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

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

                                                                                        Чем и занимаемся сейчас…

                                                                                        просто портировать код с «больших» платформ станет чуть сложнее.

                                                                                        Хе, а для нас они «маленькие» :-)

                                                                                        Просто поною.

                                                                                        Вот так выглядит нынче банальная strlen() в версии FreeBSD (часть комментариев вырезаю):

                                                                                        Тык
                                                                                        #include <sys/cdefs.h>
                                                                                        __FBSDID("$FreeBSD$");
                                                                                        
                                                                                        #include <sys/limits.h>
                                                                                        #include <sys/types.h>
                                                                                        #include <string.h>
                                                                                        
                                                                                        /*
                                                                                         * Portable strlen() for 32-bit and 64-bit systems.
                                                                                         *
                                                                                         * Rationale: it is generally much more efficient to do word length
                                                                                         * operations and avoid branches on modern computer systems, as
                                                                                         * compared to byte-length operations with a lot of branches.
                                                                                         *
                                                                                         * The expression:
                                                                                         *
                                                                                         *  ((x - 0x01....01) & ~x & 0x80....80)
                                                                                         *
                                                                                         * would evaluate to a non-zero value iff any of the bytes in the
                                                                                         * original word is zero.
                                                                                         *
                                                                                         * On multi-issue processors, we can divide the above expression into:
                                                                                         *  a)  (x - 0x01....01)
                                                                                         *  b) (~x & 0x80....80)
                                                                                         *  c) a & b
                                                                                         *
                                                                                         * Where, a) and b) can be partially computed in parallel.
                                                                                         *
                                                                                         * The algorithm above is found on "Hacker's Delight" by
                                                                                         * Henry S. Warren, Jr.
                                                                                         */
                                                                                        
                                                                                        /* Magic numbers for the algorithm */
                                                                                        #if LONG_BIT == 32
                                                                                        static const unsigned long mask01 = 0x01010101;
                                                                                        static const unsigned long mask80 = 0x80808080;
                                                                                        #elif LONG_BIT == 64
                                                                                        static const unsigned long mask01 = 0x0101010101010101;
                                                                                        static const unsigned long mask80 = 0x8080808080808080;
                                                                                        #else
                                                                                        #error Unsupported word size
                                                                                        #endif
                                                                                        
                                                                                        #define LONGPTR_MASK (sizeof(long) - 1)
                                                                                        
                                                                                        /*
                                                                                         * Helper macro to return string length if we caught the zero
                                                                                         * byte.
                                                                                         */
                                                                                        #define testbyte(x)             \
                                                                                            do {                    \
                                                                                                if (p[x] == '\0')       \
                                                                                                    return (p - str + x);   \
                                                                                            } while (0)
                                                                                        
                                                                                        size_t
                                                                                        strlen(const char *str)
                                                                                        {
                                                                                            const char *p;
                                                                                            const unsigned long *lp;
                                                                                            long va, vb;
                                                                                        
                                                                                            /*
                                                                                             * Before trying the hard (unaligned byte-by-byte access) way
                                                                                             * to figure out whether there is a nul character, try to see
                                                                                             * if there is a nul character is within this accessible word
                                                                                             * first.
                                                                                             *
                                                                                             * p and (p & ~LONGPTR_MASK) must be equally accessible since
                                                                                             * they always fall in the same memory page, as long as page
                                                                                             * boundaries is integral multiple of word size.
                                                                                             */
                                                                                            lp = (const unsigned long *)((uintptr_t)str & ~LONGPTR_MASK);
                                                                                            va = (*lp - mask01);
                                                                                            vb = ((~*lp) & mask80);
                                                                                            lp++;
                                                                                            if (va & vb)
                                                                                                /* Check if we have \0 in the first part */
                                                                                                for (p = str; p < (const char *)lp; p++)
                                                                                                    if (*p == '\0')
                                                                                                        return (p - str);
                                                                                        
                                                                                            /* Scan the rest of the string using word sized operation */
                                                                                            for (; ; lp++) {
                                                                                                va = (*lp - mask01);
                                                                                                vb = ((~*lp) & mask80);
                                                                                                if (va & vb) {
                                                                                                    p = (const char *)(lp);
                                                                                                    testbyte(0);
                                                                                                    testbyte(1);
                                                                                                    testbyte(2);
                                                                                                    testbyte(3);
                                                                                        #if (LONG_BIT >= 64)
                                                                                                    testbyte(4);
                                                                                                    testbyte(5);
                                                                                                    testbyte(6);
                                                                                                    testbyte(7);
                                                                                        #endif
                                                                                                }
                                                                                            }
                                                                                        
                                                                                            /* NOTREACHED */
                                                                                            return (0);
                                                                                        }
                                                                                        


                                                                                        Вот так в glibc:

                                                                                        Тык
                                                                                        #include <string.h>
                                                                                        #include <stdlib.h>
                                                                                        
                                                                                        #undef strlen
                                                                                        
                                                                                        #ifndef STRLEN
                                                                                        # define STRLEN strlen
                                                                                        #endif
                                                                                        
                                                                                        /* Return the length of the null-terminated string STR.  Scan for
                                                                                           the null terminator quickly by testing four bytes at a time.  */
                                                                                        size_t
                                                                                        STRLEN (const char *str)
                                                                                        {
                                                                                          const char *char_ptr;
                                                                                          const unsigned long int *longword_ptr;
                                                                                          unsigned long int longword, himagic, lomagic;
                                                                                        
                                                                                          /* Handle the first few characters by reading one character at a time.
                                                                                             Do this until CHAR_PTR is aligned on a longword boundary.  */
                                                                                          for (char_ptr = str; ((unsigned long int) char_ptr
                                                                                                    & (sizeof (longword) - 1)) != 0;
                                                                                               ++char_ptr)
                                                                                            if (*char_ptr == '\0')
                                                                                              return char_ptr - str;
                                                                                        
                                                                                          /* All these elucidatory comments refer to 4-byte longwords,
                                                                                             but the theory applies equally well to 8-byte longwords.  */
                                                                                        
                                                                                          longword_ptr = (unsigned long int *) char_ptr;
                                                                                        
                                                                                          /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits
                                                                                             the "holes."  Note that there is a hole just to the left of
                                                                                             each byte, with an extra at the end:
                                                                                        
                                                                                             bits:  01111110 11111110 11111110 11111111
                                                                                             bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
                                                                                        
                                                                                             The 1-bits make sure that carries propagate to the next 0-bit.
                                                                                             The 0-bits provide holes for carries to fall into.  */
                                                                                          himagic = 0x80808080L;
                                                                                          lomagic = 0x01010101L;
                                                                                          if (sizeof (longword) > 4)
                                                                                            {
                                                                                              /* 64-bit version of the magic.  */
                                                                                              /* Do the shift in two steps to avoid a warning if long has 32 bits.  */
                                                                                              himagic = ((himagic << 16) << 16) | himagic;
                                                                                              lomagic = ((lomagic << 16) << 16) | lomagic;
                                                                                            }
                                                                                          if (sizeof (longword) > 8)
                                                                                            abort ();
                                                                                        
                                                                                          /* Instead of the traditional loop which tests each character,
                                                                                             we will test a longword at a time.  The tricky part is testing
                                                                                             if *any of the four* bytes in the longword in question are zero.  */
                                                                                          for (;;)
                                                                                            {
                                                                                              longword = *longword_ptr++;
                                                                                        
                                                                                              if (((longword - lomagic) & ~longword & himagic) != 0)
                                                                                            {
                                                                                              /* Which of the bytes was the zero?  If none of them were, it was
                                                                                                 a misfire; continue the search.  */
                                                                                        
                                                                                              const char *cp = (const char *) (longword_ptr - 1);
                                                                                        
                                                                                              if (cp[0] == 0)
                                                                                                return cp - str;
                                                                                              if (cp[1] == 0)
                                                                                                return cp - str + 1;
                                                                                              if (cp[2] == 0)
                                                                                                return cp - str + 2;
                                                                                              if (cp[3] == 0)
                                                                                                return cp - str + 3;
                                                                                              if (sizeof (longword) > 4)
                                                                                                {
                                                                                                  if (cp[4] == 0)
                                                                                                return cp - str + 4;
                                                                                                  if (cp[5] == 0)
                                                                                                return cp - str + 5;
                                                                                                  if (cp[6] == 0)
                                                                                                return cp - str + 6;
                                                                                                  if (cp[7] == 0)
                                                                                                return cp - str + 7;
                                                                                                }
                                                                                            }
                                                                                            }
                                                                                        }
                                                                                        libc_hidden_builtin_def (strlen)



                                                                                        А вот так у нас:
                                                                                        Тык
                                                                                          .title  C$STRLEN
                                                                                            edit_level  005
                                                                                        ; Facility: 
                                                                                        ;   DEC C Run Time Library on the Alpha/VMS Platform
                                                                                        ;--
                                                                                            ; r16 = src pointer
                                                                                            ; Returns r0 = length
                                                                                            ; Destroys r16,r27-r28
                                                                                        
                                                                                                .psect $LINK$,noexe,nowrt
                                                                                            $PROCEDURE_DESCRIPTOR DECC$STRLEN, ALIASES=decc$strlen, ENTRY=strlen_code
                                                                                                .psect $CODE,pic,shr,exe,nord,nowrt   ; A 3 instruction jacket should do
                                                                                        
                                                                                        strlen_code:
                                                                                            ldq_u   r27, (r16)      ; Get QW containing start of string
                                                                                            lda r28, -1(r31)        ; Mask of all ones
                                                                                            mskql   r28, r16, r28       ; Nonzeros in low bytes to be ignored
                                                                                            and r16, #7, r0     ; Alignment = bytes not to be counted
                                                                                            or  r27, r28, r27       ; Fill ignored bytes with nonzeros
                                                                                            cmpbge  r31, r27, r27       ; Any null bytes in this QW?
                                                                                            subq    r31, r0, r0     ; Initialize count to -alignment
                                                                                            bne r27, bottom     ; Skip if null byte seen
                                                                                        
                                                                                        loop:   ldq_u   r27, 8(r16)     ; Load next QW of string
                                                                                            addq    r16, #8, r16        ; Advance pointer
                                                                                            addq    r0, #8, r0      ; Increment length
                                                                                            cmpbge  r31, r27, r27       ; Any nulls in this QW?
                                                                                            beq r27, loop       ; Repeat if not
                                                                                        
                                                                                        bottom: and r27, #^xF, r28      ; Null in low longword?
                                                                                            subq    r27, #1, r16        ; Complement the lowest 1-bit in mask
                                                                                            blbs    r27, done       ; Exit if null appears in first byte
                                                                                            andnot  r27, r16, r27       ; Make single-bit mask of null location
                                                                                            beq r28, geq_4      ; Skip if null is in high longword
                                                                                            srl r27, #2, r27        ; Map 2/4/8 --> 0/1/2
                                                                                            addq    r0, #1, r0      ; Bump length by one...
                                                                                            addq    r0, r27, r0     ; ... and then by null location
                                                                                        done:   ret r31, (r26)
                                                                                        
                                                                                            ;.align quad
                                                                                        geq_4:  srl r27, #5, r28        ; Map 10/20/40/80 --> 0/1/2/4
                                                                                            srl r27, #7, r27        ; Map 10/20/40/80 --> 0/0/0/1
                                                                                            addq    r0, #4, r0      ; Bump length by four
                                                                                            subq    r28, r27, r28       ; Compute location within high LW...
                                                                                            addq    r0, r28, r0     ; ... and add to length
                                                                                            ret r31, (r26)
                                                                                        
                                                                                            .end



                                                                                        Ну или
                                                                                        так
                                                                                        #if defined(__ia64)
                                                                                        /*
                                                                                        ** Only compile on IA64 due to problems with linkages set in STRING.H
                                                                                        ** for Alpha and VAX.
                                                                                        */
                                                                                        #include <editlevel.src>
                                                                                        #pragma module c$strlen EDIT_LEVEL(1)          /* number of bytes in string */
                                                                                        
                                                                                        #include <string.h>
                                                                                        #include <wide_types.src>
                                                                                        /*
                                                                                        **  strlen
                                                                                        **  Return values:
                                                                                        **      Returns the number of bytes in a string.
                                                                                        */
                                                                                        
                                                                                        /* Disable builtin name */
                                                                                        #undef strlen
                                                                                        
                                                                                        #ifdef __ALPHA
                                                                                        #define strlen decc$strlen2
                                                                                        #endif
                                                                                        
                                                                                        __size_t strlen(__wide_const_char_ptr s)
                                                                                        {
                                                                                            register __wide_const_char_ptr ps  = s;
                                                                                            register __wide_const_char_ptr b  = 
                                                                                                (__wide_char_ptr)((__int64)(s + 7) & (__int64)(~7));
                                                                                        
                                                                                            /* Check for zero byte up until a quadword boundary */
                                                                                            for (; ps < b && *ps ; ps++);
                                                                                        
                                                                                            /* Check full quadwords (faster) if zero byte not found. */
                                                                                            if (*ps) {
                                                                                                register __wide_const_char_ptr qword = ps;
                                                                                        
                                                                                            /* Loop until next quadword has null byte */
                                                                                                for (; qword[0] && qword[1] && qword[2] && qword[3] &&
                                                                                                       qword[4] && qword[5] && qword[6] && qword[7];
                                                                                                       qword  += 8 );
                                                                                        
                                                                                            /* Find the null byte within the last quadword */
                                                                                                for (ps=qword; *ps; ps++);
                                                                                            }
                                                                                        
                                                                                            /* return (end-start) = size of string */
                                                                                            return ps - s;
                                                                                        }
                                                                                        
                                                                                        #endif /* __ia64 */
                                                                                        

                                                                                        И сколько нужно тесткейсов написать, чтобы ВСЕ варианты обработать? Да, для банальной strlen :) А ещё интеграционные тесты, регресс, и прочее.

                                                                                        И это самый простой пример.

                                                                                        Про не такую уж стандартную «ерунду» и не говорю. Но если будет принят хоть какой-то вперёд идущий стандарт, это подстегнёт разработчиков компиляторов подтягиваться. Для нас это хорошо. Ну и переложит ответственность на других :-) А то пока за CRTL в данном случае отвечаем мы, компилятор сам по себе, стандарты вообще фиг знает где болтаются :)

                                                                                        Не всё мне тут нравится, но я за определённость. OK, синтаксис K&R убрали, но… Или уже POSIX, или ну в пень эти виляния.

                                                                                        P.S. Прекрасно понимаю ваши проблемы. Но и у нас их тоже до фига. А стандарт… Он для всех (читай для большинства) :)
                                                                                          0
                                                                                          Чем и занимаемся сейчас…

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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


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


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

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

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

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


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

                                                                                            0

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

                                                                                    0

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


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

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

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

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


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

                                                                                          0
                                                                                          Согласен, ошибаюсь, все не так просто.
                                                                                          Мысли в порядок:
                                                                                          В
                                                                                          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…
                                                                                    –1
                                                                                    А разве опускание имен аргументов не было возможно раньше?
                                                                                      –2
                                                                                      Бог создал труд и обезьяну;
                                                                                      Чтоб получился человек.
                                                                                      «Буханку» же господь не трогал;
                                                                                      Та сразу вышла хорошо.
                                                                                        0

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

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

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

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

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

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

                                                                                          0
                                                                                          Уверен, большая часть читателей в курсе, как происходит развитие 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, таким его и надо было оставить

                                                                                            0

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

                                                                                              0

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

                                                                                              +2

                                                                                              Что значит 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.
                                                                                              +1
                                                                                              «новые» ключевые слова: true, false, alignas, alignof, bool, static_assert и другие.

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


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

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

                                                                                                0

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


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

                                                                                                  –1

                                                                                                  Вброшу: это уже сейчас есть в Rust.

                                                                                                    0

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

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

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

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

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

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

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

                                                                                                                      Да при чем тут программист? Это сделает злоумышленник. Сколько уже было подобных уязвимостей, когда что-то где-то компилятор подхватывает без проверок всяких. Это всегда острая проблема, когда поведение компилятора модифицируется какими-то внешними файлами и влияет на генерацию кода. Было бы не очень хорошо, если бы компиляция какого-то чужого кода позволила стырить системные файлы с моего компьютера, а с включением произвольных файлов в код это становится возможным. Еще хуже, если компиляторы разрешат симлинки.
                                                                                                                        +1
                                                                                                                        Нет уж, последствия 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 по сравнению с этим — детский лепет.
                                                                                                      0

                                                                                                      А это библиотечный макрос, или встроен в ядро языка?

                                                                                                        0

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

                                                                                                    +1
                                                                                                    Пример с использованием __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)
                                                                                                      0

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


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

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

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

                                                                                                      Only users with full accounts can post comments. Log in, please.