Почему я всё ещё люблю C, но при этом терпеть не могу C++? +69


AliExpress RU&CIS

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



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

Почему C — это не самый лучший язык программирования?


Сразу скажу то, что, пожалуй, и так все знают: нет такого понятия, как «самый лучший язык программирования». С каждым языком связан набор задач, для решения которых он подходит лучше всего. Например, хотя и можно заниматься трассировкой лучей в Excel, применяя VBA, лучше будет делать это с использованием более подходящего языка. Поэтому полезно знать об ограничениях языков программирования — чтобы не жаловаться на то, что веб-серверы не пишут на Fortran, и на то, что почти нигде Perl или C++ не используется в роли внутренних скриптовых языков. C может считаться не очень хорошим языком по причинам, которые я перечислю ниже (это — помимо того, что язык этот просто очень старый и того, что его нельзя назвать активно развивающимся языком, но это — дело вкуса).

В синтаксисе C имеются неоднозначные конструкции (например, символ * может играть роль обычного оператора умножения, может применяться в виде унарного оператора разыменования, или может использоваться при объявлении указателей; а радости работы с typedef достойны отдельной статьи).

Этот язык не является безопасным. Например — в C-программах довольно часто встречаются ошибки, связанные с обращением к элементам массивов, которые находятся за пределами границ массивов. В языке нет проверок на ошибки такого рода, производимых во время выполнения программы. А вот, например, в Borland Pascal, не говоря уже о более современных языках, такие проверки есть (это — плюс, даже если подобные возможности можно, пользуясь параметрами компиляции, отключить ради повышения производительности). А использование в языке указателей ещё сильнее усложняет задачу программиста по поддержанию кода в хорошем состоянии. И, кроме того, C имеет и некоторые другие неприятные особенности, вроде возможности вызова функции без объявления прототипа, в результате чего функции легко можно передать аргумент неправильного типа.

У C довольно-таки скромная стандартная библиотека. В некоторых других языках программирования могут даже иметься стандартные веб-серверы (или, как минимум, всё, что нужно для создания таких серверов), а в стандартной библиотеке C нет даже описаний контейнеров.

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


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

Например, если нужно получить значение элемента массива, имея два смещения, одно из которых может быть отрицательным числом, то при программировании на C можно воспользоваться такой конструкцией: arr[off1 + off2]. А при использовании Rust это уже будет arr[((off1 as isize) + off2) as usize]. C-циклы часто короче, чем идиоматичные механизмы Rust, использование которых предусматривает комбинирование итераторов (конечно, обычными циклами можно пользоваться и в Rust, но это не приветствуется линтером, который всегда рекомендует заменять их итераторами). И, аналогично, мощными инструментами являются memset() и memmove().

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

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

При чём тут C++?


Если говорить о C++, то хочу сразу сказать, что я не отношусь к тем, кто ненавидит этот язык. Если вы этим языком пользуетесь и он вам нравится — я ничего против этого не имею. Я не могу отрицать того, что C++, в сравнении с C, даёт нам два следующих преимущества. Во-первых — это улучшение структуры программ (это — поддержка пространств имён и классов; в Simula, в конце концов, есть хоть что-то хорошее). Во-вторых — это концепция RAII (если описать это в двух словах, то речь идёт о наличии конструкторов для инициализации объектов при их создании и о наличии деструкторов для очистки ресурсов после уничтожения объектов; а если глубже разработать эту идею, то можно прийти к понятию «времени жизни объекта» из Rust). Но, в то же время, у C++ есть несколько особенностей, из-за которых мне этот язык очень и очень не нравится.

Это, прежде всего — склонность языка постоянно вбирать в себя что-то новое. Если в каком-то другом языке появится какая-нибудь возможность, которая станет популярной, она окажется и в C++. В результате стандарт C++ пересматривают каждые пару лет, и при этом всякий раз в него добавляют новые возможности. Это привело к тому, что C++ представляет собой монструозный язык, которого никто не знает на 100%, язык, в котором одни возможности часто дублируют другие. И, кроме того, тут нет стандартного способа указания того, возможности из какой редакции C++ планируется использовать в коде. В Rust это поддерживается на уровне единицы компиляции. Если я правильно помню, в C++ для той же цели предлагалась концепция эпох, но эта затея не удалась. И — вот одно интересное наблюдение. Время от времени мне попадаются новости о том, что кто-то в одиночку (и за приемлемое время) написал рабочий компилятор C. Но я ни разу не встречал похожих новостей о компиляторе C++.

Вторая особенность C++, из-за которой мне не нравится этот язык, заключается в том, что это, на самом деле, не просто смесь нескольких языков. Это, кроме того, мета-язык, иначе говоря — язык, в котором используются шаблоны. Я понимаю — для чего это создано, согласен с тем, что это — лучше, чем препроцессор C для типонезависимого кода. Но, на самом деле, это приводит к появлению некрасивого громоздкого кода. Это означает переход от идеи «заголовочный файл содержит объявления, а компилируемый код — функционал» к идее «заголовочный файл содержит весь код, используемый в проекте, в состав которого входит этот файл». Мне не нравится, когда код долго компилируется, а этот подход ведёт к увеличению времени компиляции проектов.

И, наконец, я мог бы вообще не обращать внимания на C++, если бы этот язык не был бы связан с C и не оказывал бы на C плохое влияние. Я не говорю о ситуации, когда, говоря о C и C++, их объединяют, упоминая как «C/C++», и считая, что тот, кто знает C, знает и C++. Я имею в виду связь между языками, которая оказывает влияние и на стандарты, и на компиляторы. С одной стороны — C++ основан на C, что придало C++, так сказать, хорошее «начальное ускорение». А с другой стороны — сейчас C++, вероятно, лучше смотрелся бы без большей части своего «C-наследия». Конечно, от него пытаются избавиться, называя соответствующие конструкции устаревшими, но C-фундамент C++ никуда пока не делся. Да и будет ли популярным, скажем, некий «С++24», вышедший в виде самостоятельного языка, основанного на C++21 и лишённого большей части устаревших механизмов? Не думаю.

Воздействие компиляторов C++ на C


Вышеописанная связь C и C++ приводит к тому, что к C относятся как к C++, лишённому некоторых возможностей. Печально известным примером такого восприятия C является C-компилятор Microsoft, разработчики которого не позаботились о поддержке возможностей C99 до выхода версии компилятора 2015 года (и даже тогда разработчики придерживались стратегии «bug-for-bug compatibility», когда в новой реализации чего-либо воспроизводят старые известные ошибки; делалось это для того, чтобы пользователи компилятора не были бы шокированы, внезапно обнаружив, что вариадические макросы вдруг там заработали). Но тот же подход можно видеть и в стандартах, и в других компиляторах. Эти проблемы связаны друг с другом.

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

Я, конечно, говорю, о «неопределённом поведении», и о том, как оно рассматривается компиляторами. Эта тема стала популярным «пугалом» (код полагается на способ представления чисел с дополнением до двух; в результате в нём имеется неопределённое поведение и компилятор может выбросить, оптимизировать целый блок кода!).

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

  • Поведение, определяемое архитектурой системы (то есть — то, что зависит от архитектуры процессора). Сюда, в основном, входят особенности выполнения арифметических операций. Например, если я знаю, что целевая машина использует для представления чисел дополнение до двух (нет, это не CDC 6600), то почему компилятор (который, как представляется, тоже знает об особенностях целевой архитектуры) должен считать, что система будет вести себя иначе? Ведь тогда он сможет лучше выполнять некоторые теоретически возможные оптимизации. То же применимо к операциям побитового сдвига. Если я знаю о том, что в архитектуре x86 старшие биты, выходящие за пределы числа, отбрасываются, а на ARM отрицательный сдвиг влево — это сдвиг вправо, почему я не могу воспользоваться этими знаниями, разрабатывая программу для конкретной архитектуры? В конце концов, то, что на разных платформах целые числа имеют разные размеры в байтах, считается вполне приемлемым. Компилятор, обнаружив нечто, рассчитанное на конкретную платформу, может просто выдать предупреждение о том, что код нельзя будет перенести на другую платформу, и позволить мне писать код так, как я его писал.
  • Неочевидные приёмы работы с указателями и каламбуры типизации. Такое ощущение, что плохое отношение к подобным вещам возникло лишь ради потенциальной возможности оптимизации кода компиляторами. Я согласен с тем, что использование memcpy() на перекрывающихся областях памяти может, в зависимости от реализации (в современных x86-реализациях копирование начинается с конца области) и от относительного расположения адресов, работать неправильно. Разумно будет воздержаться от подобного. А вот другие ограничения того же плана кажутся мне менее оправданными. Например — запрещение одновременной работы с одной и той же областью памяти с использованием двух указателей разных типов. Я не могу представить себе проблему, возникновение которой может предотвратить наличие подобного ограничения (это не может быть проблема, связанная с выравниванием). Возможно, сделано это для того чтобы не мешать компилятору оптимизировать код. Кульминацией всего этого является невозможность преобразования, например, целых чисел в числа с плавающей запятой с использованием объединений. Об этом уже рассуждал Линус Торвальдс, поэтому я повторяться не буду. С моей точки зрения это делается либо для улучшения возможностей компиляторов по оптимизации кода, либо из-за того, что этого требует C++ для обеспечения работы системы отслеживания типов данных (чтобы нельзя было поместить экземпляр некоего класса в объединение, а потом извлечь его как экземпляр совсем другого класса; это тоже может как-то повлиять на оптимизации).
  • Поведение кода, определяемое реализацией (то есть — поведение, которое не в точности отражает то, что предписано стандартом). Мой любимый пример подобной ситуации — это вызов функции: в зависимости от соглашения о вызове функций и от реализации компилятора аргументы функций могут вычисляться в совершенно произвольном порядке. Поэтому результат вызова foo(*ptr++, *ptr++, *ptr++) неопределён и на подобную конструкцию не стоит полагаться даже в том случае, если программисту известна целевая архитектура, на которой будет выполняться код. Если аргументы передаются в регистрах (как в архитектуре AMD64) компилятор может вычислить значение для любого регистра, который покажется ему подходящим.
  • Полностью неопределённое поведение кода. Это — тоже такой случай, когда сложно спорить со стандартом. Пожалуй, самый заметный пример такого поведения кода связан с нарушением правила, в соответствии с которым значение переменной в одном выражении можно менять лишь один раз. Вот как это выглядит в одном знаменитом примере: i++ + i++. А вот — пример, который выглядит ещё страшнее: *ptr++ = *ptr++ + *ptr++е.

C++ — это язык более высокого уровня, чем C. В то время, как в C++ имеется большинство возможностей C, использовать эти возможности не рекомендуется. Например, вместо прямого преобразования типов надо применять reinterpret_cast<>, а вместо указателей надо применять ссылки. От С++-программистов не ждут того, что они будут понимать низкоуровневый код так же хорошо, как C-программисты (это, конечно, лишь «средняя температура по больнице», в реальности всё зависит от конкретного программиста). И всё же, из-за того, что существует очень много C++-программистов, и из-за того, что C и C++ часто воспринимают как «C/C++», C-компиляторы часто расширяют в расчёте на поддержку ими C++ и переписывают на C++ для того чтобы упростить реализацию сложных конструкций. Это произошло с GCC, и того, кто начнёт отлаживать с помощью GDB С++-код, находящийся в .c-файлах, ждёт много интересного. Грустно то, что для того чтобы скомпилировать код C-компилятора нужен C++-компилятор, но, чему нельзя не радоваться, всё ещё существуют чистые C-компиляторы, вроде LCC, PCC и TCC.

Итоги


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

Но, по крайней мере, нельзя заменить C90 на какой-нибудь «C90 Special Edition» и сделать вид, что C90 никогда не существовало.

Как вы относитесь к языку C?





Комментарии (390):

  1. ncr
    /#23166756 / +5

    TL;DR: «мне не нравится, что C++ зачем-то мешает мне писать г-код»

    • JerleShannara
      /#23167516 / -1

      «Мне не нравится, что C++ старается отодвинуть меня от процессора»

    • Tangeman
      /#23167564

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

      • RarogCmex
        /#23171424 / +1

        Тем не менее, на том же Хаскеле или Агде писать г-код намного сложнее, ибо он не скомпилируется.

        • Tangeman
          /#23173828

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

          • 0xd34df00d
            /#23173894

            Неэффективно реализованный алгоритм

            Можно доказывать выполнение тех или иных инвариантов (например, что красно-чёрное дерево действительно красно-чёрное, а не как в микрософтовской STL пару лет назад).


            отсутствие предварительной валидации данных и последующий вылет по этой причине в другой части

            Это вот как раз самое оно типами обкладывать.


            плохой опыт пользователя

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

            • Tangeman
              /#23174144

              Можно доказывать выполнение тех или иных инвариантов

              И как вы докажите что strcmp(arg, "abc") будет быстрее (или наоборот) чем userstrcmp(arg, "abc")? Компилятор не знает чего хочет программер, и уж тем более пользователь.

              Это вот как раз самое оно типами обкладывать.

              У вас есть на входе аргумент (полученный из внешнего источника), и нужно проверить его на валидность - например, для иллюстрации, с помощью strcmp(arg, "aBc") == 0 - и как компилятор (даже самый умный) догадается что на самом деле нужно было strcasecmp(arg, "abc") == 0?

              опыт пользователя в том ПО, что я обычно пишу, в лучшем случае сводится к запуску сервера, и всё

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

              • 0xd34df00d
                /#23174294

                И как вы докажите что strcmp(arg, "abc") будет быстрее (или наоборот) чем userstrcmp(arg, "abc")?

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


                У вас есть на входе аргумент (полученный из внешнего источника), и нужно проверить его на валидность — например, для иллюстрации, с помощью strcmp(arg, "aBc") == 0 — и как компилятор (даже самый умный) догадается что на самом деле нужно было strcasecmp(arg, "abc") == 0?

                Если где-то дальше вы опираетесь на то, что там нет подстроки abc, то это другое место потребует доказательство этого факта, а его у вас на руках не будет.


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

                Я бы сказал, что вопрос корректности приложения куда важнее вопроса того, сколько оно кушает памяти. Если корректность вас не волнует, запустите на машине while (true) { sleep(1000); }, и оно будет есть очень мало памяти и очень мало процессора.

                • Tangeman
                  /#23174376

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

                  Как пример - использование чего-то типа regexp_match(arg, /abc/) вместо arg == "abc" (и это когда нужно убедиться именно в последнем) - заведомо говнокод, причём встречается постоянно в js/php, да ещё и в местах которые выполняются газиллионы раз.

                  то это другое место потребует доказательство этого факта, а его у вас на руках не будет.

                  Вот оно! А если то "другое место" писал кто-то без понимания что нужно доказательство? Или это один человек? Мы снова возвращаемся к тому что компилятор (а о нём речь) не в состоянии этого сделать без помощи человека, просто в принципе не способен, да и говнокод может появиться из говноспецификации - то есть в том нередком случае когда кодер пишет ровно как сказано а заказчик не знает точно как нужно и совсем не знает как правильно.

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

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

                  Разумеется, нужно правильно же балансировать и не тратить 90% усилий на оптимизацию маленького цикла, но и тянуть за собой полновесную БД ради одной таблички в 100 строк (типа "на SQL удобнее") тоже не особенно оптимально - и тоже можно считать г-кодом.

                  • 0xd34df00d
                    /#23174570

                    А если то "другое место" писал кто-то без понимания что нужно доказательство? Или это один человек?

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


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


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

                    Замечательно. Но, ещё раз, только после того, как я убедился, что код корректен.

                    • Tangeman
                      /#23174616

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

                      Вот так просто, вместо cmd == "do-something-useful" там вкралась опечатка и получилось cmd == "do-something-useeful" , или даже банально x = x * mult где вместо const mult = 2 у нас получилось const mult = 3 - и всё, ничего не работает (или работает не так) - получили г-код, вообще без шансов на обнаружение оного со стороны компилятора, любого из существующих.

                      • unC0Rr
                        /#23174928 / +1

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

            • Antervis
              /#23175260

              Можно доказывать выполнение тех или иных инвариантов (например, что красно-чёрное дерево действительно красно-чёрное, а не как в микрософтовской STL пару лет назад).

              а можно поподробнее? А то даже по меркам MS зашкварно

  2. dendron
    /#23166794

    Ну раз уж тут тема для дискуссии и вкусовщины, то добавлю и своё скромное мнение как “погромист” на C++.

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

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

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

    Можно ещё добавить что язык C вообще не подходит для написания пользовательских приложений (и нет, консольные программы — это не приложения нормального человека, сейчас не 70-е года). Если посмотреть на тот же GTK — начинает хлестать кровь из глаз. Ехал макрос через макрос, хочется немедленно удалить эту гадость и никогда не вспоминать. Отвратительно. Впрочем, это уже не важно (по причинам указанным выше).

    К одному из самых главных недостатков C (который к сожалению унаследовал и C++) я бы отнёс не просто отсутсвие контроля выхода за границы массива, а самую отвратительную в мире работу со строками и их представление. В сочетании с архитектурой Фон Неймана, каждый день в программах на C находят очередную глупейшую дырень вида «послав специальную строку можно выполнить произвольный код». Не надоело?

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

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

    Как я отношусь к языку С? Считаю что он безнадёжно устарел и ему пора в утиль, ради блага всего человечества. При том что я не являюсь поклонником Rust, и как язык C мне тоже симпатичен за счёт его простоты.

    • creker
      /#23166862 / +1

      Не надоело?

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

      Далее, что там у нас с многопоточностью?

      Появилась в С11 вместе с основными примитивами. Даже атомики наконец добавили.

      • khim
        /#23167760

        Если мы хотим, чтобы С был языком низкого уровня и переносимым ассемблером
        Вы, может быть, этого и хотите, но не является C “переносимым ассемблером”, успокойтесь уже. Или вы всерьёз можете представить себе “переносимый ассемблер”, где вот такой код:
            if (p == q) {
                *p = 1;
                *q = 2;
                printf("%d %d\n", *p, *q);
            }
        
        даст вот такой выхлоп:

        1 2
        Program returned: 0
        


        И да, это -std=c89 -Wall -Wextra на трёх ко мпиляторах из всем известной “большой четвёрки” (Clang/GCC/ICC/MSVC).

        • SergeyMax
          /#23167938 / +1

          Запустил в VS, выставил уровень оптимизации O2, получил результат 2 2. Что я упустил?

          • khim
            /#23168268

            Что именно вы там запустили? <code>malloc</code> и <code>realloc</code>, надеюсь, в коде остались?

            • SergeyMax
              /#23168284 / +2

              Отвечаю сам себе: оптимизацию выставил в конфигурации Release, а скомпилил конфигурацию Debug. Тупанул, извините. Действительно, при включении оптимизации вместо 2 2 получается 1 2.

        • lieff
          /#23168396

          Забавно, а вот gcc справился https://godbolt.org/z/4aE5z6cce


          ASM generation compiler returned: 0
          Execution build compiler returned: 0
          Program returned: 0
          2 2

          Единственный из всех получаетя.

          • khim
            /#23168452 / +2

            Это не то, что “gcc справился”. Это просто в gcc не добавили идиотскую оптимизацию для realloc.

            Проблема возникает в том месте, где стандарт (причём, кстати, только C99+ стандарт, в C89 этого не было) говорит, с одной стороны, что указатель, переданный в realloc “протухает”, а с другой - явно указывает, что тот указатель, что вернётся, может быть равен переданному.

            Вот разработчики трёх компиляторов из четырёх решили “отоптимизировать realloc”. Ну потому что могут.

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

            В других местах GCC чудит примерно так же. В соотвествующем документе есть примеры:
            http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

            • lieff
              /#23168544

              А что за оптимизация? По ссылке я понял что дело в


              The behavior is undefined in the following circumstances:
              -The value of a pointer that refers storage allocated by a call to the free or realloc function is used (7.22.3 ).

              То есть возможно просто отработал более очевидно для человека, а оптимизации не причем.
              Еще забавно что -fsanitize=undefined этот случай не ловит.

              • khim
                /#23168592

                Опимизация в том, что после вызова realloc указатель p не может указывать на тот же объект, что и q (да, даже если я явно проверил, p и q указывают на один участок памяти).

                Соответственно *p и *q - это разные объекты (первый со значением 1, второй со значением 2) и их можно подставить туда, где они используются.

                GCC так тоже умеет, это alias analysis называется. Он просто не считает realloc “специальной”, потому в данном конкретном случае код не ломается.

                И, разумеется, -fsanitize=undefined это поймать не может: если оптимизаций нет, то там нечему в ратайме становиться undefined, а если есть - то скомпилированный код не содержит ничего подозрительного (он просто содержит код не имеющий отношения к тому, что написал программист, но это же мелочи, правда?).

                • creker
                  /#23168600

                  А почему в таком случае не был выкинут вообще весь блок? Проверка бессмысленная и никогда не должна сработать, по сути. Но компиляторы не только оставили ее, но еще и творят чудеса с тем, что внутри.

                  • khim
                    /#23168664

                    Потому что в стандарте явно написано:

                    The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

                    То есть указатель невалидный, но сранивать его можно:

                    Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function,both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.

                    “Гениальное” прочтение всего этого разработчиками clang'а таково: указатель p, после вызова realloc - это указатель на “one past the end of array”.

                    Соотвественно p == q - это ешё не UB, а вот *p - это уже UB.

                • lieff
                  /#23168618

                  Ну сами оптимизации у всех компилеров выглядят абсолютно идентично. Он опустил запись в память увидев что q больше нигде не используется, сразу в регистры записал чиселки для printf, вот только сами эти чиселки из недр оптимизатора получились разные для gcc и остальных.


                  -fsanitize=undefined мог бы отлавливать обращение к p потипу как -fsanitize=address (в этом случае кстати p и q становятся разными). Но увы, не настолько продвинутый.

                • 0xd34df00d
                  /#23169460

                  GCC так тоже умеет, это alias analysis называется.

                  Это вы ещё pointer provenance не ковыряли.

                  • khim
                    /#23169750

                    Как раз ковырял. И меня всё время преследовал вопрос “а кто, собственно, дал право этим ломастерам ломать валидные программы”?

                    Ответа я так и не получил. Ибо в стандарте никакого provenance нету.

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

                    Однако если нормальный человек из наличия там такой фразы заключает, что “ну, наверное, раз написано, что можно проверить, то значит, что-нибудь полезное можно с этим сделать”, то у разработчиков компилятора подход строго противоположный “что, мы недостаточно изуродовали C99, чтобы он позволил нам издеваться над разработчиком оптимизировать код?… какая жалость — в следующей версии стандарта поправим”.

                    При этом их вот ну вообще ни разу не волнует как с этим и на этом разработчики должны код писать (даже сами разработчики clang/gcc, как показывает практика, с этим не справляются).

                    У Rust есть Miri и вообще они хотя бы понимают, что то, что происходит с UB - уже давно вышло за рамки всякого приличия и приближается к полноценной войне между разработчиками компиляторов и программистами… разработчики C/C++ компиляторов, похоже, опомнятся только тогда, когда их уволят за ненадобностью после того, как пользователей C/C++ станет минимальное количество…

                    • Antervis
                      /#23170032 / +1

                      Проблема возникает в том месте, где стандарт (причём, кстати, только C99+ стандарт, в C89 этого не было) говорит, с одной стороны, что указатель, переданный в realloc “протухает”, а с другой — явно указывает, что тот указатель, что вернётся, может быть равен переданному.
                      Вообще имеет смысл. После вызова realloc данные под p могут и не лежать, обращаться к буферу вы должны только через q. То есть проверить p == q вы можете, а разыменовывать p нет.

                      Забавно кстати, что в с++ режиме такого не происходит.
                      Но в данном случае они не могут никаким provenance прикрыться, так как стандарт явно говорит о том, что указатель после вызова realloc может оказаться таким же, каким был до.
                      Давайте на секунду предположим что realloc реализован как free + malloc. Указатели старой и новой аллокаций могут быть равны, но старый указатель либо реально либо формально смотрит в уничтоженный объект.

                      • khim
                        /#23171692

                        То есть проверить p == q вы можете, а разыменовывать p нет.

                        А смысла тогда в такой проверке? Вот зачем писать о том, что он может быть таким же, если использовать его нельзя? Вот от он ответ, туточки. В C89 про realloc было написано нечто совсем-совсем иное:

                        The realloc function returns either a null pointer or a pointer to the possibly moved allocated space.

                        И тут уж, извините, совсем без шансов: если оно “possibly moved”, то в случае когда это “possibly” не реализовалось, то вы обязаны оставить его в покое.

                        Забавно кстати, что в с++ режиме такого не происходит.

                        Происходит. Вы, скорее всего, в интерфейсе godbolt запутались: https://godbolt.org/z/vEa6o7vs9

                        Он сбрасывает настроки компиляторов при переходе C?C++, так как, например --std=c89 для C++ не имеет смысла (зато --std=c++98 не имеет смысла для C).

                        Указатели старой и новой аллокаций могут быть равны, но старый указатель либо реально либо формально смотрит в уничтоженный объект.

                        А пофиг абсолютно. В стандарте никакого provenance нету, так что равенство указателей должно обозначать равенство поведения.

                        Вот когда добавят (и начиная с той версии, в которой добавят) ситуация может измениться. Но задним числом такие вещи в старый код нельзя добавлять.

                        Во всяком случае это было бы конструктивной позицией. Однако позиция разработчиков LLVM диаметрально противоположная:

                        I think the expectation that compilers will expose flags to disable certain optimizations is a bit questionable. -fno-strict-aliasing is easy because LLVM does not intrinsically believe in strict aliasing; it's extra information Clang has to give it.

                        OTOH provenance is something LLVM violently believes in, at the level of alloca, malloc, and similar intrinsics scribbling provenance information all over LLVM's internal representions.

                        Офигеть, да? “Моя программа валидна и полностью соотвествует стандарту” — “а нам пофиг, она не соотвествует нашим хотелкам, а если она, вдруг, соотвествует-таки стандарту, то ничего страшного, мы стандарт поправим”.

                        Такой подход, фактически, обозначает, что этот компилятор нельзя использовать вообще никогда и ни для чего. Unfit for any purpose. Ну потому что если он может ломать вылидные программы и если это случается — то они задним числом объявляются невалидными… то как вообще написать программу, которую бы это чудо гарантированно не посчитало невалидной?

                        Никак очевидно. В общем случае. Мне было предложено обращаться со сложными конструкциями, чтобы разработчики оценили вероятность того, что они могут быть поломаны в будущем!

                        Класс. Фантастика. Кафка отдыхает.

                        Rust, понятно, следует тому же курсу (он же пока что на LLVM основан), но он хотя бы стремится “красными флажками” пометить такой код (то, что под unsafe ведёт себя так же — “правила игры пока что не зафиксированы и в разработке”, но вsafe Rust никакх UB не бывает, если оно туда пролезло, то это ошибка компилятора, которую нужно исправить).

                        • Antervis
                          /#23172406

                          вот вы ниже жалуетесь что

                          Вообще-то, внезапно, все оптимизации (ну или почти все) опираются на UB. То есть буквально, я не могу, навскидку, придумать никакой оптимизации C кода, которая бы не ломала какую-либо программу с UB
                          Как вы вообще можете представить себе реализацию гарантии что программы с UB будут ожидаемо работать?
                          Офигеть, да? “Моя программа валидна и полностью соотвествует стандарту”
                          вот вы прям уверены в этом, да?
                          Rust, понятно, следует тому же курсу (он же пока что на LLVM основан), но он хотя бы стремится “красными флажками” пометить такой код
                          давайте для начала вспомним то, что раст на подобное выдал бы вам ошибку компиляции. И его фронтенду было бы совершенно иррелевантно что в рантайме указатели могут быть равны — лайфтайм прекратился => обращаться по старой ссылке нельзя. А как только вы написали unsafe вы ходите примерно по тому же минному полю. Да и он емнип лайфтаймы не обходит.

                          • khim
                            /#23172814

                            Как вы вообще можете представить себе реализацию гарантии что программы с UB будут ожидаемо работать?

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

                            вот вы прям уверены в этом, да?

                            Да, Уверен. Когда я всю эту муть про pointer provenance обсуждал мне показали DR260: http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm

                            Вот только ключевое решение там нифига не разрешает компиляторам делать то, что они делают с этой программой. Оно звучит, извините, вот как:

                            After much discussion, the UK C Panel came to a number of conclusions as to what it would be desirable for the Standard to mean

                            И это, ну уж извините, но… классика жанра: закон, поверяющий правило. Если коммитет по стандартизации пришёл, after much dicussion, что что-то там would be desirable for the Standard to mean., то это обозначает, что сегодня, сейчас стандарт этого, я извиняюсь, ещё не mean.От только будет mean, возможно, когда-нибудь в будущем.

                            А потому, извините, ключик, позволяющие делать подобные говнооптимизации должен, как минимум, иметься, а в идеале — должен действовать по умолчанию. Уж для С89, для которого вообще никак нельзя интерперетировать эту программу, как программу с UB — так 100%.

                            Вместо этого мне заявили, что although provenance is not defined by either standard, it is a real and valid emergent property that every compiler vendor ever agrees on… после этого стало ясно: всё, приплыли.

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

                            Увы и ах, но пользоваться таким языком нельзя. Не “можно аккуратно”, а просто “нельзя”.

                            Потому как ситуация, когда они плевали на POSIX, WIN32 и прочее (где, например, есть такие чудесные функции какmmap и их аналоги, которые не могут существовать в удовлетворяющей стандарту C/C++ программе) — это уже плохо, но ещё не смертельно.

                            Однако если даже стандарт для этих ломастеров — это что-то, чем они могут попу вытереть после похода в туалет, то всё, приплыли. Такой язык, увы, unfit for any purpose.

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

                            Всё же остальное… для реальных программ, к сожалению, не годится.

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

                            Именно так. Rust бы выдал ошибку компиляции, я бы исправил код — и всё, нет проблем.

                            Да, ошибка компиляции для кода, который когда-то был валидным — это плохо (Haskell этим славится), но, в общем, не смертельно плохо.

                            Сообщение об ошибке во время исполнения? Ещё хуже, конечно, но всё ещё не смертельно. Python так устроен.

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

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

                            Да и он емнип лайфтаймы не обходит.

                            Он позволяет работать с указателями, которые уже как раз лайфтаймы обходят. И да, unsafe Rust — это такое же минное поле, как и C с С++ (пока они базируются на LLVM и GCC, а не на своём собственном компиляторе).

                            Но в реальных проектах на Rust unsafe мало (и разработчики постоянно думают над тем, как сделать так, чтобы его было ещё меньше).

                            А в C/C++ минное поле — вся программа и разработчикам языка, похоже, вообще наплевать как и что программисты должны делать, чтобы она работала.

                            • Antervis
                              /#23172950

                              Вот только ключевое решение там нифига не разрешает компиляторам делать то, что они делают с этой программой. Оно звучит, извините, вот как:
                              нет, почитайте до конца. Решение звучит вот как:
                              If two objects have identical bit-pattern representations and their types are the same they may still compare as unequal (for example if one object has an indeterminate value) and if one is an indeterminate value attempting to read such an object invokes undefined behavior. Implementations are permitted to track the origins of a bit-pattern and treat those representing an indeterminate value as distinct from those representing a determined value. They may also treat pointers based on different origins as distinct even though they are bitwise identical.

                              Вместо этого мне заявили, что although provenance is not defined by either standard, it is a real and valid emergent property that every compiler vendor ever agrees on… после этого стало ясно: всё, приплыли.
                              насколько я понял тут разработчики компиляторов соглашаются в интерпретации стандарта, а не в игнорировании его гарантий, как вы пытаетесь это преподнести. Тогда и беситесь вы, получается, из-за того, что ваша интерпретация видите ли не совпадает с интерпретацией разработчиков компиляторов (которые по совместительству разработчики стандартов). По сути программа как была некорректной, так и осталась, вам лишь формализовали причину.
                              Но в реальных проектах на Rust unsafe мало (и разработчики постоянно думают над тем, как сделать так, чтобы его было ещё меньше).
                              тем не менее unsafe там нужен как раз для всякого такого вот трюкачества. Имо то, что его мало, не значит, что в нём меньше граничных случаев. А отсутствие стандарта (и соответственно прописанных гарантий всех этих граничных случаев) приводит к тому, что этих граничных случаев будет всё больше и их поведение будет всё менее стабильным.
                              А в C/C++ минное поле — вся программа и разработчикам языка, похоже, вообще наплевать как и что программисты должны делать, чтобы она работала.
                              вы игнорируете очень много факторов. Завтра например realloc начнет работать через ремаппинг страниц, и может случиться такая штука, когда после него указатель с тем же битовым представлением начнет маппиться в другой участок физической памяти, даже если физически данные никто не двигал.

                              • khim
                                /#23173338

                                нет, почитайте до конца.

                                Прочитать до конца что, извините? Решение, которое не вступило в силу? Да…, довольно-таки страшненький законопроект. Слава богу, что это — ещё не закон. Посмотрим что они там понапишут в проектах изменений C++23 или в C28. Ну а пока у нас, слава богу, C++20 и C18, где этой чуши нету.

                                насколько я понял тут разработчики компиляторов соглашаются в интерпретации стандарта

                                Нет. Тут разработчики стандарта соглашаются с ломастерами в том, что, во имя оптимизаций, нужно простую и понятную концепцию указателя изуродовать. В будущих стандартах очевидно (именно потому “будет желательно в стандарте иметь”, а не “стандарт говорит”). Вы в английских временах совсем никак, что ли? Будущее от настоящего не отличаете?

                                По сути программа как была некорректной, так и осталась, вам лишь формализовали причину.

                                Ещё раз. Резолюция DR260 не является частью никакого стандарта. Ни C++20, ни C18, ни, тем более, более ранних. И именно то, что сущесвуют не ратифицированные в настоящее время предложения об изменениях стандарта доказывает этот факт.

                                Если бы это всё уже было бы частью стандарта, то вот это вот всё было бы не нужно:

                                http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2090.htm

                                http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2263.htm

                                http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

                                Видите сколько их? Это потому что сами разработчики компиляторов всё никак не могут придумать как же, чёрт побери, этот самый pointer provenance должен реально работать.

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

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

                                А отсутствие стандарта (и соответственно прописанных гарантий всех этих граничных случаев) приводит к тому, что этих граничных случаев будет всё больше и их поведение будет всё менее стабильным.

                                Возможно. И потому разговоры о принятии стандарта ведутся. И Miri разрабатывается. И формализация “правил игры” проводится. Но для того, чтобы от стандарта был какой-нибудь прок — он должен соблюдаться. Если в стандарте написано одно, а в компиляторе другое, то править нужно компилятор, в первую очередь. Даже если для этого в нём придётся отключить вообще все оптимизации к чёртовой матери.

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

                                Но никак не “чёта мало у нас UB в стандарте, давайте добавим ещё пяток и начнём на них полагаться, а в стандарт их добавим потом, когда-нибудь, может быть, если рак на горе свистнет”.

                                вы игнорируете очень много факторов.

                                Я игнорирую всё, кроме текста стандарта, да. Сами же ломастеры, разработчики компиляторов C и C++, ратуют за такой подход, когда им говорят, что то, что они творят — несовместимо ни с POSIX, ни с ECMA-234.

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

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

                                И на что это, извините, повлияет? Если содержимое по адресу записанному в указатель не изменяется, то мне пофиг в каком участке физической памяти оно находится, а если изменяется - то это не соотвествует стандарту.

                                • Antervis
                                  /#23173568

                                  Хотите чего-то большего, ну заведите себе -O10 какое-нибудь которое генерит очень быстрые (пусть и неправильные) программы, не забудьте упоминуть в документации, что в этом режиме работоспособность программ, совместимых со стандартом, не гарантируется — и развлекайтесь в своё удовольствие.
                                  давайте я вам напомню что в режиме сборки по умолчанию (-O0) оптимизации которые вы так хаите выключены. Фактически никто не заставлял вас включать в вашем проекте те оптимизации, поведением которых вы так недовольны.
                                  А когда им это невыгодно, то, вдруг, внезапно, оказывается что текст стандарта предназначен для использования в качестве туалетной бумаги, а ориентироваться нужно на хотелки ломастеров.
                                  ориентироваться нужно на хотелки пользователей языка. Большинство из которых пишут на с++ преимущественно потому, что им важно быстродействие.
                                  Возможно. И потому разговоры о принятии стандарта ведутся. И Miri разрабатывается.
                                  на данный момент miri пытается детектить UB не зная что такое UB.
                                  Если содержимое по адресу записанному в указатель не изменяется, то мне пофиг в каком участке физической памяти оно находится, а если изменяется — то это не соотвествует стандарту.
                                  realloc не обязан возвращать буфер по тому же адресу…

                                  • khim
                                    /#23173786

                                    realloc не обязан возвращать буфер по тому же адресу…

                                    Вот только решает это не компилятор, а операционная система, извините. И, ещё раз напоминаю, что мы рассматрваем случай, когда я явно проверил, что адрес не изменился — причём в режиме C89, в котором вообще нет никакой мути про старые и новые объекты, а realloc делает буквально следующее: the realloc function returns either a null pointer or a pointer to the possibly moved allocated space.

                                    Если оно не было moved, то кто и что могло повлиять на содержимое этой памяти? UB, в этой редакции стандарта, это лишь: the value of a pointer that refers to space deallocated by a call to the free or reallot function is referred to.

                                    А никакой деаллокации тут не было (ибо не было moved allocated space).

                                    ориентироваться нужно на хотелки пользователей языка.

                                    Ну на это ломастеры забили давно и очень прочно. Во внимание принимается только мнение Линуса и только разработчиками GCC. Все остальные на попытку мягко намекнуть, что на стандарте свет клином не сошёлся посылаются в “пешее эротическое” последние лет 10, если не 20.

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

                                    А вот фиг. У нас тут недавно проводили исследование и оказалось, что быстродействие волнует где-то 10-20% разработчиков, пишущих на C++.

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

                                    И их вполне бы устроило быстродействие C#, Java или там Safe Rust, если бы можно было достаточно просто использовать имеющиеся библиотеки, написанные на C++.

                                    Если какой-нибудь Google разработает Rust++ (как Apple разработал, в своё время, Objective C++), позволяющий легко пользовать C++ библиотеки в Rust… результат может ломастеров неприятно удивить.

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

                                    Откуда вы знаете? Да, C как раз можно и в -O0 использовать (и многие так и делают), но стандартная библиотека C++ (и многие её последователи) изначально под оптимизацию заточены.

                                    Кроме того clang ломает программы с UB даже с -O0, так что это не парацея: https://godbolt.org/z/1d7Goaca4

                                    Да, пока что у нас есть только программы с “плохими, очевидными, UB”, которые clang ломает в режиме -O0 и с “хорошими, неочевидными, не описанными в стандарте UB” которые он в -O0 пока не ломает… но ясно, что лыжи уже пора намазывать.

                                    С таким подходом наступление момента, когда он будет ломать вообще всё (кроме бенчмарков — бенчмарки это святое) — только лишь вопрос времени.

                                    на данный момент miri пытается детектить UB не зная что такое UB.

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

                                    • Antervis
                                      /#23173850

                                      Вот только решает это не компилятор, а операционная система, извините.
                                      по поводу «не обязан» — это решает не компилятор и не ОС, а контракт метода realloc.
                                      Если оно не было moved, то кто и что могло повлиять на содержимое этой памяти? UB, в этой редакции стандарта, это лишь: the value of a pointer that refers to space deallocated by a call to the free or reallot function is referred to.
                                      вы акцентируетесь на том, что на конкретной архитектуре с конкретным рантаймом конкретное поведение для вас не очевидно. При этом разработчики стандарта вынуждены оперировать асбтрактной возможно сферической вычислительной машиной возможно в вакууме, и формализация идет соответственно. И опять же, из всего множества ситуаций попадающих под правило «обращение к объекту вне лайфтайма» вы приводите одно конкретное исключение которое по вашему мнению должно работать иначе. Возьмите да законтрибьютьте хак для realloc, если вам так печет, я даже не знаю что еще посоветовать.
                                      Кроме того clang ломает программы с UB даже с -O0, так что это не парацея: godbolt.org/z/1d7Goaca4
                                      вообще-то изменение const поля это UB, вне зависимости от константности указателя по которому идет обращение. Это даже может привести к крашу
                                      Нет уж. Извините. Либо крестик, либо трусы. Либо стандарт — это закон и для разработчиков компиляторов и для писателей программ, либо тогда уж, давайте тогда объясняйте с какого перепугу вы считаете, что проверка if (x + 1 < x) должна быть выкинута без аппеляции к стандарту.
                                      переполнение знаковых же UB…

                                      • 0xd34df00d
                                        /#23173906

                                        Кроме того clang ломает программы с UB
                                        вообще-то изменение const поля это UB

                                        Зачем вы повторяете то же самое утверждение с «вообще-то», как будто вы ему возражаете?

                                      • khim
                                        /#23174052

                                        по поводу «не обязан» — это решает не компилятор и не ОС, а контракт метода realloc.

                                        Решает всё-таки OS. Ну или, если точнее, стандартная библиотека. Больше некому.

                                        Контракт же описывает область возможных возвращаемых значений. И про тот факт, что realloc может оставить объект в покое и никуда его не двигать — во всех стандартнах сказано явно.

                                        Такое вполне себе приглашение к тому, чтобы этот случай специально обработать.

                                        вы акцентируетесь на том, что на конкретной архитектуре с конкретным рантаймом конкретное поведение для вас не очевидно.

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

                                        Ok, принято, остался только стандарт C, всё остальное неважно. Где в этом стандарте написано, что то, что я тут делаю - это UB?

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

                                        Можно и другие случаи рассмотреть, когда это правило к бреду приводит. Они, кстати, в этих самых proposal'ах как раз приводятся.

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

                                        Это в чистом, дистиллированном, виде “я хочу сделать программисту плохо — и делаю” . Потому что могу.

                                        Если бы проверка p == q приводила бы к тому, что p и q рассматривались бы как синонимы (что, собственно, происходит в случае, когда я clang принудительно “отрываю” знание того, как работает realloc) — кому от этого поплохело бы?

                                        Круче всех поступает MSVC, этот вообще, блин, лапочка. Он умудряется что-то найти и “наоптимизировать” вот в такой вот программе: https://godbolt.org/z/cYr3he4xz

                                        Это что за <цензурные слова кончились>: я теперь не могу указатель передать в функцию-похожую-на-realloc передать? Которая вообще ничего не делает, просто возвращает что передали? Он от этого “протухает”? Фантастика.

                                        На noinline не обращайте внимание, просто на godbolt нельзя создать проект из двух файлов. Если это сделать (и разнести функции по разным файлам), то MSVC всё равно “наоптимизируется” всласть.

                                        переполнение знаковых же UB…

                                        Это в том случае, если мы признаём стандарт документом, который нужно соблюдать. Если же мы сходили в туалет и вытерли стандартом попу, то это нифига не UB, это вам любой опрос покажет, если его правильно сформулировать. Например вот так:

                                        Как известно в корректной программе на языках C и C++ можно сравнить число x со значением 4294967295 вот таким вот странным способом:

                                        uint32_t x = read_value();

                                        bool res = x + 1 < x;

                                        Это происходит потому, что число 4294967295 слишком велико и не умещается в uint32_t.

                                        Внимание вопрос: можно ли в следующей программе заменитьres наtrue?

                                        int32_t x = read_value();

                                        bool res = x + 1 < x;

                                        Я вас уверяю: если вы так поставите вопрос, то хорошо если несколько процентов наберёте тех, кто правильно ответит, не заклядывая в стандарт.

                                        Это в чистом виде “идиотское наследие прошлого”, которое сохраняют потому что оно, в таком виде, было когда-то вписано в стандарт (никаких других причин нет).

                                        • Antervis
                                          /#23174516

                                          Решает всё-таки OS. Ну или, если точнее, стандартная библиотека. Больше некому.
                                          так это в рантайме. Во время компиляции, особенно при динамической линковке стдлибы, компилятор знает лишь контракт этой функции.
                                          Ok, принято, остался только стандарт C, всё остальное неважно. Где в этом стандарте написано, что то, что я тут делаю — это UB?
                                          Забавно. Вот так выглядит дока на cppreference (для си конечно же):
                                          Return value
                                          On success, returns the pointer to the beginning of newly allocated memory. To avoid a memory leak, the returned pointer must be deallocated with free() or realloc(). The original pointer ptr is invalidated and any access to it is undefined behavior (even if reallocation was in-place)

                                          • khim
                                            /#23175940

                                            Вот так выглядит дока на cppreference (для си конечно же):

                                            Причём тут cppreference? Я прошу цитату из стандарта, причём вполне конкретного, C89. Потому что не только поведение realloc не только определено в C89 по другому, но в C99 ещё и undefined behavior определён не так, как в C99 (и последующих): https://news.quelsolaar.com/2020/03/16/how-one-word-broke-c/

                                            Собственно он определён так, как его определял Керниган в своей книжке. Более того, до революцию совершённой C99 про undefined behavior писалось буквально следующее: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n897.pdf

                                            Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior.

                                            Вы видите тут что-нибудь про то, что в C программе никогда и ни при каких условяих есть UB, то компилятор может делать с ней любые преобразования? Или о том, что если в стандарте прописаны UB, то всё, теперь это поведение никто и никогда не может доопределить? Или что если разработчикам компиляторов не хватает UB, то они могут добавить новые?

                                            Шутки ради, если память кончилась, realloc возвращает NULL и доступ к буферу возможен только с помощью оригинального ptr.

                                            В этом случае проверка p == q не пройдёт. И да, можно поменять проверку на p == q && q != NULL, результат не меняется.

                                            а вот это точно бага, надо репортить.

                                            Беполезно репортить. Разработчикам C и C++ компиляторов на вас на-пле-вать. Смиритесь.

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

                                            Лучше. Гораздо лучше. Легче ловится и правится. Однако есть проблема: как я там и писал, если не полагаться на UB, то вообще ничего оптимизировать не получится. Нигде и ничего.

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

                                            Вы уж меня извините, но я сам — тоже разработчик компилятора. Конкретно сейчас JIT-компилятора, несколько лет назад — GCC правил (проект не взлетел, потому в upstream попали только какие-то странные правки).

                                            И я прекрасно понимаю почему компилятор делает эту ошибку. И понимаю как её править.

                                            Но так же понимаю, что если саботажникам наплевать и на стандарт и на пользователей, то это всё бессмысленно.

                                            Вот прямая цитата одного из разработчиков LLVM:

                                            Although provenance is not defined by either standard, it is a real and valid emergent property that every compiler vendor ever agrees on

                                            Это что за безобразие такое? Вы, когда программу пишите, если скажете “ой, чё-та сложна мне написать программу так, чтобы вода в котле никогда не закипала” и измените её так, что всё приходило в норму секунд за 20… что вам заказчик скажет, если вода в котле таки закипит и немножко >бум-пум сделает? Ну примерно как в одном известном городке в 1986м? Что вы молодец, эффективную программу написали, да?

                                            Да, блин, когда разработичики компиляторов пытались переложить с больной головы на здоровую и писали huge bodies of C code are land mines just waiting to explode ( туточки: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html ) они что — не понимали, что они делают?

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

                                            И ответ на вопрос “а с какого перепугу переполнение знаковых — это UB?” звучащий как “а потому что так написано в стандарте” был приемлем (не очень радостен, не очень разумен, но приемлем).

                                            Но если нам начинают рассказывать сказки, что этого правила в стандарте больше нет, но вы обязаны его соблюдать. И добавляют пафоса типа if you believe pointers are mere addresses, you are not writing C++; you are writing the C of K&R, which is a dead language, то увы, приходится признать, что то, что сотворили эти саботажники — unfit for any purpose и искать другой язык.

                                            Как-то долго не приходило в голову правильное название для этих деятелей. Вроде ж как не враги, оффициально они охоту на разработчиков не объявляли, но делают всё возможное для того, чтобы пользоваться C и C++ стало невозможно…саботажники, пожалуй, самое правильное слово.

                                            • technic93
                                              /#23176106

                                              Докину в эту ветку ссылку по теме
                                              https://www.ralfj.de/blog/2020/12/14/provenance.html

                                              • khim
                                                /#23176652

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

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

                                                Когда и если стандарт будет изменён — можете для этого, пока не существующего, стандарта, всё это включить. А пока стандарт таков каков он есть — обеспечьте пресловутый “as if” и не морочьте мне голову, пжлста: https://en.wikipedia.org/wiki/As-if_rule

                                                • technic93
                                                  /#23176870

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


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

                                            • Antervis
                                              /#23176292 / +1

                                              Вы видите тут что-нибудь про то, что в C программе никогда и ни при каких условяих есть UB, то компилятор может делать с ней любые преобразования? Или о том, что если в стандарте прописаны UB, то всё, теперь это поведение никто и никогда не может доопределить?
                                              А вы видите в стандарте требование компиляторов определять поведение каждого отдельно взятого UB? Я вот не вижу. И не понимаю почему вы думаете что компилятор обязан за вас доработать вашу программу, причем еще и в каждом отдельно взятом месте догадаться даже не до желаемого вами результата, а до того, который вам «легче ловится и правится».
                                              Беполезно репортить. Разработчикам C и C++ компиляторов на вас на-пле-вать. Смиритесь.
                                              одно дело ныть что в компиляторы не тащат ваши хотелки (которые замедлят весь плюсовый код непропорционально числу спрятанных под ковер UB), другое — ныть что очевидные баги в компиляторах не фиксятся. Это попросту ложь

                                              • khim
                                                /#23177204 / +1

                                                А вы видите в стандарте требование компиляторов определять поведение каждого отдельно взятого UB?

                                                В стандарте — не вижу. В документах комитета по стандартизациии — вижу рекомендацию.

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

                                                Я не прошу “дорабатывать” мою программу. Стандарт чётко и однозначно описывает что в ней происходит. Я всего лишь прошу не ломать корректную программу.

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

                                                Вау! Как грозно и круто. Ну-ка ну-ка пример бенчмарка, который с опцией -fno-builtin-realloc замедляется хоть как-нибудь?

                                                Речь тут не идёт про то, что компиляторы полагаются на UB. речь идёт строго о том, что они полагаются на UB, которое не прописано в стандарте.

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

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

                                                Баги тоже не особо фиксятся, но это бы ещё полбеды было.

                                                Это попросту ложь.

                                                Увы, но это правда. Ещё раз, читайте моё утвержление внимательно:

                                                Разработчики clang, icc и msvc сознательно и целенаправленно добавили в свой компилятор код единственным предназначением которого является неправильная компиляция некоторых корректных C89 программ. И отказваются его исправлять.

                                                Ещё раз: вот именно так. В случе с clang я даже могу показать где конкретно этот код сидит и когда он был добавлен.

                                                И да, произошло это много лет назад под лозунгом “а давайте мы сломаем код сейчас, а оптимизации придумаем… потом… может быть” .

                                                В результате программы сломаны, а оптимизаций нету. До сих пор.

                                                • Antervis
                                                  /#23178380 / +1

                                                  Разработчики clang, icc и msvc сознательно и целенаправленно добавили в свой компилятор код единственным предназначением которого является неправильная компиляция некоторых корректных C89 программ. И отказваются его исправлять.
                                                  аргумент про то что с89 не актуален не принимается? Точнее про то, что вам в режиме с89 совершенно не нужен самый современный компилятор, в котором этот с89 так фатально сломан.

                                                  Кстати, а вы можете привести какие-то другие примеры, где компиляторы ломают код без UB?

                                                  • khim
                                                    /#23178458

                                                    аргумент про то что с89 не актуален не принимается?

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

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

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

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

                                                    Кстати, а вы можете привести какие-то другие примеры, где компиляторы ломают код без UB?

                                                    Я — нет. Но саботажники, слава богу, сделали это за меня. Откройте proposal и найдёте там примеры того, как разные компиляторы ломают валидные программы. Вместе с предложением задним числом объявить эти программы невалидными. С помощью добавления изменений в соотвествующий стандарт:

                                                    http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

                              • 0xd34df00d
                                /#23173468

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

                                А старый указатель при этом всё ещё будет указывать в старый участок? Это как? Ваш стандартный x86 тоже provenance трекает, что ли?

        • creker
          /#23168432 / -1

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

          • khim
            /#23168482

            А как вы представляете себе язык в отрыве от компиляторов и стандарта?

            Да, ответ на вопрос “кто виноват?” у вас правильный.

            Теперь надо ответить на другой, практически более важный вопрос: а делать-то что?

            Я для себя пока подумываю о переходе на Rust:: всех этих ужасов в безопасном Rust нету, чтобы выстрелить себе в ногу нужно устраивать конструкцию весьма непростую (всегда включающую в себя небезопасный код, хотя, увы, достаточно стандартной библиотеки), а небезопасный Rust вроде занимает 2-3% кода в большинстве проектов, там можно и консилиум устроить и 10 review для этих кусочков кода провести.

            • creker
              /#23168522

              Я здесь имею ввиду эту смену «идеологии». Т.е. у нас был портативный ассемблер с предсказуемым выхлопом компилятора. Но в какой-то момент все поменялось и вот у нас тонны UB, из-за которых шагу влево нельзя ступить. Делать надо было очень просто — оставить язык таким, каким он задумывался, и развивать стандарт в этом направлении. Да, язык был бы не таким быстрым, но только вот люди и так отключают все эти оптимизации, когда им нужен «портативный ассемблер». И было бы четкое разделение — вот не самый быстрый «портативный ассемблер» под названием Си для ядра, драйверов и прочего. А вот все остальные языки для всего остального, где куча оптимизаций и удобств.

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

              • khim
                /#23168608

                Дык уже разговор в предметную область перешёл. Не “стоит ли попробовать”, а как нам это сделать: https://lwn.net/Articles/853423/

                И BPF уже тоже на Rust пишут: https://lwn.net/Articles/859784/

                И Rust , кстати, не “такой же быстрый”. Он медленнее в теории, но быстрее на практике.

                В теории медленнее потому, что некоторые фишки, которые в C++ возможны, Rust не умеет (например move в Rust это всегда пересылка участка памяти, а в C++ можно умнее сделать).

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

                • creker
                  /#23168654

                  Насколько помню, особенности раст еще и позволяют делать оптимизации, которые невозможны в С/С++. Вроде алиазинга.

                  • Gorthauer87
                    /#23168846

                    Там эта драма со стюардессой и llvm noalias уже годами длится.

        • firk
          /#23169224

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

          Ну да, с -O2 он не ассемблер, поэтому не надо пихать его везде, где не требуется жёсткая оптимизация. Ну или хотя бы делать -O2 -fno-strict-aliasing -fno-strict-overflow (большинство проблем -O2 именно в этих двух флагах). Ну а те куски кода, где забота о производительности по-максимуму отдана компилятору - тщательно проверять на соответствие формальному стандарту.

          Кстати, экспериментируя с уровнями оптимизации (gcc) для своего кода, заметил: между -O0 и -O1 разница большая почти всегда (до полутора а может где-то и двух раз), а вот -O1, -O2 и -O3 всё примерно одно и то же. Иногда -O3 чуть лучше остальных (на какие-нить 10%), иногда даже почему-то чуть медленнее. Это всё для меня ещё один аргумент не использовать -O2 и выше без острой нужды, а если и использовать - то, во-первых, удостоверившись, что он действительно именно тут даст прирост, а во-вторых тщательно проверяя код на формальные придирки.

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

          • khim
            /#23169332

            Вы уж меня извините, но ни-O2, ни даже-fno-strict-aliasing на язык влиять не должны. По крайней мере так утверждают разработчики компиляторов.

            Что касается realloc и проверки на равенство: да, разумеется проверять нужно когда что-то оптимизируешь. Если, например, у тебя структура имеет внутри себя указатели и она была “передвинута”, то все эти указатели нужно пересчитать (что, кстати, представит собой отдельную попаболь, так как просто взять p-q и потом пересчитать указатели невозможно (разность между двумя указателями, не указывающими на один объект - это UB).

            Что касается “тщательных проверок на соотвествие формальному стандарту” - фиг. Не поможет. Этот код сrealloc полностью соотвествует C89. А C99 и более новые нужно читать ну просто на удивление креативно, чтобы придумать как это поведения может укладываться в рамки стандарта.

            А если прочитать proposal , то можно понять что нет тут никаких нарушений стандарта, просто компиляторщикам так удобнее, посему они сейчас добавят в стандарт ещё горстку UB (пока ещё, правда, неясно каких) и всё “исправят” (ну, в смысле, стандарт задним числом легализует то непотребство, которые компиляторы творят уже сегодня): http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

            Вариант “используйте-O1” на долгосроке работать, увы, не будет: сейчас просто все бенчмарки гоняются с-O2/-O3, потому все усилия по “оптизациям” сконцентрированы на них, если люди перестанут ими пользоваться и перейдут на -O1 - их и в -O1 включат.

            Дурное дело-то нехитрое.

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

            • firk
              /#23173378

              Вы уж меня извините, но ни -O2, ни даже-fno-strict-aliasing на язык влиять не должны. По крайней мере так утверждают разработчики компиляторов.

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

              Да, Си с -O2 и Си с -O0/-O1 можно считать существенно разными режимами компиляции, что бы там кто ни говорил. Предсказуемая построчная конвертация (причём - в обе стороны) C <-> ASM - это именно про -O0/-O1 (хотя кажется этот самый известный режим нигде не стандартизирован, но по факту он есть во всех компиляторах). -O2/-O3 это неидеальная эмуляция логики обычного Си, работающая в некоторых стандартизированных рамках, за пределами которых начинается UB.

              Если, например, у тебя структура имеет внутри себя указатели и она была “передвинута”,

              Ну примерно такое и предполагал. Если до этого дошло, то и правда придётся проверять. Но если посмотреть глобальнее, то это ведь вопрос общей идеологии работы кода. А точно ли структуру нужно организовывать с указателями внутри? Ведь есть альтернатива, и не одна, и заранее утверждать что тупо указатели будут быстрейшим вариантом - нельзя. А точно ли именно тут нужен realloc (если вообще нужен)? А может, лучше оптимизировать код так, чтобы realloc вызывался реже, и когда он уж вызвался, переписывание указателей было незначительной погрешностью к затраченному времени? Да, скорее всего при этом потратится чуточку больше памяти (а может и нет), но зато скорость вырастет (при любой логике компилятора), потому что realloc сам по себе накладен.

              Не поможет. Этот код с realloc полностью соотвествует C89.

              Ну, тут тогда уступаю и соглашаюсь.

              Вариант “используйте -O1” на долгосроке работать, увы, не будет: сейчас просто все бенчмарки гоняются с -O2/-O3

              Ну, как я писал - с моим кодом почему-то -O1 и -O2 почти всегда одинаковые, а -O3 иногда чуть отличается, причём заранее неизвестно в какую сторону. Может быть, потому что я не пытаюсь грузить всеми оптимизациями компилятор, а пишу так, чтобы лишних действий и в исходнике было по-минимуму? Всякие отладочные ветки, которые на самом деле не должны выполняться, можно отключать препроцессором (например assert так и работает) а не оптимизатором. Не-отладочные ветки, которые на самом деле никогда не выполняются - это проблема и запутанного исходника, а не только оптимизаций. Бенчмарки числодробилок пусть и будут на максимальном (из полезных) уровне оптимизации, но realloc'ам "на авось" там не место. А в прикладной логике супер-оптимизации не нужны.

              • khim
                /#23173570

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

                Погодите — одна и та же программа выдаёт разный текст при запуске и вы считаете это нормальным, то что это?

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

                Да, Си с -O2 и Си с -O0/-O1 можно считать существенно разными режимами компиляции, что бы там кто ни говорил.

                Какая-то крайне новаторская идея. Режимы компиляции разные, это да, но причём тут язык?

                А точно ли структуру нужно организовывать с указателями внутри?

                А давайте компилятор не будет решать за меня, как мне писать программы? Если программа соотвествует стандарту, то она должна работать корректно, если не соотвествует — объясните какое именно правило она нарушает.

                И не нужно умножать сущности без необходиимости. В стандарте C11 и без того больше 200 UB перечислено, должно бы быть достаточно, вроде как.

                Бенчмарки числодробилок пусть и будут на максимальном (из полезных) уровне оптимизации, но realloc'ам "на авось" там не место.

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

                Почему-то когда компилятор делает явную чушь, которую ему позволяет делать стандарт (типа выкидывания проверки на NULL, что приводит, в результате, к CVE), так разработчики компиляторов очень-очень любят прикрываться стандартом.

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

                Нет уж. Извините. Либо крестик, либо трусы. Либо стандарт — это закон и для разработчиков компиляторов и для писателей программ, либо тогда уж, давайте тогда объясняйте с какого перепугу вы считаете, что проверка if (x + 1 < x) должна быть выкинута без аппеляции к стандарту.

                • firk
                  /#23177528

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

                  Хорошо, значит -O2 и -fno-strict-* влияют на язык. Но только в плане сужения области его определения. Некоторые программы корректные для -O0 но некорректные для -O2, если же они корректны для обоих случаев то дадут одинаковый результат. А должны, не должны - ну, влияют, таков факт.

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

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

                  Если программа соотвествует стандарту, то она должна работать корректно, если не соотвествует — объясните какое именно правило она нарушает.

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

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

                  Что написано? Что realloc может быть медленным? Это не пункт стандарта, это реальность. Если realloc действительно сдвинет указатель, ему скорее всего понадобится копировать все байты со старого места на новое, а это трата времени. Если заботитесь о скорости работы софта, желательно сокращать количество таких действий - например делать realloc инкрементами по 30%-100% от прошлого объёма, а не по одному элементу увеличивающегося массива.

                  так разработчики компиляторов очень-очень любят прикрываться стандартом

                  так, внезапно, оказывается, что соблюдать его необязательно.

                  Нет уж. Извините. Либо крестик, либо трусы.

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

                  давайте тогда объясняйте с какого перепугу вы считаете, что проверка if (x + 1 < x) должна быть выкинута без аппеляции к стандарту.

                  Ну тут могу предположить, что ответ будет такой: мы вам дали опцию чтобы выкидывать такие проверки, а включать или не включать её - на ваш выбор. Опция по дефолту включена в -O2, потому что нам показалось это уместным, но мы ни в коем случае вас не заставляем, и можно сделать и -O2 без неё, если дописать -fno-strict-overflow.

                  • khim
                    /#23177660 / +1

                    Но тут никаких выходов, кроме как смириться, не вижу.

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

                    Какие будут приняты решения? Ну прежде всего нужно провести переговоры.

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

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

                    Как бы нормальный, рабочий, процесс.

                    Ну а чем разработчики компилятора такие особенные?

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

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

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

                    Бесполезно. Как мне было объяснено среди саботажников достигнут консенсус: они не собираются соблюдать стандарт и не собираются поддерживать обратную совместимость (кроме поддержки бенчмарков, это святое).

                    В этой ситуации ничего не остаётся, кроме как переходить на другой язык.

                    Как я уже тут много раз писал Rust кажется подходящим кандидатом.

                    Да, он тоже основывается на том же LLVM, однако его разработчики стремятся к тому, чтобы список UB был документирован (в будущем, пока у них списка нет, увы), и, главное, в safe Rust никаких UB быть не должно (за этим следит компилятор и если какое-то UB прорывается из недр LLVM в safe Rust, то компилятор чинят).

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

                    Вариант не самый лучший, но… какой уж есть.

        • deagl_Dim
          /#23176058 / -3

          В документации всё написано. По факту у вас код с UB и вы жалуетесь на то, что он выдаёт какие-то странные результаты. Во всех доках написано что a = realloc(a, size); надо делать и будет счастье, но вы всё равно делаете по своему и чем-то не довольны. Я вижу что по факту размер не меняется, но это не отменяет того факта что вы не следуете документации. А потом внезапно проблемы возникают.

          • unC0Rr
            /#23176094 / +4

            Вот как раз так не надо делать. В случае невозможности выделить часть памяти realloc вернёт NULL, и вы потеряете доступ к памяти по a хотя бы для того, чтобы освободить её.

            • deagl_Dim
              /#23178430

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

          • khim
            /#23176598

            В документации всё написано.

            Ответ не принят. Причём не просто не принят, но категорически не принят.

            Последние лет 20 саботажники (также известные как разработчики компиляторов) всех задолбали отмазкой “этого нет в стандарте”. Документация на x86 говорит иное? “Нам пофиг, этого нет в стандарте”. Документация на POSIX написана иначе? “Нам пофиг, этого нет в стандарте”.

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

            Будет новая редакция, будет новый ключик компиляции — тогда Ok. А сейчас-то вы с какого перепугу корректную прогр- амму ломаете?

            По факту у вас код с UB и вы жалуетесь на то, что он выдаёт какие-то странные результаты.

            По факту у меня нет UB. По факту мне привели какие-то драфты и идиотские отмазки в духе “после вызова realloc указатель, ранее указывавший на объект стал указывать на one-past-the-end массива (который мы, очевидно, высосали из пальца, чтобы валидную программу объявить невалидной)”.

            Я вижу что по факту размер не меняется, но это не отменяет того факта что вы не следуете документации.

            Я как раз следую единственной документации, которую мне сказали учитывать: тексту стандарта. Там явно написано, что возвращаемый указатель модет быть равен переданному. Также там явно написано, что если указатели равны - то они ведут себя одинаково. Единственный случай, где это, возможно, не так - если указатель за конец одного массива это указатель на другой, с ним не связанный, объект.

            И то я не уверен, что тут законно что-то оптимизировать, но, извините, у меня в программе нет никакого массива!

            Даже сам proposal начинается с признания, что DR260 CR has never been incorporated in the standard text: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

            А извините, если он никуда не был incorporated, то, соотвественно, он и не действует.

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

            Ну раз уж вы возвели стандарт на пьедестал и ничего больше не признаёте.

    • 0xd34df00d
      /#23166940

      Как погромист, уставший от C++, и, в частности, от темплейтов в проде:


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

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


      По этой же причине отсутствие аналогов — это даже хорошо. А вот альтернативы… Кто-то лисп вспомнит, кто-то заговорит про макросы в расте, а я вспомню, например, template haskell, где можно работать с AST обычным хаскель-кодом, и на этапе компиляции хоть скачать файл и что-то с ним сделать (так на одном моем месте работы по xsd-схеме создавались всякие определения данных), хоть использовать обычные библиотеки, которые в норме работают в рантайме (успехов использовать, не знаю, boost graph library в компилтайме в плюсах).


      Если начинать рассуждать о времени компиляции как о значимом факторе, то и C, и C++ нужно отправлять на помойку на пенсию

      Все ж C существенно быстрее компилируется. Особенно если в плюсах пользоваться темплейтами всерьез.


      А в остальном согласен.

      • vkni
        /#23167198

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


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

        Кмк, в качестве 0-го приближения к шаблонам C++ отлично идёт Miranda. То есть, ленивый, чистый функциональный язык, базирующийся на pattern matching (реально С++ шаблоны не такие).

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

        Вопрос, насколько это интересно?

        • 0xd34df00d
          /#23167276

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


          И, к сожалению, шаблоны вот нифига не ленивые — я бы сказал, что это язык с классической call-by-value операционной семантикой. Например, попробуйте скомпилять:


          template<int N, int Div>
          struct CheckDiv
          {
              static constexpr bool result = Div >= N || ((N % Div) && CheckDiv<N, Div + 1>::result);
          };
          
          template<int N>
          struct IsPrime : CheckDiv<N, 2> {};
          
          int main()
          {
              static_assert (IsPrime<3>::result);
          }

          Что до отхода от SFINAE — на constexpr/consteval делается далеко не всё, а концепты в их текущей форме — это по большому счёту сахар для SFINAE. По крайней мере, я не смог опровергнуть утверждение об их изоморфизме.


          Хотя писать


          template<typename T>
          void foo(T& t)
          {
              if constexpr (requires requires { t.push_back(int{}); })
                  ...
              else if constexpr (requires requires { t.insert(int{}); })
                  ...
          }

          куда приятнее, чем стандартную хренотень с detector idiom или ещё чем, конечно.


          А большие кодовые базы — ну это просто большие времена компиляции (5-10 минут на один cpp-файл — спокойно), потребление памяти (по 7-10 гигов на один файл — тоже спокойно, прощай многопоточная компиляция на всех 64 ядрах на 64-гиговой машине) и довольно тяжело читаемые ошибки.

          • vkni
            /#23167374

            (хотя это немножко ложь, у плюсов в компилтайме есть глобальное мутабельное состояние)


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

            Ленивость там на другом уровне — если вы сделаете несколько шаблонов, которые нигде вообще не подключаются, то они и не будут проверяться на ошибки. Здесь просто условие не в метаязыке, а в самом С++.

            В «доказательство» я могу привести два языка, с которых генерируются шаблоны — это «MetaFun» и «eClean». Оба они ленивые.

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

            • DungeonLords
              /#23167554

              Приглашаю любителей шаблонов обсудить пример. Дано два вектора со всегда одинаковым кол-вом элементов:


                      std::vector<float> x{ 1, 2, 4, 8 };
                      std::vector<float> y{ 1, 2, 4, 8 };

              Нужно сохранить их в csv файл. Лучшим решением оказалось


              for(quint32 i=0;i<x.size();++i){
                *csvStream << x[i] << ";" << y[i] << "\n";
              }

              Однако если мы посмотрим как делают обход вектора в различных источниках, то встретим конструкции через итераторы (begin(), end()).
              Вопрос. Противоречит ли моё решение нормам современного C++?

              • 0xd34df00d
                /#23167616

                Можно обмазаться range'ами и сделать что-то вроде


                for (const auto& [x, y] : zip(xs, ys))
                    out << x << ";" << y << "\n";

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

              • Sklott
                /#23168272 / +1

                Противоречит ли моё решение нормам современного C++?

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

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

          • Antervis
            /#23167500

            Что до отхода от SFINAE — на constexpr/consteval делается далеко не всё, а концепты в их текущей форме — это по большому счёту сахар для SFINAE. По крайней мере, я не смог опровергнуть утверждение об их изоморфизме.
            В отличие от sfinae, концепты могут быть более/менее ограниченными (constrained) относительно других концептов, что позволяет делать по ним перегрузки. Вот пример — здесь vector::iterator полностью удовлетворяет концепту forward_iterator, но выбирается перегрузка с random_access_iterator потому что в ней больше удовлетворенных ограничений.

            А еще есть случаи, когда можно легко запретить конструкторы/деструкторы/операторы присвоения класса через requires clause, но приходится прибегать к наследованию с sfinae. В совсем граничном случае может быть кейс, не реализуемый через sfinae вообще (или за любое разумное время)

            • 0xd34df00d
              /#23167626

              Так это и есть определение сахара.

      • dolfinus
        /#23167686

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

        Тут главное знать меру, иначе отладка заметно усложняется. Анекдотичная ситуация, когда в модуле из 30 строк, а ошибка возникает в 120, тут маловероятна, потому что сами макросы должны быть валидным AST. Но вот разобраться в том, какой из 100500 вложенных макросов содержит ошибку в логике, бывает нетривиально.

    • kmeaw
      /#23167566

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


      github.com/ziglang/zig/blob/master/lib/std/array_list.zig

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

      • 0xd34df00d
        /#23167628

        А можно написать функцию, которая принимает значение, а возвращает тип, зависящий от этого значения?

        • kmeaw
          /#23167650

          Можно, если эта функция может быть реализована в comptime, в частности не содержит «global run-time side effects».

          Пример: github.com/ziglang/zig/blob/master/lib/std/crypto/sha3.zig#L19

          fn Keccak(comptime bits: usize, comptime delim: u8) type — возвращает тип-структуру, описывающую состояние SHA3.

          • 0xd34df00d
            /#23167664

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

            • kmeaw
              /#23167672

              Может: github.com/ziglang/zig/blob/master/lib/std/heap.zig#L802
              Функция StackFallbackAllocator принимает comptime-аргумент size и возвращает тип-структуру, у которой поле buffer это массив байт длиной size.

              • 0xd34df00d
                /#23167708

                Прикольно. Жаль, что это всё только в компилтайме, а то были бы полноценные завтипы.

    • anger32
      /#23167644

      Далее, что там у нас с многопоточностью?

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

      Для прикладного кода у UNIX есть POSIX и опять же публичные системные вызовы ядра. У win — winapi и CreateThread(). Никаких проблем у них с C, с очевидностью, нет. Опять же к вопросу, что в современные языки затаскивают абстракцию над абстракцией подчас без новой функциональности.

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

      В python/cpython, к слову, потоки вообще попередохли из-за неустранимых ошибок проектирования.

      • 0xd34df00d
        /#23167666

        Потоки никакого отношения к языкам программирования не имеют, так как являются концепцией ОС и конкретного syscall ее системной библиотеки.

        Слова «модель памяти» вам о чём-нибудь говорят? Ну это так, для начала и чисто формально.


        Если таки пойти чуть дальше — даже если у вас в языке есть поддержка многопоточности, но стандартная библиотека языка наполовину написана так, будто её нет (упомянутый вашим оппонентом strtok), то это вызывает грусть, печаль и разочарование.


        Поэтому да, и правда,


        Большей высокомерной глупости давненько не встречал.

        тут звучит довольно забавно.

        • anger32
          /#23167692 / -1

          Ваша софистика понятна, но ничего не меняет. Язык программирования не может самостоятельно создавать поток или управлять им. Если этот тезис сможете как-то опровергнуть — будет основание говорить о появлении независимости от syscall.

          Интерфейсы, абстракции, прикладная алгоритмика — да. Но не ниже. Те же 3D стеки, тот же Linux/DRM для организации собственной модели памяти никаких ништяков со стороны языка не требуют.

          • 0xd34df00d
            /#23167710 / +2

            Язык программирования не может самостоятельно создавать поток или управлять им. Если этот тезис сможете как-то опровергнуть — будет основание говорить о появлении независимости от syscall.

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


            pthread_mutex_lock(&mutex);
            GlobalVar = 42;

            • anger32
              /#23167732 / -1

              Мне не нужно опровергать этот тезис

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

              • 0xd34df00d
                /#23167772

                Системные проекты используют это уже много десятилетий.

                Реализация, напрочь игнорирующая любую системную функцию по работе с барьерами, будет абсолютно корректной реализацией C99 (и C++03).


                Ожидаемо.

                Что ожидаемо?

                • anger32
                  /#23168188

                  Реализация, напрочь игнорирующая любую системную функцию по работе с барьерами, будет абсолютно корректной реализацией C99 (и C++03).

                  Гляньте исходники того же Linux ради приличия. Никаких функций тут не используется, чтобы их игнорировать: искать __smp_mb().

                  Что ожидаемо?

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

                  • mayorovp
                    /#23168344

                    Правильно, там используется конструкция asm, которая implementation-defined, то есть вообще не является частью языка Си.

                    • anger32
                      /#23168672

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

                      • DabjeilQutwyngo
                        /#23169312

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

                      • mayorovp
                        /#23169556

                        Но реально-то оно именно что в язык и тянется. Только не в Си, а в "GCC C".

                  • 0xd34df00d
                    /#23169538

                    Никаких функций тут не используется, чтобы их игнорировать: искать __smp_mb().

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


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

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

                    • anger32
                      /#23170628

                      Компилятор, который вырежет эту инструкцию

                      … перестанет быть одним из самых востребованных.

                      • 0xd34df00d
                        /#23170704

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

                        • anger32
                          /#23174392 / -1

                          Это неважно

                          Исключительно это и важно, поскольку компенсировать это невозможно.

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

                          А это компенсируется легко в исторически зарекомендовавших себя языках => процитированное есть исключительно ситуативная опциональная вкусовщина.

      • kmeaw
        /#23167668

        Языку программирования желательно знать про существование потоков.
        Разработчики стандарта C это понимают, и в C11 появилось ключевое слово _Thread_local, а в gcc до этого использовалось нестандартное __thread для thread-local storage.

        • anger32
          /#23167696

          Не спорю, это все полезно и вносит вариативность. Но новизны тут нет. Пример: pthread_key_create() -> IEEE Std 1003.1-2001

    • SinsI
      /#23167844 / +1

      Если начинать рассуждать о времени компиляции как о значимом факторе
      … то нужно взять разработчиков компиляторов и авторов Стандарта, запереть в комнате и не выпускать пока не доделают модули до нормального состояния.

      • DabjeilQutwyngo
        /#23169364

        Не заметил, чтобы кто-то упомянул о частном решении задачи ускорения компиляции в MS VC++ с помощью предкомпилированных заголовочных файлов (pch). Нормально так ускоряет, на порядки. Применяю на проекте, где практически всё сделано на шаблонах, и шаблоны работают вместе с дженериками C++/CLI.

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

        • SinsI
          /#23170876

          Это не ускорение именно компиляции в чистом виде, а только уменьшение времени повторной компиляции.
          Ключевая разница — в том, что достаточно добавить или изменить дополнительный #define для проекта — и эти заголовочные файлы придётся создавать заново.

    • FD4A
      /#23168238

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

      Со строками беда тут не поспоришь. Для Gui конечно тоже не использую.

    • DabjeilQutwyngo
      /#23169230 / +1

      если вы пишите не критичные к производительности приложения

      в C++ недостаточно быстро избавляются от легаси

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

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

      консольные программы — это не приложения нормального человека, сейчас не 70-е года


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

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

      Ещё один пример из огромного маразма веб-технологий, прикрываемого вшивой заботой о безопасности — это интерпретация безопасности как валюты. Так в нынешнем штандарте в главе 7 про «The same origin policy» и написано. У меня вопрос, если веб-сайты управляют доступом к файлам, то почему разработчики браузера и этого гнилого HTML-штандарта не позоволяют пользователю делать ровно тоже самое, чтобы определять, к каким файлам и директориям у браузера будет доступ? А ведь это систмено важно: тогда с локальной машины можно запускать веб-приложения, написанные на XML/XSLT/CSS/SVG с использование протокола data, например, вообще без использования тормозного JavaScript и прочих плясок с бубном вокруг забот об иллюзии безопасности.

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

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

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

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

      Для сравнения производительности можно сравнить любой WYSIWIG-редактор с текстовым процессором типа MS Word. Второй прекрасно работает на одном процессоре, в одном потоке. А первый не может нормально даже на четырёх: постоянно возникают задержки курсора, заминки, запинки, и вообще нет ощущения управления курсором в реальном режиме времени. Потому что это среда выполнения, которая не может работать в таком режиме. А особенно ужасны веб-видеоплееры: в них видео рано или поздно начинает загружаться с замиранием потока на секунды. Потому что нарушены фундаментальные системные принципы, и никакое аппаратное ускорение это не исправит.

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

    • fx32
      /#23171412 / -1

      каждый день в программах на C находят очередную глупейшую дырень вида «послав специальную строку можно выполнить произвольный код». Не надоело?

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

      Язык С прекрасный язык для своих задач и своей области применения.

    • Andrey_1984
      /#23171416 / +1

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

    • japplegame
      /#23175644

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

      Вы просто не видели мета-языка шаблонов языка D. Помощнее плюсовых и с человеческим лицом.

  3. Alexey2005
    /#23166800 / -4

    Оба этих языка медленно умирают, и приговор им выписала фрагментация платформ. Когда весь мир состоял из одной только x86 (и винды в качестве ОС), всё было нормально. Но как только новые платформы начали появляться целыми пачками, и программисты захотели, чтобы единожды написанный код собирался везде и под всё, C/C++ дружно сдохли, оставшись лишь там, где заменить их совершенно нечем.
    Потому что они, вопреки распространённому мнению, не являются кроссплатформенными. Да, они позволяют писать кроссплатформенный код, но вот количество усилий, которые приходится для этого прикладывать, приводит в уныние.
    Отдельного плевка заслуживают их системы сборки. Makefile — это такая write-only жесть, что Perl плачет кровавыми слезами. Если у вас есть 200Кб-makefile, и где-то что-то отказывается собираться, и надо найти в тоннах этой лапши ошибку, то вам становится очень-очень грустно.
    Сборка тоже не кроссплатформенная. Попробуйте, скажем, собрать OpenCV под винду, сидя при этом в Linux. Это квест из разряда «миссия невыполнима». Фактически, без docker-контейнера C/C++ могут собирать под другую платформу разве только проекты уровня Hello World. В современном фрагментированном мире от таких языков будут стараться держаться как можно дальше.

    • qark
      /#23166838 / +3

      Makefile связан с C/C++ примерно так же как и CVS.

    • pavlushk0
      /#23166856 / +2

      Не существовало промежутка времени, когда "был только х86". Одной из целей создания Си как раз и была переносимость, собсно.

    • geher
      /#23166874 / +2

      Когда весь мир состоял из одной только x86 (и винды в качестве ОС), всё было нормально.

      Мир никогда не состоял из одной только х86 и винды в качестве ОС.

      Внезапно, когда С появился, уже существовало просто огромное количество различных платформ. Назову только самые известные: PDP-11, IBM/360. И необходимость переносимости кода уже тогда была (одна из причин, по которой ЯВУ вообще появились).

      После архитектуры тоже появлялись, как грибы: VAX, SPARC, ARM (тоже, кстати, достаточно древняя платформа).

      И осей было как собак нерезаных.

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

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

      Писать на С или С++ кроссплатформенно ничто особо не мешает. И собираются оно за милую душу. Главное, использовать платформонезависимые целочисленные типы (их сейчас есть уже) и не баловаться сверх меры низкоуровневыми возможностями (вроде доступа к памяти по указателю, полученному из целочисленного числа и приводимого в зависимости от потребностей к указателям различных типов). А использование готовых библиотек (того же QT) позволит избавиться от зависимостей от различных интерфейсов различных ОС.

      Более того, под некоторые компьютеры компиляция на них самих невозможна. Только кросскомпиляция и заливка готового бинарника.

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

      • KanuTaH
        /#23166894

        Более того, C (1972 год) появился задолго до x86 (1978 год), а уж тем более винды, даже самой первой и никому не нужной (Windows 1.0 - 1985 год), и появился именно потому, что назрела потребность в создании ЯП для написания переносимого, но эффективного кода (с каковой задачей, к слову, он вполне справляется и поныне).

        • victor_1212
          /#23167336

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

    • daniilk
      /#23167430 / -1

      Отдельного плевка заслуживают их системы сборки. Makefile

      Сразу можно сказать что человек не в теме, от слова совсем.

      В современном фрагментированном мире от таких языков будут стараться держаться как можно дальше.

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

      В современном фрагментированном мире от таких языков будут стараться держаться как можно дальше.

      Ага ну ну, фейсбук изначально был сделан на php, но когда оно стало невыносимо медленно замутили компилятор php to c++. Смотри TensorFlow, PyTorch.

      Фактически, без docker-контейнера C/C++ могут собирать под другую платформу разве только проекты уровня Hello World.

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

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

    • cher-nov
      /#23167828 / +1

      Когда весь мир состоял из одной только x86 (и винды в качестве ОС)

      как интересно, это когда такое было?

    • Sklott
      /#23168290 / -1

      Попробуйте, скажем, собрать OpenCV под винду, сидя при этом в Linux. Это квест из разряда «миссия невыполнима». Фактически, без docker-контейнера C/C++ могут собирать под другую платформу разве только проекты уровня Hello World.


      Я не смотрел именно OpenCV, может там не захотели заморачиваться, но вот например Skia вполне себе официально компилируется для Windows под Linux с помощью clang, а если приспичит и немного подшаманить, то и через MinGW. К тому-же очень часто вижу, что многие кросс-платформенные проекты чаще всего делают компиляцию в основном на Linux и очень часто на нем-же делают кросс-компиляцию для Windows. Сам в работе так делаю.

      Вот под Windows скомпилировать для Linux — уже проблема, но тут скорее от отсутствия реальной необходимости таких извращений.

    • Tujh
      /#23175078

      Вот только что, ради эксперимента сделал,

      mingw64-cmake . -Bout
      cd out
      make
      ...
      <собралось>

      что я делаю не так?

  4. creker
    /#23166854 / +1

    Ирония в том, что человек пишет о предсказуемости компилятора, хотя именно в этом проблема современного С. Он все дальше уходит от своего изначального предназначения «переносимый ассемблер» и все больше превращается в язык высокого уровня в его худшем проявлении. Нормальные языки высокого уровня не страдают фигней с UB, а С все больше полагается на UB, чтобы компилятор мог творить черти что. Вместе того, чтобы разделить то, что реально UB, а что зависит от платформы, мы имеем бред, когда переполнение знаковых целых до сих пор UB. Уж не говоря об алиазинге, о котором Линус говорит. Вот уж это точно идиотия, которой не место в языке низкого уровня, которым должен быть С.

    • kmeaw
      /#23167080

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

      • creker
        /#23167208

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

        • kmeaw
          /#23167210 / +1

          А можете привести пример случая UB, от которого больше вреда, чем пользы?

          • creker
            /#23167250 / +3

            Запросто. Упомянутое переполнение знаковых. Нахрена это было делать UB? Код всегда будет содержать подобные ошибки, но в том же расте по крайней мере получишь панику или wrap-around. В С/С++ компилятор из ошибки программиста делает катастрофу, потому что код не только содержит невидимую ошибку, но еще имеет непредсказуемое зависимое от платформы и компилятора поведение. Я думаю немало уязвимостей было вызвано подобным.

            Алиазинг же просто тупость. У программиста, по сути, отбирают полезный и понятный инструмент — возможность по-разному трактовать кусок памяти. И если в C хотя бы union официально можно для этого использовать, то в С++ даже этого нельзя. Надо писать уродливые memcpy и молиться, что оптимизатор это все уберет. Ах да, еще заодно помнить, что mempcy имеет свое UB, если буферы перекрываются. Замечательно.

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

            • Gryphon88
              /#23167258

              Нахрена это было делать UB?
              Это из-за различного представления отрицательных чисел до воцарения х86, зачем это тянут в стандарте после 2000 года — вообще не в курсах.

              • creker
                /#23167268 / +1

                Да ок, сделайте это implementation defined и дело сделано.

                Язык не может определиться, то ли он низкого, то ли высокого уровня. То ли вы даете возможность работать на любом железе и кодить именно под него (implementation defined), то ли вы жестко задаете поведение, а компиляторы сами разбираются, как его реализовать на какой-то экзотической платформе. Но вместе этого выбрано худшее решение — UB.

              • 0xd34df00d
                /#23167278

                В C++20 (ЕМНИП, либо таки отложили на 23) отрицательные числа уже обязаны быть two's complement.

                • Gryphon88
                  /#23167316

                  Я вот не помню, кто так не делает, вроде последние исключения перестали серийно выпускаться ещё в 80х.

            • vkni
              /#23167356

              За UB стоит философия совершенно другого стиля программирования.

              Представьте мир до С. Тогда программы были не универсальными, а писались под конкретную машину. То есть, нормально было полностью переписать программу при портировании. Ведь во главу угла ставилось быстродействие, а не универсальность!

              Поэтому С, позволяя в принципе писать универсальные программы, позволял и «подточить под машину». То есть, эти UB были часто вполне определены для сочетания компилятор/машина. Соответственно, как я уже приводил пример AIX/POWER — вы могли спокойно разыменовывать NULL, если точно знали, что ваша программа не покинет эту систему.

              См. статью «What every compiler writer should know about programmers»

              • creker
                /#23167386

                Об этом недавно была статейка. Да, на заре Си UB означало в большей степени implementation defined — т.е. места, где стандарт не хочет мешать, а дает вольность компилятору и программисту делать так, как им нужно. Но в какой-то момент трактовку в стандарте поменяли и UB стало трактоваться совершенно иначе. Оно превратилось в универсальное оправдание для компиляторов делать какие им вздумается оптимизации и ломать код. В итоге мы имеем язык, как выше писал, который не может определиться. Он и не низко уровневый, потому что компилятор не дает использовать возможности железа из-за непредсказуемости UB, но и не высокоуровневый, потому что заставляет страдать с ручным управлением памятью и прочими штуками. Но в стандарт многопоточность и атомики мы почему-то добавили.

            • Amomum
              /#23167588

              Запросто. Упомянутое переполнение знаковых. Нахрена это было делать UB? Код всегда будет содержать подобные ошибки, но в том же расте по крайней мере получишь панику или wrap-around.

              Потому что чтобы сделать панику или wrap-around нужно ну… сделать это. Сделать проверку или учитывать, что может быть wrap-around.


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


              Пример:


              https://godbolt.org/z/ocvErqG37 — unsigned int, переполнение это wrap-around
              https://godbolt.org/z/481ch13ez — int, переполнение это UB


              Если что, я не фанат этого момента, мне не нравится ни С, ни С++ — по разным причинам :) Просто объясняю, как сам понимаю конкретно этот момент.


              Разумеется, пример высосанный из пальца, это просто демонстрация; но насколько я понимаю, почти все UB в стандарте позволяют делать похожие штуки.

              • khim
                /#23167764

                Сделать проверку или учитывать, что может быть wrap-around.
                Вы дня начала найдите процессор, который не умеет делать wrap-around.

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

                Разумеется, пример высосанный из пальца, это просто демонстрация; но насколько я понимаю, почти все UB в стандарте позволяют делать похожие штуки.
                Собственно в этом и проблема. Когда у вас подобный код:
                    if (p == q) {
                        *p = 1;
                        *q = 2;
                        printf("%d %d\n", *p, *q);
                    }
                

                Успешно оптимизируется до
                    if (p == q) {
                        printf("%d %d\n", 1, 2);
                    }
                


                И из этого никак не удаётся даже warningа получить, то это, извините, не язык, а минное поле.

                • Amomum
                  /#23168326

                  Вы дня начала найдите процессор, который не умеет делать wrap-around.

                  Вы по ссылкам посмотрели ассемблер? Учитывать, что может быть wrap around; сделать-то его не проблема, разумеется.


                  И из этого никак не удаётся даже warningа получить, то это, извините, не язык, а минное поле.

                  Таки да. Мне, собственно, очень не нравится, что многие типы UB, которые можно детектировать на этапе компиляции (или в рантайме в отладочной сборке), не детектируются вообще никак. И вместо того, чтобы починить язык, изобретаются санитайзеры и валгринд...

                  • khim
                    /#23168406

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

                    Что касается UB… Почитайте provenance proposal:

                    http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

                    У вас волосы дыбом встанут. Самое же страшное в нём даже не то, как он предлагает изменить стандарт, а в том, что разработчики компиляторов УЖЕ используют эти фичи новых UB, планируемые к добавлению в стандарт.

                    Фактически и C и C++ сегодня - это недо-Rust, в котором ошибки манипуляций с лайфтаймами приводят не к тому, что компилятор начинает выраждать недовольство, а к тому, что программа начинает неправильно работать.

                    Практически - таким языком пользоваться, увы, нельзя. Ну не способен человек за всем этим проследить, потому в Rust и встроен borrow checker!

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

                    Простейший известный мне способ получить UB в безопасном коде: берёте Rc, выкручиваете счётчик ссылок на переполнение через mem::forget, дальше когда счётчик близок к переполнению - у вас происходит катастрофа в “безопасном Rust” с помощью “безопасного Rust”… так что да, 100% гарантии нет, но сравните с тем, что в C или C++, где у вас самые простейшие конструкции готовы превратить вашу программу в труху.

                    • Amomum
                      /#23168424

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

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


                      У вас волосы дыбом встанут. Самое же страшное в нём даже не то, как он предлагает изменить стандарт, а в том, что разработчики компиляторов УЖЕ используют эти фичи новых UB, планируемые к добавлению в стандарт.

                      Да, я читал, и по-моему все достаточно логично.
                      Я не говорю "хорошо и мне нравится", просто логично, потому что сейчас это уже происходит или подразумевается, только нестандартизировано :)


                      И еще раз повторю, мне не нравится С. Мне не нравится UB, я не считаю, что ради двух инструкций нужно так всем сношать мозги. Не нужно меня убеждать, что С — то плохо, а Rust — хорошо, я с вами уже согласен :D


                      Я вам просто пытаюсь объяснить, как оправдывают наличие UB.

                      • creker
                        /#23168460

                        Я вам просто пытаюсь объяснить, как оправдывают наличие UB.

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

                        • Amomum
                          /#23168486

                          Я думаю тут все и так это знают.

                          За всех говорить не могу.


                          Не стоят эти жалкие оптимизации этого.

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

                          • hhba
                            /#23173414

                            Именно поэтому на моей прошлой работе использование оптимизаций было попросту запрещено. Оптимизируй руками, если надо. ЧСХ надо было в реальной жизни куда реже, чем кажется…

                            • 0xd34df00d
                              /#23173482

                              ЧСХ надо было в реальной жизни куда реже, чем кажется…

                              Зачем тогда брать C?

                              • khim
                                /#23173664

                                В качестве переносимого ассемблера?

                                Да, скорость, без оптимизаций, получается раз в три ниже, чем с оптимизациями (больше бывает только если у вас есть SIMD и векторизация, С это не C++, там в стандартной библиотеке нет 100500 уровней индирекции, без изничтожения которых вы получите замедление раз в 100), но это всё равно гораздо быстрее, чем PHP или там Python, да и кода получается меньше, а если уж приспичит - можно и ручками ускорить.

                                • 0xd34df00d
                                  /#23173920

                                  В качестве переносимого ассемблера?

                                  Так а зачем эта ассемблерность, если производительность неважна? Или это самоцель, что ли?


                                  Да, скорость, без оптимизаций, получается раз в три ниже, чем с оптимизациями

                                  Если вас устраивает производительность раза в три ниже (по моему опыту — зависит, иногда и куда больше), то возьмите какой-нибудь другой компилируемый язык без [такого огромного количества] UB и собирайте спокойно с оптимизациями. Go там, я не знаю. Хаскель, в конце концов (уж кода будет точно сильно меньше).

                                  • khim
                                    /#23174090

                                    Так а зачем эта ассемблерность, если производительность неважна?

                                    В embedded очень часто важна производительность и размер кода, но только до некоторой степени.

                                    Размер в 3-4 раза больше минимально возможного и скорость в 3-4 раза меньше минимально возможной — годится.

                                    Рантайм на мегабайт или два — не годится. У чипа всей памяти может быть килобайт 100, а то и меньше.

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

                                    Сделать под это однопоточный async — и вообще красота будет (может кто-нибудь и сделал уже).

                                    Go там, я не знаю. Хаскель, в конце концов (уж кода будет точно сильно меньше).

                                    У них у обоих “hello, world” больше мегабайта. Или я чего-то упустил?

                                    • 0xd34df00d
                                      /#23174310

                                      В embedded очень часто важна производительность и размер кода, но только до некоторой степени.

                                      А, ну если embedded… Но там, с другой стороны, переносимость не так важна. Вон, даже в этом треде люди отметились, что у них там всё под одну железку, один компилятор, один народ.

                                    • hhba
                                      /#23174378

                                      Больше мегабайта? Хм, а я как раз думал о том, что у Го хорошие перспективы в микроконтроллерах. Простой язык, сопрограммы на борту, синтаксис правда чуть чудной, но совсем не тот «блдск ужс ^32», что у Раста.

                                      Насчет Хаскеля 0xd34df00d пошутил наверное. Слишком сложно для нашего брата.

                                      Сделать под это однопоточный async


                                      Я бы все-таки предпочел многопоточность. Ну, ок, пусть будет и то, и другое, но не только асинки. В принципе я согласен на таски из Ады.

                                      • khim
                                        /#23174460 / +1

                                        Простой язык, сопрограммы на борту, синтаксис правда чуть чудной, но совсем не тот «блдск ужс ^32», что у Раста.

                                        Только вот там GC в комплекте и всё, что с этим связано. Как и у Haskell. Там заметно больше мегабайта получается.

                                        Я бы все-таки предпочел многопоточность. Ну, ок, пусть будет и то, и другое, но не только асинки.

                                        Речь идёт о чём-то для “самых маленьких”.

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

                                        А так-то, разумеется Rust умеет и в многопоточность и в SQL и во всякие прочие радости, разумеется.

                                        А что касается fn mut let ref impl… ну что поделать, да, изначальные разработчики всё очень любили скрщ.

                                        Теперь уж не исправить, так что с этим нужно просто примириться. Это не самая большая проблема в этой жизни. За ошибки компиляции вместо отстрела ноги при малейшей ошибке Rust'у многое можно простить (хотя, ещё раз повторяю, на всякий случай: в unsafe там такой же содом и гоморра, как в C/C++, так что их количество рекомендуется сводить к минимуму… однако при этом некторые важные вещи только через unsafe и можно сделать, так что гнаться за “нуль unsafe”, как за самоцелью, не стоит).

                                        • 0xd34df00d
                                          /#23174582

                                          Как и у Haskell. Там заметно больше мегабайта получается.

                                          И тут мне таки стало интересно.


                                          % cat Main.hs
                                          main :: IO ()
                                          main = putStrLn "hello world"
                                          % ghc -O2 Main.hs
                                          [1 of 1] Compiling Main             ( Main.hs, Main.o )
                                          Linking Main ...
                                          % strip -s Main
                                          % ls -ltra Main
                                          -rwxr-xr-x 1 d34df00d d34df00d 760848 июн 21 19:35 Main

                                          Ура, таки меньше мегабайта!


                                          Но это так, в порядке развлечения. Понятно, что в микроконтроллеры это не запихнёшь.

                                        • hhba
                                          /#23175202

                                          Только вот там GC в комплекте и всё, что с этим связано. Как и у Haskell. Там заметно больше мегабайта получается.


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

                                          А многопоточный рантайм маленьким не бывает


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

                                          Теперь уж не исправить, так что с этим нужно просто примириться


                                          «Теперь UB уже не исправить, с этим нужно просто примириться» (Б. Керниган)

                                          Это не самая большая проблема в этой жизни


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

                                          • khim
                                            /#23176036 / +1

                                            «Теперь UB уже не исправить, с этим нужно просто примириться» (Б. Керниган)

                                            Ну только не Керниган, а Ритчи. Саботажники ж уже тогда хотели протащить нечто подобное: https://groups.google.com/g/comp.lang.c/c/K0Cz2s9il3E/m/YDyo_xaRG5kJ

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

                                            Только совсем недавно саботажники, обнаружив, что помечать restrict активно народ так и не начал… перегнули палку и пользоваться C и/или С++ стало невозможно.

                                            Там и других проблем хватает.

                                            Каких, интересно?

                                            Язык очень-очень сложный, и не говорите ради Бога, что это не так и лично вам он зашел изи)))

                                            Я боюсь вы просто слабо знакомы с так называемым Modern C++.

                                            Там где std::move, SFINAE и прочее метапрограммирование. Когда читаешь книжку про Rust, прям душа радуется.

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

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

                                            А вот как это восопринимается чистым C'шником, не знакомым с Modern C++… не знаю, не пробовал.

                                            • hhba
                                              /#23176120 / -1

                                              Ну только не Керниган, а Ритчи.


                                              Ну это шутка была в любом случае, так что не важно)))

                                              Каких, интересно?


                                              1) Сложный язык, как я далее написал.
                                              2) Множество трудолюбивых фанатов, которые тут же побежали в репу и в голосование))))

                                              Я боюсь вы просто слабо знакомы с так называемым Modern C++. Там где std::move, SFINAE и прочее метапрограммирование. Когда читаешь книжку про Rust, прям душа радуется.


                                              Много лет я писал (ну или пописывал, так как на С/C# писал все же больше) совершенно без опоры на современные стандарты, и ничего. Два года назад с вашей кстати подачи начал читать одну книгу по С++17, и все в ней было довольно понятно, и все новые вещи были довольно прозрачны (хотя и неочевидно полезны, если у тебя нет проблем с самоконтролем) — правда та книга намеренно не погружала читателя в глубины глубин. Так что я бы не сказал, что не знаком вообще, но и сколько-нибудь знающим человеком себя конечно не назову. Тем не менее мое мнение однозначно — это вполне по силам С-программисту. А вот книжки вида «Раст для умалишенных» я пытался аж два раза осилить, где-то спустя десять минут оно надоедало. Полезнее это время потратить на дальнейшее чтение той книги по С++17, кою я к сожалению забросил совершенно из-за нехватки времени.

                                        • creker
                                          /#23175458

                                          Только вот там GC в комплекте и всё, что с этим связано.

                                          Кстати Go в embedded очень даже успешен. github.com/f-secure-foundry/tamago Вот эта штука очень интересная, но есть и другие. Сейчас вот вторая попытка заапастримить. Там изменений очень мало требуется в рантайме.

                                          • hhba
                                            /#23176188

                                            Ну, знаете, там хотя и баре-метал, но все же это для верхнего сегмента эмбеда пока что. Понятно, что на МК наверное тоже натягивается, но насколько посильно для унтерменшей сделать туда BSP?

                                            • creker
                                              /#23177320

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

                                              • hhba
                                                /#23177376

                                                Ну, э, я просто перешел сюда, и там именно верхний сегмент: github.com/f-secure-foundry/tamago#supported-hardware

                                                • creker
                                                  /#23177410

                                                  Даже первый в списке? SoC, который можно в флэшку засунуть, как-то сложно назвать верхним сегментов. Я так понимаю именно для этой платформы делался проект — авторы про этот продукт часто рассказывают.

                                                  • hhba
                                                    /#23177442

                                                    Процессор на ядре А7 вы считаете не относится к верхнему сегменту? Более чем. Те самые «малинка и ее друзья» по сути. Взрослый процессор, просто не самый мощный в линейке, ни разу не микроконтроллер.

                                      • 0xd34df00d
                                        /#23174580

                                        Насчет Хаскеля 0xd34df00d пошутил наверное. Слишком сложно для нашего брата.

                                        В известном смысле не сложнее C++. Даже проще, я бы сказал.


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

                                        • hhba
                                          /#23175170

                                          не сложнее C++


                                          Давайте вот лично вы никогда таких вещей говорить не будете))) Это же просто издевательство над среднестатистическим человеком. Примерно как если бы какой-нибудь знаменитый порноактер с прохладцей в голосе говорил, что пенис в 32см — не так уж много.

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


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

                              • hhba
                                /#23174360

                                А что другое брать, простите? Я ж про эмбед, там выбор не так велик. И для большинства нашего брата плюсы слишком сложны и непредсказуемы, так что выбор сужается буквально до одного языка — не самого безопасного, зато самого простого. Впрочем лично с вами мы уже это обсуждали, и даже не один раз наверное.

                                P.S.: Я тут не так давно внимательно проштудировал обновленный сайт adacore — пожалуй соглашусь, что и Аду можно назвать «достаточно простой». Раньше мне так не казалось, но я еще в институте про нее последний раз читал более-менее подробно, с тех пор я стал наверное поумнее.

                            • Amomum
                              /#23173690

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

                              • khim
                                /#23173824

                                За вами уже вышли. Гляньте сюда: https://godbolt.org/z/1d7Goaca4

                                Обратите внимание, что clang'у пофиг на-O0, он успешно может сломать программу с UB даже в этом режиме.

                                А поскольку, как мы уже выяснили, в UB записывается то, что левой пятке разработчиков захочется объявить UB (на самом деле нет, какое-то обсуждение происходит, обычно и какая-то логика там наблюдается… только вот документов, перечисляющих все эти UB в результате не остаётся), то это только лишь вопрос времени когда GCC воспоследует за ним и с него придётся куда-нибудь бежать тоже.

                                Остался только вопрос: куда, собственно, бежать-то?

                                P.S. Хотя у GCC есть, конечно, пока что “тормоз” в лице Линуса (долгих ему лет жизни), но он, увы, тоже не вечен.

                                • Amomum
                                  /#23173856

                                  Как хорошо, что я использую Кейл! Там как раз новая, шестая версия компилятора - это переупакованный clang...

                                  • hhba
                                    /#23177486

                                    Эммм, а чем же это хорошо, учитывая приведенный выше пример про цланг? Наоборот же…

                                    К слову, на той же прошлой работе мы имели дело с одним анально огороженным компилятором (Paradigm C++), разработчики которого чурались частых улучшений. А могли бы брать пример вот с цланга например! :)

                                    И еще интересно было бы конечно глянуть на то, как ведет себя iarcc в таких же случаях. Жаль годболт не умеет.

                                    • Amomum
                                      /#23178186

                                      Понятно, нужен был тег "сарказм" :)

                              • hhba
                                /#23174368

                                где, кажется, есть какой-то культ оптимизации ради оптимизации


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

              • creker
                /#23168454 / -1

                Потому что чтобы сделать панику или wrap-around нужно ну… сделать это. Сделать проверку или учитывать, что может быть wrap-around.

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

                Я прекрасно понимаю, как работает UB и что дает компилятору. Я как раз против этого и выступаю. Не хочу я, чтобы компилятор такие вольности имел. Потому что сегодня он мне «a < a+1» на «eax 1» заменил, а завтра компилятору вздумается что-то сделать по-другому, и я получу уязвимость в коде. При этом предупреждений и ошибок мне никто не даст никаких.

                • Amomum
                  /#23168458

                  Ну так, Rust имел потрясающую возможность — учиться на ошибках С и С++ :)

                  • creker
                    /#23168466

                    С/С++ ее тоже имели, но они продолжали пользоваться костылями в виде UB для оптимизаций. Никто не мешал в начале пути этих языков остановиться и задуматься над этим.

                    • Amomum
                      /#23168506

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

                      • EvilMonk
                        /#23170622

                        Вот кстати про обратную совместимость. Почему бы, начиная с какой-нибудь версии, её не убрать? Например, программы, написанные на С++20 и раньше, также продолжать поддерживать на С++20, а на С++23 уже создавать что-то с нуля. Вроде с Python'ом такое было. Никто же не запрещает писать на более ранней версии языка?

                        • Amomum
                          /#23171552

                          Уфф, даже не знаю, с чего начать… Это повлечет за собой столько боли.
                          Питон — отличный пример; третья версия появилась двенадцать лет назад. Вторая все еще здесь. Она все еще нужна. Ее все еще приходится ставить отдельно от третьей.
                          Ее приходилось поддерживать в течении 10 лет вроде.


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

                        • khim
                          /#23171882

                          Вроде с Python'ом такое было.

                          У Python был переход со 2й версии на 3ю, который занял 10 лет и до сих пор до конца не окончен.

                          Никто же не запрещает писать на более ранней версии языка?

                          А это не Python, это, скорее, Rust. Где вы можете в одной программе компоновать модули на Rust 2015, Rust 2018 и Rust 2021. И да, это очень правильный подход.

          • Temtaime
            /#23167254

            От любого UB только вред. В нём нет пользы. Те жалкие доли процента производительности, которые могут появиться из-за UB — это не польза, а на грани погрешности.
            Есть D — в нём практически нет UB. По производительности он не уступает, а благодаря намного более мощным шаблонам — можно генерировать код в компайл-тайме, и LLVM потом это всё оптимизирует по самые яйца.

            • khim
              /#23167766

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

              Если вы знаете хоть одну такую — приведите пример, я объясню как сделать программу, которая с -O0 будет работать, а с вашей оптимизацией сломается.

              Есть D — в нём практически нет UB.
              Практически в нём полно UB. Там мало UB, которые вам не нравятся — это возможно. Но вообще UB в нем полно.

              Вот safe Rust — реально пытается избавиться от UB, но в нём они тоже есть, просто их случайно устроить очень тяжело.

              а благодаря намного более мощным шаблонам — можно генерировать код в компайл-тайме,
              Опять-таки в Rust поступили проще: процедурные макросы это программа на Rust (полноценном, не урезанном) и могут делать что угодно.

              • kmeaw
                /#23168040

                Я пока не могу придумать, как написать программу, которая после function inlining сломается.

                • khim
                  /#23168298 / +1

                  Это потому что вы интуитивно отметаете программы с UB - с теми, которые вам кажутся настоящими, “плохими” UB, которые программист уж точно не будет допускать. Но возьмите классику жанра: https://godbolt.org/z/9hfPeWrhM

                  Много вы опитимизаций можете придумать, которые подобные программы оставят работоспособными? И да, с function inilining, всё легко может сломаться (как, собственно, в clang и происходит).

                  • kmeaw
                    /#23168366

                    Оптимизаций, где подобные программы не изменят поведения я придумать не смогу.

                    Всё зависит от определения «сломаться». Моя интуиция воспринимает функцию bar(), как UB-вариант «return rand();». Если под «поломкой» понимать любое изменение поведения, заметное пользователю, то оптимизации могут и без UB сломать программу, работа которой зависит от относительной производительности различных участков кода.

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

                    • khim
                      /#23168434

                      Примеры на которых сгенерированный код ведёт себя не так, как ожидат программист приводить бессмысленно: тут же начнётся “сага о настоящем шотландце”: https://ru.wikipedia.org/wiki/%D0%9D%D0%B8_%D0%BE%D0%B4%D0%B8%D0%BD_%D0%B8%D1%81%D1%82%D0%B8%D0%BD%D0%BD%D1%8B%D0%B9_%D1%88%D0%BE%D1%82%D0%BB%D0%B0%D0%BD%D0%B4%D0%B5%D1%86

                      Можно себе, наверное, представить и человека, помнящего наизусть все стандарты C, C++ и все решения комитетов по стандартизации (где описаны ошибки в стандарте, который будут править в следующих версиях, типа такого: www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf ), для него вообще никаких программ, которые могли бы сломаться при какой-либо оптимизации не будет.

                      Нам ведь что говорили?

                      От любого UB только вред. В нём нет пользы. Те жалкие доли процента производительности, которые могут появиться из-за UB — это не польза, а на грани погрешности.

                      ОТ любого. А не от одного конкретного, который вам не нравится. Моего примера, в общем, достаточно, чтобы показать, что не от любого. А если рассматривать “бессмысленные UB”, которые проблемы создают, а ускорения от них в реальных программах нету, ну так кто бы спорил! Вот то самое UB, которое от realloc - пользы от него нуль просто потому, что в реальных программах realloc мало когда появляется в критически важных по скорости участках кода, а вред несомненен.

                      P.S. Описанная программа, кстати, работает на большинстве компиляторов 70х, вот в 80е (или уже в 90е?) начали появляться такие, которые её ломают.

                  • creker
                    /#23168488 / +1

                    Хех, здесь встает другой вопрос — какого хрена переменные по-умолчанию не зануляются. Тогда бы UB не было и компилятор был бы волен оптимизировать этот код сколько угодно. Т.е. UB это следствие, а проблема тут в идеологии авторов стандартов и компиляторов. Все в языке происходит от нее. И зануления нет, потому что кому-то кажется, что это слишком дорого. Зато иметь UB от этого это прям замечательно. Главное, что компиляторы в бенчмарках красиво смотрятся.

                    Если мы вспомним managed языки, то там отсутствие UB никому не мешает код оптимизировать очень даже хорошо. Да и расту не мешает тот факт, что в корректном коде UB получить практически невозможно. Он умудряется обгонять порой С/С++. Все потому что весь остальной дизайн языка построен вокруг отсутствия UB, поэтому у компилятора не отбирают свободу оптимизировать. Хотя это все тот же llvm.

                    • ilammy
                      /#23168512

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

                      Добавляйте в флаги -finit-local-zero и -finit-derived, будут инициализироваться (кроме указателей, вроде).

                      • creker
                        /#23168528

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

                      • khim
                        /#23168630

                        Тоже мне, нашли панацею.

                        Во-первых “cc1: warning: command-line option '-finit-local-zero' is valid for Fortran but not for C”.

                        А во-вторых даже если обнулять - всё равно оптимизации невозможны: https://godbolt.org/z/chaxze71P

                        Всё, что можно сделать - это разделить UB на “хорошие” и “плохие”. Но всем пофиг: разработчики компиляторов нифига не заинтересованы в этом (они, наоборот, новых UB добавтить хотят), “инжинерные программисты” пишут не на C или C++, а на конкретной версии конкретного компилятора, а те, кто уже понял во что превратились C и C++ - переходят на Rust.

                    • technic93
                      /#23176220

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

                      • creker
                        /#23177362

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

                • mayorovp
                  /#23168390

                  Ну вот function inlining ломает программы если в этой функции среди параметров есть эксклюзивный (restrict) указатель. Ну или ломал раньше, но новостей о починке я не видел.

              • japplegame
                /#23171434

                Оптимизации опираются на UB? На какие UB опирается, например, свертка констант?

                • khim
                  /#23171964

                  Ну вот вам программа, которую свёртка констант ломает: https://godbolt.org/z/Yjc9vczob

                  А вот другая: https://godbolt.org/z/xPxa6ErTz

                  Вообще люди, пытающиеся утверждать, что UB не нужны для оптимизации, мысленно делят все UB на два класса: “вещи, которые я делать люблю” (и которые, вроде как, оптимизации не мешают) и “дичь какая-то, кто вообще такое пишет”.

                  И когда говорят про UB имеют в виду только “вещи, которые я делать люблю”.

                  Но это же, извините меня, ни разу не “любые UB”. “Любые UB” — это то, что я написал (и не только).

                  Успехов вам в оптимизации подобного кода (особенно еслиfoo иbar в разных единицах трансляции).

                  • 0xd34df00d
                    /#23173524

                    Ну вот вам программа, которую свёртка констант ломает: https://godbolt.org/z/Yjc9vczob

                    Зачем так сложно?


                    https://godbolt.org/z/47zzejqfc

                    • khim
                      /#23173794

                      Да, вы правы. Ваш вариант прекрасен ещё и тем, что clang его успешно с-O0 ломает: https://godbolt.org/z/1d7Goaca4

                      Это для тех, кто будет рассусоливать про то, что стандартный режим компиляции это -O0 и там компилятор ведёт себя “разумно”.

                      Нет, там он тоже не ведёт себя “разумно”. Он ведёт себя как он хочет (как мы уже выяснили стандарт ему не указ), а ты, смерд — как хочешь, так и живи с этим.

                      • 0xd34df00d
                        /#23173934

                        Там ещё интересно, что если вы возьмёте гцц и скормите ему тот же исходник, только в C++-режиме, он его тоже сломает даже с -O0.

                      • japplegame
                        /#23175372

                        Нет он тоже не прав. По вашему свертка констант в данном случае "опирается" на это вот UB с модификацией константы? Тогда у меня для вас плохие новости: свертка констант отлично работает БЕЗ этого UB, достаточно просто убрать const.

                        Интересно также узнать какое поведение компилятору в данном случае предписывает стантдарт (вы же вроде как выяснили, что стандарт компилятору не указ)?

                        • khim
                          /#23176202 / +1

                          Похоже все с вами спорящие вас просто не поняли. Давайте вернёмся в список постулатов:

                          1. Все (или почти все) оптимизации в языке C (и C++) опираются на отсутствие в программе UB - так язык C устроен, в нём иначе нельзя.

                          2. При этом некоторые UB - крайне нелогичны и неожиданны для программиста.

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

                          Вы с чем конкретно спорите: с пунктом #1, #2 или #3?

                          Для меня, если честно, последнее было неожиданностью. Но из песни слов не выкинешь: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

                          Если вы прочитаете это предложение внимательно, то вам станет ясно, что #3 - это не мои выдумки, а, увы, горькая правда.

                          • technic93
                            /#23176236

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

                            • khim
                              /#23177146 / +1

                              Всё ещё хуже. Стандарты пока ещё совместимы (резолюция об изменения стандарта принята, но пока не ратифицирована, более того, пока неясно как именно будет изменён стандарт), но компиляторы уже превращают старые программы в тыкву.

                              Ну это всё равно как если бы дом построили в водоохранной зоне, а когда это было бы обнаружено, то было бы принято решение об изменения закона, а не о сносе нафиг “незаконной постройки”.

                          • japplegame
                            /#23176596

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

                            То есть то, что в некоторых случаях компилятор для оптимизации опирается на предположение, что код не содержит UB, совсем не означает, что если если этот UB сделать вполне себе DB, то оптимизация станет, внезапно, невозможной.
                            С пунктами 2 и 3 полностью согласен.

                            • khim
                              /#23176930 / +1

                              Я не согласен с пунктом 1. Точнее со оценкой степени количества зависимых от UB (точнее их отсутствия) оптимизаций.

                              В таком случае вы должны уметь объяснить как оптимизировать все те программы, которые мы 0xd34df00d напридлумывали. Вперёд и с песней.

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

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

                              То есть то, что в некоторых случаях компилятор для оптимизации опирается на предположение, что код не содержит UB, совсем не означает, что если если этот UB сделать вполне себе DB, то оптимизация станет, внезапно, невозможной.

                              Проблема в том, что он опирается на подобное предположение не в некоторых случаях, а почти во всех. Ну разве что 2 * 2 вы на 4 можете безболезненно заменить. И то только тогда, когда обе двойки у вас прямо в коде написаны. Если хотя бы одна из них константа - всё, уже ничего менять нельзя. И переменные в регистры со стека поднимать нельзя. И вообще много чего нельзя.

                              Потому что и на то, что переменные без оказания register живут на стеке и на то, что у вас бессмысленное присваивание x = 2 делается в цикле и на многое другое — какая-нибудь программа с UB может полагаться.

                              И не надо тут рассказывать сказки про то, что "код не имеет смысла": если имеются компиляторы (пусть старые и древние, из 1970х), где он работает стабильно - значит смысл таки имеется.

                              Просто с точки зрения стандарта это UB.

                  • Temtaime
                    /#23173812

                    Мы, вроде бы, про реальную разработку, а не про специальную олимпиаду.
                    Представленные программы НЕ являются корректными и результаты их выполнения не определены.
                    Удивительно, функция, чей результат не определён, внезапно, возвращает разные значения!

                    • khim
                      /#23173874

                      Мы, вроде бы, про реальную разработку, а не про специальную олимпиаду.
                      Представленные программы НЕ являются корректными и результаты их выполнения не определены.

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

                      Удивительно, функция, чей результат не определён, внезапно, возвращает разные значения!

                      Стоп-стоп-стоп. Тпру. Все приведённые мною программы дают вполне определённый и одинаковый результат при компиляции с помощью разных версий GCC (с опцией -O0, разумеется). Многие из нах дают такой же результат и при компиляциями разными другими компиляторами (скажем всем известный Turbo C 2.01, да).

                      Так что даёт вам право говорить, что их результат неопределён? Стандарт? Так вы ж сами сказали, что От любого UB только вред. В нём нет пользы.

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

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

                      P.S. Или вы хотели сказать, что программы с UB не нужно просто писать? Дык я с радостью, только объясните, чёрт побери, как это сделать! Если, как теперь выясняется, тщательно прочитать стандарт и удостовериться, что то, что ты делаешь не попадает в список в соотвествующем приложение — недостаточно (в C++, кстати, такого приложения и нету, оно только в стандартах C, в явном виде, присуствует), то… как тогда?

                      • japplegame
                        /#23175480 / -1

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

                        • khim
                          /#23176108 / +1

                          Замените гипотетически все UB на ошибки компиляции и что, оптимизации станут невозможными?

                          Rust получится.

                          В чем важность UB для оптимизации?

                          В том, что конкретно в языке C почти любая оптимизация ломает какую-нибудь, возможно извращённую, программу с UB.

                          В других языках UB может и не быть. Например в Java UB нет, а оптимизации у компилятора есть.

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

                          UB - это всего лишь костыль для компенсации несовершенства компиляторов.

                          Ещё раз: в языках C и C++ UB это единственное, что позволяет вообще что-то как-то оптимизировать.

                          В других языках может быть иначе. Но в C/C++ так.

                          Совершенствуйте компиляторы, исправляйте стандарты, просто не пишите и другим не давайте в конце-концов.

                          Я бы согласился, если бы количество UB со временем уменьшалось (как собственно и планировалось) http://www.open-std.org/jtc1/sc22/wg14/www/docs/n897.pdf

                          Undefined behavior gives the implementor license not to catch certain program errors that aredifficult to diagnose. It also identifies areas of possible conforming language extension: theimplementor may augment the language by providing a definition of the officially undefinedbehavior.

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

                          С этого момента C/C++ are unfit for any purpose.

                  • japplegame
                    /#23175302

                    По первому вашему примеру можно предъявить сразу две претензии:

                    1) Ваш код изначально поломан. Поэтому совершенно неясно, что именно сломала оптимизация. Ведь "сломать" означает, что до поломки все работало как надо, а после поломки перестало. Верно? Вы считаете, что ваш пример без оптимизации работал как надо? Если да, то какие именно результаты вы считаете приемлемыми от подобной "правильно" работающей программы?
                    2) Примем вашу странную терминологию и назовем это "поломать". Но на этом ваше весьма вольное обращение с русским языком не заканчивается. Почему вы считаете, что из "ломать программу с UB" следует "опираться на UB"? Где логика? Наоборот же, если бы не было UB, то оптимизация ничего бы не "сломала" (по вашей терминологии). Получается, что оптимизация опирается не на UB, а на отсутствие UB.

                    • khim
                      /#23176228

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

                      Ваш код изначально поломан.

                      Что за наезды? Я вам снейчас с десяток компиляторов (из 80х-90х) подгоню, где он будет отлично работать.

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

                      Работающий код, очевидно. Со вполне простой и понятной семантикой. Программирование с стиле фортрановских common block'ов. А чё такого то?

                      Если да, то какие именно результаты вы считаете приемлемыми от подобной "правильно" работающей программы?

                      Вот именно тот, который выдаёт GCC с -O0. Это, собственно то, что первые лет 10 существования C все компиляторы выдавали.

                      Получается, что оптимизация опирается не на UB, а на отсутствие UB.

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

                      Что проще: написать if x + 1 < x (простая и понятная конструкция, очевидно делающая то, что хотели) или что-нибудь типа if x = std::numeric_limits<decltype(x)>::max(). Дикое нагромождение разной жути, ешё и работающее хуже, как вы известном ессе изучали: https://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_2015_submission_29.pdf

                      На самом деле можно сделать и эффективно и без UB, если написать вот так:

                      if (std::is_signed_v<decltype(x)> &&

                      std::make_signed(std::make_unsigned(x) + 1U) < x ||

                      !std::is_signed_v<decltype(x)> && (x + 1) < x)

                      Это, конечно, легко и просто и совсем почти не сложнее, чем if x + 1 < x, согласитесь?

                      • technic93
                        /#23176510

                        Мне кажется тут в каком то смысле извечная проблема классиков про борьбу поколений...


                        Вот я вижу такой код:


                        if x + 1 < x (простая и понятная конструкция, очевидно делающая то, что хотели)

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


                        Т.е. такая проверка на переполнение, это действительно стиль си из 90ых. Обусловлена она возможно тем что искать эти дурацкие макросы INT_MAX (или как-то так) лень, а возможно какая-то мода, а возможно действительно экономили размер бинарника.


                        Для меня, гораздо более очевидный и читаемый код, будет такой
                        if x == i32::MAX. Тут намерения выражены более явно, и я бы никогда не написал первый вариант в угоду второму.


                        Далее мы приходим к ещё одному моменту почему писать первый вариант лучше (который скорее всего и есть решающий). А что если тип у икса не int32_t? В си (и с++) это компилятор проглотит без ошибки и без предупреждений, в итоге при рефакторинге типа икса будет баг в программе. Поэтому если бы си был современным языком, без молчаливых кастов, то стрёмный первый вариант и не родился бы.


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


                        И наконец-то про тот код на С++, во-первых decltype и std::numeric_limits действительно выглядят много-словно, т.е. если бы решение выглядело if x == typeof(x)::MAX, то мне оно все ещё казалось бы лучше. Задача же обрабатывать signed и unsigned в одной функции сразу, в Си не стоит потому-что там нету темплейтов.


                        Если мы все ещё хотим сэкономить на спичках, то можно научить это делать компилятор, вроде как задача найти паттерн сравнения с максимальным/минимальным значением не сложная. Или же используйте вынесите ваш код в вспомогательную функцию которую можно определить для любого типа (вот где мощность темплейтов действительно полезна). Было бы классно ещё вызывать её как-то так if x.is_max_saturated().


                        Имхо, во всех вариантах намерения выражены более явно и понятно, чем в первом, а эра while (*d++=*s++) ушла. Может эта эра и осталось в эмбеддед, но компиляторы оттуда уходят получается...


                        П.С. я понимаю, что вы это прекрасно понимаете и без меня :) Просто делюсь наблюдениями.

                        • japplegame
                          /#23176858

                          Имхо, во всех вариантах намерения выражены более явно и понятно, чем в первом, а эра while (*d++=*s++) ушла. Может эта эра и осталось в эмбеддед, но компиляторы оттуда уходят получается...
                          Не нужна эта эра и в эмбеддед. Она там остается исключительно потому что там в основном работают консервативные инженеры в возрасте, уверенные, что они всегда умнее компилятора, а время остановилось. Я же по своему опыту в эмбеддед могу сказать, что почти всегда нет никакой необходимости в ручных оптимизациях, компилятор сам все делает не хуже, а местами и лучше человека.

                        • khim
                          /#23177038 / +1

                          А что если тип у икса не int32_t? В си (и с++) это компилятор проглотит без ошибки и без предупреждений, в итоге при рефакторинге типа икса будет баг в программе.

                          Я правильно понял, что в такой программе бага не будет:

                          int64_t x;
                          if (x == INT_MAX) {
                            ...
                          }

                          Это ж просто офигительно, но… как?

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

                          А если у бабушки было бы известно что, то она была бы дедушкой, извините. Мы не обсуждаем Java, Ruby или даже Haskell. Речь идёт про C (ну и чуть-чуть про C++).

                          Вариант if x + 1 < x лучше как раз тем, что он не зависит от типа, с которым вы работаете, Любой целый тип, со знаком или без знака обрабатывается одинаково.

                          Задача же обрабатывать signed и unsigned в одной функции сразу, в Си не стоит потому-что там нету темплейтов.

                          Однако кто-то туг про рефакторинг песни пел… не напомните, кто?

                          Что касается темплейтов, то там ситуация чудесна и удивительна: в стандарте темплейтов нет, но во всех реализациях — они, в том или ином виде, есть. А потому что без них tgmath.h не делается: https://en.cppreference.com/w/c/numeric/tgmath

                          И в любом случае вопрос не в том, удобнее ли if x + 1 < x, чем другие варианты, а в то, что для чисел без знака это всё ещё работает, а для чисел со знаком — нет. Притом, что я не знаю ни одного выпускаемого сегодня процессора, который бы умел это отлавливать для чисел без знака, но не для чисел со знаком.

                          П.С. я понимаю, что вы это прекрасно понимаете и без меня :) Просто делюсь наблюдениями.

                          Это понятно. Но просто если уж вы начинаете прикрываться стандартом и оправдываете подобный идиотизм, то уж будьте последовательны: либо вы реализуете язык, как он описан в стандарте (и плюёте на всё остальное), либо стандарт для вас не закон — а тогда с какого фига у вас числа без знака и со знаком обрабатываются по разному?

                          • technic93
                            /#23177612

                            Ну типа -Wsign-compare может чуть чуть помочь.

                            • khim
                              /#23177724

                              Чем она поможет, если вы вместо LONG_LONG_MAX (как должно быть для 64-битной платформы) написали INT_MAX?

                              Абсолютно законная (и абсолютно бессмысленная) деятельность.

                              Может PVS-Studio чем-то поможет, но зачем? Зачем заменять простую и надёжную конструкцию на что-то более сложное и менее надёжное?

                              Потому что в стандарте так написано? Ok, принимается, но только если стандарт чего-то стоит.

                              А если стандарт разработчика к чему-то обязывает, а саботажника — нет, то блин, какого чёрта?

                              Вы там все этодругина, что ли, перепили?

                              • technic93
                                /#23178432

                                Поэтому и написано "чуть чуть". В том месте я изначально намекал на раст, где таких неявных кастов между i32/i64 нету ;)

                      • japplegame
                        /#23176768

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

                        Ни в коем случае. Просто я не понимаю зачем вы оправдываете заведомый, простите, говнокод и негодуете когда оптимизация его ломает.
                        Что за наезды? Я вам снейчас с десяток компиляторов (из 80х-90х) подгоню, где он будет отлично работать.
                        Вам просто повезло, что он работает так как вы ожидаете. Программа с переполнением стека тоже может годами работать, а потом внезапно в ней что-то начнет ломаться, потому что авторы компилятора что-то там перекроили в генераторе кода. И вина в этом именно автора кода переполняющего стек, а не авторов компилятора.
                        Работающий код, очевидно. Со вполне простой и понятной семантикой. Программирование с стиле фортрановских common block'ов. А чё такого то?
                        Использование неинициализированных переменных содержащих мусор, вы называете «понятной семантикой»? Действительно, а что такого, когда результат вашей программы зависит, грубо говоря, от положения звезд на небе?
                        Вот именно тот, который выдаёт GCC с -O0. Это, собственно то, что первые лет 10 существования C все компиляторы выдавали.
                        А на каком основании вы решили, что GCC с -O0 выдает верное решение, а с -O2 неверное? Конкретно в вашем примере GCC с -O0 выдал 9. Но стоило мне в функции foo j = 3 исправить на j = 2 и GCC с -O0 начал выдавать 6. Так 6 или 9 или еще какое-нибудь число из диапазона 32битного знакового целого?

                        • khim
                          /#23177122

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

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

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

                          Быстро, эффективно, и компиляция занимает миллисекунды.

                          И вина в этом именно автора кода переполняющего стек, а не авторов компилятора.

                          Потому что, внезапно, переполнение стека - это тоже UB.

                          Использование неинициализированных переменных содержащих мусор, вы называете «понятной семантикой»?

                          Здравствуйте, я ваша тётя. Кто сказал что они неинициализированы? Они в одной функции инициализируются, в другой используются. В чём проблема?

                          Действительно, а что такого, когда результат вашей программы зависит, грубо говоря, от положения звезд на небе? :)

                          Причём тут звёзды на небе вообще? Они зависят от раскладки переменных на стеке. В ранних яызках (в 60е) эта информация вообще в мануалах писалась (и программы на основе этих подходов писались), потом это стало “дурным тоном”… но это ж вы тут завели гармонь, что программы с UB умеете оптимизировать, не я.

                          А на каком основании вы решили, что GCC с -O0 выдает верное решение, а с -O2 неверное?

                          На основании здравого смысла, очевидно.

                          Но стоило мне в функции foo j = 3 исправить на j = 2 и GCC с -O0 начал выдавать 6. Так 6 или 9 или еще какое-нибудь число из диапазона 32битного знакового целого?

                          Вы издеваетесь или как? Разумеется если вы замените j на 2, то результат будет 6.

                          Фишка этой программы в том, что тут нет “неиницилизированных переменных”. Они инициализируются. В функции foo. А потом используются. В функции bar.

                          Поскольку переменные инициализированы, то мы знаем ответ.

                          Это был нормальный и поддерживаемый стиль в 60е. Более того, на калькуляторах этот стиль и сегодня используют, почитайте про ans: http://tibasicdev.wikidot.com/68k:ans

                          А про то, что программы с UB вы умеете как-то оптимизировать — это ж вы сказки поёте, не я.

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

                          • kovserg
                            /#23177936

                            Почему до сих пор нет опции компилятора которая вместо использования UB для оптимизации, выдавала ошибку. Или хотя бы предупреждение что вот «это» теперь оно воспринимает как UB.

                            • khim
                              /#23178100

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

                              Что-нибудь типа:

                              void foo(int* a, int* b, int* c, int i, int j, int k) {
                                a[i] = b[j] + c[k];
                              }

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

                              У вас a может указывать на константу (это UB), у вас все три индекса могут за границы массива выходить, у вас память под эти пересенные может не быть аллоцирована, в конце-концов или они вообще могут быть неинициализорованными

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

                              Ну и кому нафиг нужен компилятор, который такую функцию откажется компилировать с диагностикой на три страницы?

                              Для оптимизации используется не UB, а отсутствие UB — а это весьма и весьма непросто сделать, так что компилятор просто полагается на то, что вы всё хорошо продумали и написали всё так, что UB ваша программа не вызвывает.

                              И такой подход можно было бы “понять и простить”, если бы… если бы хоть где-нибудь был бы полный и исчерпываюший список этих самых UB!

                              Но, внезапно, оказывается, что такого списка нет и саботажники требуют, чтобы ваша программа не содержала UB — но при этом отказываются объяснить что же, чёрт побери такое эти UB!

                              В C++ стандарте списка UB вообще нет, в C стандарте он есть, но если верить саботажникам, то имеются ещё и какие-то мистические, нигде пока не описанные real and valid emergent property that every compiler vendor ever agrees on.

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

                              Кафка отдыхает.

                              • kovserg
                                /#23178266

                                Так я об этом и говорю. Как только компилятор считает что UB и пытается это использовать надо об этом сообщать.
                                Например даже int a=0x12345; может быть UB. Но это UB (int 16 бит) компилятор пока еще ругается что число не влазит, а когда использует всякую дичь которая позволяет выйти из бесконечного цикла (исходя из того что на какой-то архитектуре, отличной от целевой, это может вызвать исключение), просто молчит и генерит всякие непотребства.

                                ps: придётся пользоваться старыми, проверенными компиляторами, а не этим слабо предсказуемыми. Которые в борьбе за оптимизацию ради оптимизации, оптимизируют здравый смысл ибо UB.

                                • khim
                                  /#23178440

                                  Например даже int a=0x12345; может быть UB.

                                  По-моему это просто прекраснейший пример. Потому что вот конкретно это присвоение UB не является и имеет право быть в валидной программе и тут себя одинаково ведут себя все компиляторы (в смысле генерируют валидную программу, как и должны, warning'и это опционально).

                                  Но это UB (int 16 бит) компилятор пока еще ругается что число не влазит.

                                  Да, но только он ругается потому, что полезно на это ругаться, а не потому что это UB. Это вообще не UB и программа, содержащая подобную конструкцию абсолютно валидна. Правда-правда.

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

                                  Погодите? Какие непотребства он генерит? Этот случай очень чётко описан в стандарте:

                                  An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of 1 a for statement) its expression-3, may be assumed by the implementation to terminate

                                  Тут тоже нет никакого UB, всё очень чётко а аккуратно прописано: если цикл ничего не меняет “во внешнем мире”, то его можно удалить. И всё.

                                  Хотите бесконечного цикла — используйтеfor(;;); илиwhile(true);

                                  нафига вам больше бесконечных циклов?

                                  Или вы про то, что вы пытаетесь из цикла смотреть на какой-то глобал без volatile ? Ну это, как бы, да, UB, но я бы не сказал, что это какое-то особо страшное UB. Без барьеров и прочего у вас сама железяка может устроить вам кузкину мать, даже без компилятора: https://preshing.com/20120515/memory-reordering-caught-in-the-act/

                                  ps: придётся пользоваться старыми, проверенными компиляторами, а не этим слабо предсказуемыми. Которые в борьбе за оптимизацию ради оптимизации, оптимизируют здравый смысл ибо UB.

                                  Проблема в том, что эти “слабопредсказуемые” это где-то примерно начиная с GCC 2.95, вышедшего в прошлом веке (скорее даже egcs).

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

      • Gryphon88
        /#23167290

        В своё время, когда компьютеры по возможностям соответствовали современным нетоповым микроконтроллерам, был зоопарк архитектур и не существовало IDE, наличие кучи UB было оправданной: программист писал код на бумажке, держа в голове те или особенности, и фактически вклад программиста и компилятора в преобразование текста программы в исполняемый код был примерно одинаковый. Ну просто не мог тогдашний комп прожевать полноценный компилятор. А зачем сейчас UB оставляют — без понятия.

        • kmeaw
          /#23167432

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

          Если бы UB было однозначным злом, то рынок бы давно захватил компилятор, который спасает программиста от этой «проблемы». Сейчас же мы имеем UBSan и кучу флажков (всякие -fwrapv, -fno-delete-null-pointer-checks, -fno-strict-aliasing).

          • creker
            /#23167496

            Вместо компилятора мы имеем языки, которые захватили мир и не страдают всей этой фигней с UB. Для С/С++ уже скорее всего время упущено. Убирать UB из них поломает обратную совместимость. Да и это чисто идеологическая проблема в том числе — за стандартами и компиляторами стоят люди, которым UB и возможность отыграть жалкие доли процентов производительности похоже важнее всего остального. Благо появился раст, который может быть быстрее без всех этих проблем.

            • 0xd34df00d
              /#23167498 / +1

              Убирать UB из них поломает обратную совместимость.

              Эм, нет. Любое определённое поведение является подмножеством неопределённого поведения.


              Вот производительность поломать может, да.

              • creker
                /#23167514

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

                • kmeaw
                  /#23167556

                  Линус регулярно сталкивался с тем, что новая версия gcc генерирует не тот код при сборке ядра, какой ему бы хотелось. И ничего страшного не происходит, экосистема в хаос не погружается — он просто ругается в LKML, добавляет очередной ключик в KBUILD_CFLAGS и продолжает использовать gcc.
                  С glibc тоже была похожая история, когда появилась новая версия memcpy, копирующая память в другом направлении на некоторых процессорах. Это сломало кучу кода (в частности плагин Flash), Линус снова ругался, но тоже ничего страшного не случилось — все по-прежнему используют glibc.
                  Можно сначала реализовать какое-то ожидаемое программистами поведение в качестве UB в одном компиляторе (вряд ли это сложнее, чем спроектировать новый язык и сделать для него хороший оптимизирующий компилятор). Для gcc существенную часть этой работы можно сделать, просто изменив настройки по-умолчанию.

                  • creker
                    /#23168532

                    Это маленькие примеры изменения поведения. А при изменении стандарта поведение изменится кардинально и у всех. Я думаю тут хаос и наступит, когда повсюду начнет ломаться тонна кода. Да, это все решаемо, но усилий и времени это будет стоить очень много. По сути, получится чуть ли не ситуация как питоном 2 и 3. Вроде бы код совместимый, а вроде и нет.

  5. NeoCode
    /#23166902 / +2

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

    С++ слишком сложный. Причем дело не в количестве фич — к примеру у C# их тоже много; дело в какой-то вывернутости наизнанку, в результате чего в коде больших проектов на С++ обязательно найдется «дописывание компилятора» — такие супер-пупер-универсальные навороченные шаблоны или макросы, призванные решить какие-то сверхабстрактные задачи, никакого отношения к целевой задаче не имеющие. Достаточно посмотреть добрую половину библиотек Boost, куда попадают такие образчики. А ведь кроме Буста, люди практически в каждом проекте свои такие вот велосипеды пишут…

    Это все говорит о том, что навороты С++ идут куда-то не туда. Вероятно, из-за обратной совместимости — сначала с Си, а затем и с предыдущими версиями С++ — ничего уже не сделать. В том числе поэтому я понемногу проектирую свой язык программирования, не менее низкоуровневый чем Си, не такой сверхстрогий как Rust, и включающий практически всё из большинства современных языков, но в максимально продуманном и интегрированном виде.

    • Alexey2005
      /#23167184

      Разработку нового языка программирования я бы советовал начинать не с компилятора, а с плагинов к популярным IDE и менеджера пакетов.
      Именно они обеспечивают 90% продуктивности, а весь синтаксический сахар — от силы 10%.
      Потому что самый быстро создаваемый код — это код, который уже написан. Реиспользование кода должно ставиться во главу угла в современном мире. Прошли времена, когда каждый писал софт от и до. Поэтому если инструментарий языка не позволяет быстро собрать программу из готовых кусочков, а IDE — быстро разбираться в этих кусочках, если вдруг к ним нет внятной документации, то такой язык в конечном итоге будет неинтересен даже собственному создателю. В нём просто нет смысла в XXI веке.

      • ss-nopol
        /#23168084 / +1

        Разработку нового языка программирования я бы советовал начинать не с компилятора, а с плагинов к популярным IDE и менеджера пакетов.
        Именно они обеспечивают 90% продуктивности, а весь синтаксический сахар — от силы 10%.

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

        • mayorovp
          /#23168400

          Хотите сказать, что автодополнение — костыль? Что же в таком случае является правильным решением той проблемы, которую решает автодополнение? Неужто однобуквенные идентификаторы?

          • ilammy
            /#23168490 / +1

            Да ладно бы автодополнение идентификаторов просто чтобы не писать длинные имена каждый раз и не делать опечаток. Это решается каким-нибудь тупым ctags успешно.


            Автодополнение допустимых методов объектов — это мощный инструмент для изучения языка и библиотек. Если IDE не умеет анализировать программу и выводить типы переменных — то всё, никаких подсказок.


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

          • ss-nopol
            /#23169210

            Хотите сказать, что автодополнение — костыль?
            А что, Вам автодополнение обеспечивает 90% производительности? Ну тогда да, костыль.

        • Alexey2005
          /#23168826

          Современное программирование — это не столько про написание собственного кода, сколько про чтение чужого. В XXI веке писать собственные велосипеды уже непродуктивно, поскольку реализации большинства алгоритмических задач уже не только написаны, но и отполированы до блеска.
          Поэтому и продуктивность определяется главным образом тем, насколько быстро программист может читать чужой код, ориентироваться в нём и прикручивать к собственному проекту.
          При таком раскладе IDE даёт просто чудовищный буст к продуктивности, поскольку упрощает ориентирование по коду.

    • victor_1212
      /#23167288

      C, C++ просто инструменты для создания sw продуктов, у обоих есть положительные и отрицательные качества и своя область применения, в смысле синтаксиса, в контексте информатики как науки, imho оба языка достаточно простые, если бы нужно было делать новый язык программирования, начал бы с изучения algol-68, не для того, чтобы его расширить, а просто для самообучения, чтобы использовать как reference point,
      ps
      пожалуй главное, что не нравится в C++ это иллюзия объектно ориентированного языка,
      чем симпатичен C — это своей честностью «типа недалеко ушел от ассемблера и этим горжусь», и широтой применения

  6. bogolt
    /#23167024 / +2

    В IIRC C++

    может быть «Если я правильно помню то в C++»… ( IIRC это аббревиатура if i remember correctly )

    • technic93
      /#23167150 / +2

      Интересно ход мыслей переводчика. Подумал наверное что это програмистский термин =)

      Апд. Или я пытаюсь понять ход мыслей АИ сейчас...

  7. redsh0927
    /#23167052 / -5

    в стандартной библиотеке C нет даже описаний контейнеров
    Туда им и дорога. Это не недостаток, а плюс. Невозможно из «контейнеров» построить оптимальную и эффективную структуру данных.
    Причина, по которой мне всё ещё нравится C, заключается в том, что это — простой язык.
    Точнее, си — это язык-чистый лист, кульман. На котором можно изобразить почти что угодно и это что угодно просто превратится в исполняемый код практически 1:1 без всякой отсебятины. Надо проверять границы массива — проверяешь, не надо — не проверяешь.

    Всегда очень много слов про всякие UB, неявные преобразования и прочие «i++ + ++i». Да наплевать. Я даже не знаю как работает *p++, просто всегда пишу *(p++) или (*p)++. Си — язык инженеров, а не снобов-перфекционистов.

    • 0xd34df00d
      /#23167058 / +4

      Надо проверять границы массива — проверяешь, не надо — не проверяешь.

      Надо проверять в компилтайме. Как это на С сделать?


      Всегда очень много слов про всякие UB, неявные преобразования и прочие «i++ + ++i». Да напревать. Я даже не знаю как работает p++, просто всегда пишу (p++) или (*p)++. Си — язык инженеров, а не снобов-перфекционистов.

      Это до первого ночного звонка или дебаггинга длиной в неделю из-за того, что компилятор что-то наоптимизировал в вашем инженерном коде.

      • anger32
        /#23167562

        Надо проверять в компилтайме. Как это на С сделать?

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

        struct mem {
            char data[0];
        }

        Как мне это сделать в молодежном питоне?

        Это до первого ночного звонка или дебаггинга длиной в неделю из-за того, что компилятор что-то наоптимизировал в вашем инженерном коде.

        Число прецедентов стремится к 0 в системной разработке. Скорее будешь неделями искать какого «волшебного» бита в регистре не хватает и о чем молчит документация вендора.

        • 0xd34df00d
          /#23167636

          Пас обратно. [...] Как мне это сделать в молодежном питоне?

          Не, не обратно. Я про питон даже не вспоминал, так что непонятно, причём он тут.


          Число прецедентов стремится к 0 в системной разработке. Скорее будешь неделями искать какого «волшебного» бита в регистре не хватает и о чем молчит документация вендора.

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

          • anger32
            /#23167680

            Не, не обратно. Я про питон даже не вспоминал, так что непонятно, причём он тут.

            Не придирайтесь к словам, питон лишь пример. Возьмите удобный вам язык со строгим контролем границ массива. Рассматривайте мой вопрос лишь в контексте того, что есть задачи, где контроль границ даже вреден. Ровно как в процитированном вами: «Надо проверять границы массива — проверяешь, не надо — не проверяешь»

            То есть, С — это язык для проектов

            Он вообще не для проектов, так как создан был задолго до этого явления. Использовать ванильный Си в современном понимании термина «проект» скорее значит не очень себя любить. Но для системного уровня его заменить пока как-то нечем. Это факт, проверенный десятилетиями. Увы. В будущем что-нибудь да найдется. Вроде экспериментов с rust в Linux. Посмотрим что из этого выгорит.

            • 0xd34df00d
              /#23167722

              Рассматривайте мой вопрос лишь в контексте того, что есть задачи, где контроль границ даже вреден. Ровно как в процитированном вами: «Надо проверять границы массива — проверяешь, не надо — не проверяешь»

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


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

              Энное время назад системным программированием считалась и разработка СУБД, и разработка компиляторов. СУБД я не пилил, а вот компиляторы на С я бы точно писать не стал.

              • anger32
                /#23167738

                Не знаю, когда не надо их проверять, особенно если оверхеда в рантайме это не имеет

                Не знание не освобождает от вопроса. Пример приведен — сегмент памяти переменной длины. Используется, например, для описания низкоуровневого клиент-серверного протокола с переменной длиной сообщения.

                а вот компиляторы на С я бы точно писать не стал

                Как бы вам сказать… Есть такой, например. Применяется широко и исходно на Си. А вот когда себя из исходников соберет, вот тогда и плюсами может побаловаться. Для утилитарной библиотеки, например.

                • 0xd34df00d
                  /#23167778

                  Пример приведен — сегмент памяти переменной длины.

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


                  Как бы вам сказать… Есть такой, например. Применяется широко и исходно на Си.

                  И начался он на С… когда? Лет 35 назад, да?


                  Вообще для general-purpose-языка быть self-hosted — хорошая демонстрация возможностей. Поэтому компилятор сей пишется на сях, компилятор раста — на расте, компилятор хаскеля — на хаскеле, компилятор идриса — на идрисе… ну вы поняли идею.


                  А вот если вам нужно сделать компилятор какого-нибудь предметно-специфичного языка, или proof of concept, или что такое, то писать это на сях — чистой воды мазохизм.

                  • anger32
                    /#23168162

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


                    В общем случае не знаю. Жестко дан только максимум буфера, а не его фактическое состояние. Пример:

                    Исходный буфер (размечен структурой выше + uint64_t len):
                    [ <-------------------- len --------------------> ]

                    Сейчас в нем сообщение поместилось:
                    [ header(sz<len/2) ][ data(sz<len/2) ]

                    а завтра уже нет:
                    [ header(size<len/2) ][ <-------- data(size>len*2) -------> ]

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

                    Нет, я конечно могу выделять буфер сразу на пару гигов (точно ли «хватит всем»?). Но почему-то не все такому подходу радуются.

                    А вот если вам нужно сделать компилятор какого-нибудь предметно-специфичного языка, или proof of concept, или что такое, то писать это на сях — чистой воды мазохизм

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

                    • mayorovp
                      /#23168414

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

                      • anger32
                        /#23168738

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

                        • mayorovp
                          /#23169572

                          А зачем вам обязательно сырые указатели-то? Большинство проблем уходит если рядом с указателем хранить размер буфера.


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

                          • anger32
                            /#23170714

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

                            Тут случаем нет ремарки вроде: «пока не пришел какой-нибудь garbage collector»?

                            • mayorovp
                              /#23171458

                              От языка зависит. В C# и питоне такой ремарки нет (конкретно для буфера). В Rust нет никакого GC.

                              • creker
                                /#23171506

                                В смысле в C# нет? Там же mark and sweep сборщик. Чем больше живых объект, тем больше ресурсов тратит сборщик мусора. Ему этот буфер надо каждый раз найти и пометить живым. Подозреваю, что в питоне будет тоже самое, раз там есть mark and sweep сборщик для разрешения циклических ссылок.

                                • mayorovp
                                  /#23171528

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

                                • khim
                                  /#23174470

                                  У стандартной реализации Python есть mark and sweep сборщик, но он не так давно стал обязательным. В разных мелких версиях для микроконтроллеров его нету.

                              • anger32
                                /#23174394

                                Потому лишь последний и внедряется в линь в экспериментальном порядке. Буфер же тут не единственный дискуссионный момент.

                    • 0xd34df00d
                      /#23169560 / +1

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

                      И текущий size вы не знаете? Даже в рантайме? Как вы тогда понимаете, сколько там от заголовка, а сколько — от тела?


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

                      Я вообще начал с того, что непонятно, что вы считаете системным уровнем. Потому что если это, скажем, эмбеддщина — это один разговор. Если это и классические системные задачи вроде компиляторов — совсем другой, и я бы сказал, что там есть сильно более важные задачи, чем «выделить заголовок с фиксированной структурой и тело произвольной длины одним malloc'ом непрерывно в памяти».

                      • anger32
                        /#23170672

                        И текущий size вы не знаете? Даже в рантайме? Как вы тогда понимаете, сколько там от заголовка, а сколько — от тела?

                        Может быть по разному реализовано. Если size хэдера фиксированного размера — одна песня. Если он плавает, то, например, код запроса в ioctl() может его определять. Интереснее size данных, который может вообще заголовком не определяться никак, а быть равным (размер всей передачи "-" size хэдера).

                        Думаю, что все это не так важно. Не вижу путей как тут должен компилятор в compile-time контролировать границы.

                        Я вообще начал с того, что непонятно, что вы считаете системным уровнем

                        Парой постов ниже отписался.

                        • 0xd34df00d
                          /#23170718

                          Думаю, что все это не так важно. Не вижу путей как тут должен компилятор в compile-time контролировать границы.

                          Он проконтролирует, что вы всё правильно проконтролируете.


                          В конце концов, если у вас там


                          for (size_t i = 0; i < packetSize - headerSize; ++i)
                              data[i] = ...;

                          то статически понятно, что все обращения корректны. Даже если все значения известны только в рантайме.


                          Более того, даже если там не data[i], а data[lookupTable[i]], но про значения в lookupTable известно что-нибудь хорошее (например, это какая-то перестановка списка из [0..packetSize - headerSize], пусть даже известная только в рантайме), то вы тоже можете статически всё доказать и ничего не проверять в рантайме.

            • creker
              /#23168558

              Что значит системного уровня? Ядро и драйвера? Да, кроме раста нет ничего. Но нынче системное программирование это в том числе базы данных, компиляторы и прочий подобный софт. Уж это все пишут на каких угодно языках и С там никому не сдался.

              • anger32
                /#23168754

                Хороший вопрос, спасибо. Ответ зависит от того, что считать системой. Если программные системы — то и СУБД можно считать системным уровнем. Если говорить о программно-аппаратных системах, то я бы сказал, что всё, что выходит за прикладные интерфейсы ядра/драйвера — прикладной уровень. Про драйверный код ремарка не случайна, поскольку он есть в LLVM и отсутствует в GCC. Граница немного плавает.

                В дискуссии я исхожу из второго подхода.

        • khim
          /#23167768

          Число прецедентов стремится к 0 в системной разработке
          Серьёзно? Расскажите мне про компанию, где бы это было совмещено с ежемесячным обновлением компилятора (как, скажем, Android разрабатывается).

          Обычно сам намёк на то, что ты хочешь обновить компилятор у сторонников подобного “инженерного подхода” вызывает истерику: “низя-низя-низя, всё ж поломается!”

          Но это значит, что вы пишите не на C, на конкретно «GCC C версии x.y.z сборки Васи Пупкина».

          • anger32
            /#23168122

            Серьёзно? Расскажите мне про компанию, где бы это было совмещено с ежемесячным обновлением компилятора (как, скажем, Android разрабатывается).


            Вы хотите чтобы я еще и антитезис себе подобрал, серьезно?

            Для начала стоит понять зачем всё это в системной разработке. Микрокод, драйвер, BSP,… пишутся под конкретное окружение и адаптируется под конкретные вариации инструментов. В будущем появляется необходимость перенести наработки в другое окружение => портируем, адаптируем и т.п.

            Обычно сам намёк на то, что ты хочешь обновить компилятор у сторонников подобного “инженерного подхода” вызывает истерику: “низя-низя-низя, всё ж поломается!”

            У нас с вами разное обычно. У нас в конторе используется GCC 4.4.2 (отходит в небытие), 4.8.3, 8.3.0. В дополнение к нему LLVM/clang, версию увы не помню, поскольку он пока в экспериментальной стадии. И МЦСТ/LCC версий аутентичных версиям CPU. При этом компиляторно зависимые макросы нужны преимущественно последнему, так как отдельные builtins не поддержаны.

            • khim
              /#23168320

              Вы уж меня извините, но вопрос стоял не: а хотители ли вы заменить компилятор, а можете ли вы это сделать.

              Если не можете - значит ваша программа имеет очень мало отношения к языкам C и/или C++.

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

              Если не учитывать UB, то переносимости не будет. А если пользоваться одним, конкретным, компилятором, то можно себе устроить "переносимый ассемблер" хоть из бейсика.

              В 90е было можно: просто вкорячиваешь туда через PEEK/POKE машинный код и вызываешь. Делов-то. У конкретного интерпретатора все структуры по фиксированным адресам (они даже в книжках в те годы печатались).

              Всё? BASIC у нас теперь стал высокоуровневым ассеблером, да?

              • anger32
                /#23168658

                Вы уж меня извините, но вопрос стоял не: а хотители ли вы заменить компилятор, а можете ли вы это сделать

                Не вижу таких проблем в реальных проектах. Регулярно собираем объектники LLVM и линкуем в GCC. Нативно компилируем постоянно и свои проекты и опенсорс. Без примеров говорить тут особо не о чем, больше похоже на какие-то фобии.

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

        • mayorovp
          /#23168412

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

          Теоретически, при наличии в языке завтипов — можно.


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


          template<size_t N>
          struct mem {
              char data[N];
          }
          
          // …
          
          struct buffer {
              size_t len;
              mem<len> *ptr;
          }

          • anger32
            /#23168694

            На C/C++ можно, ибо контроль границ отсутствует. Дискуссия о контроле границ массивов. Выше пример давал — границы приходят извне.

            • mayorovp
              /#23169494

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

    • mistergrim
      /#23167100 / +1

      Мне кажется, инженер должен знать, как работает его инструмент.

    • tzlom
      /#23167102 / +1

      А инженер это фигак фигак и в продакшн? В моей компании полно легаси С кода, но проблема не в этом, проблема в том что каждый новый кусок С кода который попадает мне на ревью содержит ошибки работы с памятью. При этом не важно сколько опыта у человека - КАЖДЫЙ пуллреквест или течет или сегфолтится при определенных условиях. А почему? Потому что писать нормальную обработку ошибок на С это попаболь. Поэтому даже людей которые не особо в C++ рубят я по возможности заставляю писать С++ код. Естественно там можно тоже накосячить но правильно написанный код хотя бы имеет шансы влезть в экран.

      • redsh0927
        /#23167162 / -1

        «Нормальная» обработка ошибок — это эксепшены поди? Эти прыжки в обход структуры кода наподобие всеми оплёваного goto? Спасибо, не надо. Посмотрел код возврата, принял решение что делать дальше. Просто, наглядно, надёжно.
        гг, хорошо что мне никакие снобы не командуют на чём писать. Я и асм юзаю более чем регулярно. На асме у меня куча вещей, которые на ЯВУ не влезли бы в доступные такты в разы.

        • creker
          /#23167216

          Просто, наглядно, надёжно.

          Код освобождения ресурсов у вас тоже накопирован повсюду, чтобы было «Просто, наглядно, надёжно»? Да, к сожалению, goto единственный способ сделать нормальную обработку ошибок в С. Потому что проблема не в обработке, а в очистке ресурсов. Си не нужны исключения, ему нужен defer.

          • aamonster
            /#23168028 / +1

            gcc аттрибут cleanup.

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

            • creker
              /#23168566

              gcc это не С. Но да, в проекте для iOS я прекрасно пользуюсь расширениями для реализации полноценного defer. Просто потому что это iOS и думать о других компиляторах и платформах мне не нужно.

              • aamonster
                /#23169048

                Про необщность ответа было в дважды протраченном тексте :-)

        • tzlom
          /#23167240

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


          auto arg = something();
          int result = call_me_will_return_int(arg);

          и то же на С


          // это же С так что объявляем всё сверху, конечно сейчас это не обязательно но всё ещё повсеместно встречается
          int result;
          double arg1;
          int error_code;
          // 300 строк спустя
          error_code = call_me_will_return_int(arg, &result);
          if(error_code != OK)
          {
            LOG_ERROR_CODE(error_code);
            // return ? 
            // а вот нет, пиши ифы до самого конца - снизу руками чистится память
          }

          Одно только


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


          Ну и про "такты", если вы пишете под МК то возможно вы правы, я пишу под Xeon и я отыграл 8% времени исполнения кода переписав его с СИ на плюсы. И это был уже оптимизированный до меня код числодробилки откручивающийся на 16мс. Разумеется не смена языка дала прирост, но те оптимизации которые С++ позволил сделать раздули бы СИ код раза в 4, в то время как результирущий С++ код стал меньше в 3 раза.

          • JerleShannara
            /#23167552

            Не возможно, а именно что правы, порой под МК лучше вообще побольше ассемблерных вставок сделать, чем материться от того, что память кончилась. Такой просчёт тактов и объёмов памяти даёт возможность поставить более слабый и дешевый(не в реалиях повидлового дефицита нынешнего правда) МК, что даёт неплохую экономию на серии, что позволяет либо сэкономить на цене комплектухи, либо за те же деньги поставить более качественную пасивку и поднять срок службы.
            Увы, но сейчас становится модным «а давайте Cortex-M4 втулим в термостат, наговнокодим и приколхозим питон в мк», что очень сильно огорчает.

            • tzlom
              /#23168230

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

              • JerleShannara
                /#23169162

                Согласен, для ответственного применения(а КМК доменная печь от кривой работы термостата может испортиться до того, что дешевле будет новую поставить/построить) я бы поставил industrial grade, может даже ещё более старый (8051 чем не вариант) и написал бы на ассемблере, добавив мажоритарное резервирование в аппаратной части. В случае авиационного применения(естественно не доменной печи) — может быть даже на разных архитектурах МК.

                • MichaelBorisov
                  /#23169812

                  Мне один чел рассказывал, какие в аэрокосмической отрасли на Западе нынче используются подходы к программированию. Главная установка: «не писать код».

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

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

                  Кроме того, используется тестирование вида «Hardware in the loop». Для каждого блока управления создаётся испытательный стенд, который подаёт ему на вход тест-сигналы и сравнивает выходные сигналы с эталонными.

                  • mayorovp
                    /#23170044

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

                    • MichaelBorisov
                      /#23174484

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

                      • mayorovp
                        /#23174558

                        А вот по этим критериям уже не факт что графические языки программирования выигрывают.

          • anger32
            /#23167560

            если у вашей платформы нет системы раскрутки стека


            Перед такими заявлениями почитайте про setjmp(). Для порядка. К слову, исключения в плюсах до сих пор местами сделаны через него.

            • tzlom
              /#23168240

              А кто сказал что я о нём не знаю? От setjmp до раскрутки стека как от меня до луны.


              К слову, исключения в плюсах до сих пор местами сделаны через него.

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

              • anger32
                /#23168646

                Вот я что то сомневаюсь, пруфы есть?

                Погуглите «SJLJ» в контексте разных компиляторов. Не единственный вариат, как я и сказал, но не лишенный своих преимуществ.

                • tzlom
                  /#23168784

                  А, так вы о деталях реализации раскрутки стека, я как то и не понял когда вы функцию setjmp() упомянули, это разные вещи всё таки.
                  Принципиально вообще никакой разницы какой там механизм раскрутки стека, главное что он есть, SJLJ есть только в С++, в Си ничего подобного нет, и память будет течь если накосячить, вот пример: https://godbolt.org/z/4ra1obh6G

          • viordash
            /#23168442 / -1

            вместо goto можно же использовать отдельные методы, да и читабельность повысится.

            void SomeProcess(int* data) {
                if (Func1(data) == 1) {
                    return;
                }
                ....
                if (Func3(data) == 3) {
                    return;
                }
            }
            
            int main() {
                int *data = (int*)malloc(100 * sizeof(int));
                SomeProcess(data);
                free(data);
            }
            

            • kmeaw
              /#23168462 / +1

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

              Например, память и файловый дескриптор
              void SomeProcess(int *data) {
              FILE *f = OpenFile(1);
              void *mem = AllocateMemory();
              Func1(mem, data);
              Func2(f, mem);
              CloseFile(f);

              f = OpenFile(2);
              Func3(mem);
              Func2(f, mem);
              CloseFile(f);
              FreeMemory(mem);
              }

              • viordash
                /#23168974

                вариант для вашего примера

                void MemProcess(void *mem) {
                	FILE *f = OpenFile(1);
                	if (FileErr(f)) {
                		return;
                	}
                	Func1(mem, data);
                	Func2(f, mem);
                	CloseFile(f);
                
                	f = OpenFile(2);
                	if (FileErr(f)) {
                		return;
                	}
                	Func3(mem);
                	Func2(f, mem);	
                }
                
                void SomeProcess(int *data) {
                	void *mem = AllocateMemory();
                	MemProcess(mem);
                	FreeMemory(mem);
                }
                

                • kmeaw
                  /#23169018

                  К сожалению, неэквивалентный результат. Мне стоило уточнить, что я брал принятый в Go порядок аргументов — сначала приёмник, потом источник.
                  Func1 пишет в mem и читает f. Func2 пишет в f и читает mem. Func3 пишет в mem.

                  • viordash
                    /#23169130

                    извините, но я не понял вас.
                    Основной посыл, что вместо использования goto, аллоцировать/открывать до отдельного метода, а освобождать/закрывать после него. Это позволяет внутри метода не писать сложные if-ы, и выходить из метода в произвольном месте

                    • kmeaw
                      /#23169166

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

                      Вот пример реального кода:
                      git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/accessibility/speakup/kobjects.c?h=v5.12.12#n1004

        • 0xd34df00d
          /#23167252 / +1

          Нормальная» обработка ошибок — это эксепшены поди?

          Это Maybe, Either и прочие трихомонады.


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

          Или забыл посмотреть. Или не учёл, что ошибочный код возврата может быть просто выделенным значением возвращаемого типа, при этом валидным для некоторых других API (man 2 fork и man 2 kill, про это даже статья на хабре была).


          Очень надёжно.


          гг, хорошо что мне никакие снобы не командуют на чём писать.

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

          • Gryphon88
            /#23167332

            Это Maybe, Either и прочие трихомонады.
            Вроде можно, но получается дороговато: много прыжков и выделения памяти.

            • tzlom
              /#23168262

              А ещё компилятор по рукам не ударит если вы вызовете divexn без всех этих обёрток.

  8. INSTE
    /#23167172

    А почему автор заявляет, что любит C, и не любит кресты и при этом ноет на тему sequence points и aliasing? При чем вообще кресты в этих двух вещах? Это же чисто сишные вещи, всегда ими были и остаются (оставим за бортом тему реализации в компиляторах, не факт что без крестов было бы лучше).

    • aamonster
      /#23169056

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

      И вообще, как известно, программисты делятся на две категории: тех, кто ненавидит C++ и пишет не на нём, и тех, кто ненавидит C++ и пишет на нём.

  9. Gryphon88
    /#23167212

    Кто постоянно пишет на С++, скажите, а в обратной совместимости с С остался какой-то смысл? В 1993 он был, но, имхо, достаточно быстро потерялся.

    • Antervis
      /#23167232

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

      • Gryphon88
        /#23167238

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

        • tzlom
          /#23167244

          Это обсуждают уже лет 8, в 23м стандарте это не произойдёт.
          Есть надежда на модули — если они взлетят это может стать границей раздела старого и нового кода, но я бы раньше 30го года не загадывал.

          • hhba
            /#23177412

            но я бы раньше 30го года не загадывал.


            Вы шутите что ли? Я своими глазами читал утверждение, что взят коммитмент на переделку стандартной библиотеки на модули к С++23…

            • tzlom
              /#23177632

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


              #include <iostream>
              import <iostream>

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

        • Junecat
          /#23167534

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

          - есть, конечно. Кажется, это называется "Раст"... Ну, или C# - как варинат. Go еще не дорос...

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

          • khim
            /#23167776

            Ну, или C# — как варинат. Go еще не дорос...
            Ни C#, ни Go не годятся, так как полагаются на сборщик мусора.

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

          • MichaelBorisov
            /#23169838 / -1

            Ха-ха, у меня тоже было такое чувство, когда переходил на C после долгой работы на C++.

            Ничего, привык за пару месяцев. Более того, мне понравилось! Оказалось, без контейнеров и умных указателей прекрасно можно жить; более того, нет расхода ресурсов контроллера на все эти модные вещи! Сейчас работаю с довольно большими проектами на сотни тысяч строк C-кода. Ни одного malloc! Вся память выделяется или статически, или на стеке, или из пулов большими блоками.

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

            • Hoksmur
              /#23170418 / +1

              MichaelBorisov,, а может статью о приёмах работы? Советы опытного специалиста многого стоят.

              • MichaelBorisov
                /#23174288

                Да я ничего не изобрёл, просто освоил приёмы, десятилетиями применявшиеся до этого в C-проектах. Многое почерпнул, изучая исходники ядра ReactOS, ещё LwIP. Это другая философия, другой мир, отличный от «плюсов».

                Статью писать… Спасибо за предолжение, можно попробовать. Подумаю, что можно в такую статью включить.

            • rsashka
              /#23170632

              Я пишу для микроконтроллеров на С++

              Да, без RTTI и динамического распределения памяти, но писать на чистом C выходит гораздо дольше, но самое главное ошибок получается больше, чем при ООП

              • MichaelBorisov
                /#23174308

                А ООП можно и на C. Структуры вместо классов, вместо виртуальных функций — явная реализация Vtable (хотя мне не приходилось пока прибегать к этому приёму).

            • 0xd34df00d
              /#23170696

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

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

              • MichaelBorisov
                /#23174340

                по опыту, в красоте и лаконичности код таки потеряет

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

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

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

                Особенно если вам нужна производительность, и в плюсах вам можно было бы использовать темплейты

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

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

        • Antervis
          /#23167582

          Тут есть несколько вариантов. А: язык продолжит постепенно развиваться, сохраняя обратную совместимость. Иными словами, лягушка в кипятке. Б: в какой-то момент появится форк плюсов без устаревшей хрени. Такой подход приведет к фрагментации языка (как это было с python 2 -> 3), чего хотелось бы избежать. В: введение эпох, т.е. способа смешивания разных диалектов языка в одной программе. Решение кажется классным, но разработчики компиляторов против перспективы экспоненциального роста поддерживаемых сущностей.

          Выбор, сами понимаете, далек от идеального

        • khim
          /#23167774

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

          Появление в C++11 rvalues и move-семантики привело к тому, что надёжный код без учёта лайфтаймов писать стало невероятно сложно, компилятор про них ничего не знает (в простых случаях может вывести, но в сложных случаях нужна помощь зала программиста), а если поменять логику и заставить программиста их указывать явно…

          Это называется Rust и уже давно сделано.

    • vkni
      /#23167346 / +1

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

      Часто есть и «прямые пути», например, Rust-C++ или Ocaml-C++ (в частной лавочке). Но что угодно с чем угодно — это через С.

      • creker
        /#23167396

        Ну тут в большей степени спасибо надо сказать C ABI. Наверное единственный надежный и совместимый интерфейс между языками. Из плюсов можно был бы и убрать совместимость с С на уровне кода, но оставить FFI для C ABI. Возможность именно вставить кусок С кода в плюсовый код это скорее бонус, чем необходимость, как мне кажется.

        • Ritan
          /#23167544

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

          • khim
            /#23167780

            Вопрос в том, кто будет делать заголовочные файлы библиотек для этого нового C+=2?
            Никто не будет. Нужно использовать подход Swift: информация об ABI просто зашивается в библиотеку, при сборке библиотека открывается и эта информация извлекается.

            На платформах вне macOS/iOS процесс идёт, но медленно.

          • creker
            /#23168576

            Именно так. С код компилируется в либу, к ней пишется header файл с прослойкой для FFI. Все работает и даже быстро. Собственно, даже вон C# и Go с этим справились. Первый даже заголовочные файлы не требует писать, все описывается атрибутами функций.

            А в идеале да, как выше пишут. Вся информация должна быть в самой библиотеке. Как вот упомянутый C#. Чтобы подключить либу надо просто, внезапно, подключить либу. Вся информация из нее будет извлечена автоматически. Даже комментарии можно приложить в файле рядышком. Хедер файлы это ошибка дизайна, которую С++, по крайней мере, намерен исправить.

            • mayorovp
              /#23169584

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

              На самом деле это недостаток.

              • creker
                /#23169612

                И в чем он заключается?

                • mayorovp
                  /#23170036

                  Приходится вручную конвертировать заголовочный файл в P/Invoke-декларации, по 10 раз перепроверяя всё.


                  Собственно, это проблема любого FFI в мире, где Си является стандартом.

                  • creker
                    /#23170198

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

  10. daniilk
    /#23167438

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

    Так что люблю/не люблю.

  11. ss-nopol
    /#23167518

    Достаточно посмотреть добрую половину библиотек Boost, куда попадают такие образчики.

    А ведь в Бусте качество кода выше среднего. Груз совместимости с С это гораздо меньший груз, чем груз совместимости с имеющимся языком шаблонов. Улучшать что-то в нём наверное бессмысленно, так что даже если когда-то будет создан новый язык шаблонов для С++, поддержку старого придётся всё равно оставить…

    Вот 0xd34df00d пишет, да я и до этого читал что в хаскеле язык шаблонов совпадает с самим хаскелем. Я не могу требовать наверное того же от шаблонов С++, но хотя бы читаемость кода шаблонов должна быть не намного хуже, чем читаемость самого С++ (без шаблонов).

    Кстати о читаемости кода. Ценность читаемости и простоты понимания кода трудно переоценить.

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

    • aamonster
      /#23168058

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

      • victor_1212
        /#23169764 / +1

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

  12. arthin
    /#23168246

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

    • aamonster
      /#23170142

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

    • victor_1212
      /#23170276

      правильно конечно, справедливо везде, где серьезный debugging необходим, jtag debugging особенно, часто gdb недостаточно

  13. AN3333
    /#23168362

    Сразу скажу то, что, пожалуй, и так все знают: нет такого понятия, как «самый лучший язык программирования».

    Есть такое понятие. И его обсасывают до бесконечности.

  14. victor_1212
    /#23169244

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

    например такой вопрос — как вы себе на настоящий момент представляете идеальную (хотя бы в теории) систему программирования — которая могла бы на следующие 50 лет заменить C, C++ (и их так сказать эко системы), каковы требования и пожелания?
    должен это быть один язык, или несколько специализированных по типам продуктов?
    подходящие кандидаты?
    ps
    предлагается на минуту типа поднять голову от земли, и подумать, что собственно нам надо для успешной работы, все-таки коллективный опыт солидный, а количество иногда переходит в качество

    • rsashka
      /#23169300

      Однозначно несколько языков.

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

    • victor_1212
      /#23169356

      немного подумав, мне бы хотелось иметь единый высокоуровневый язык описания hw архитектуры платформы (включая cpu, и пр), желательно интегрированный с языком программирования, imho не вижу почему это нельзя сделать, вместе с системой генерации на основе описания сначала компилятора, и toolchain, а затем всех необходимых images, и executables

    • khim
      /#23169390

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

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

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

      Такой подход к добру ну никак привести не может.

      В идеале языков должно быть много, но в реальности есть только один подходящий на замену: Rust. И как сделать так, чтобы его не смогли извратить, если народ массово с C и C++ будет убегать - вопрос непонятный пока.

    • creker
      /#23169604

      Конечно несколько. С++ как раз жертва желания засунуть все подряд в один язык, чтобы на нем можно было писать чуть ли не симуляцию вселенной. Не работает оно так. Поэтому у нас есть раст для ядра и драйверов и прочей системщины, чтобы заменить C/C++. Есть Go/Java/C# и прочие для бэкэнда. Javascript/Typescript для фронта.

      • Gryphon88
        /#23170110

        Знаете, когда я смотрю историю джаваскрипта, мне кажется, что где-то в кресле раскачивается полубухой Брендан Эйх и приговаривает «В 95м я не это имел в виду!». С ван Россумом, имхо, сходная фигня: человек писал «универсальный клей», а тут кааак попёрло.

        • creker
          /#23170202

          Че поделаешь. Ничего лучше для UI нет даже для десктоп приложений в плане мощи и универсальности HTML/CSS. Вот на бэкэнд его засунули непонятно зачем, это да. Сейчас вон flutter пытается мир захватить, может чего из него получится.

      • victor_1212
        /#23170216

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

        ps
        > С++ как раз жертва желания засунуть все подряд в один язык
        причем прямо с момента рождения

        • creker
          /#23171468

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

        • khim
          /#23171816

          Я пока писал простенькие поделки “для себя”, ничего в прод пока не запускал (он в Android сильно недавно появился просто: https://security.googleblog.com/2021/04/rust-in-android-platform.html, а я сейчас для Android код пишу по работе).

          Ощущения к borrow checker'у (основная “фишка” и основная сложность в практическом использовании) сильно меняются по мере освоения языка: вначале удивляешься на его “тупизну”, но через пару месяцев начинаешь понимать, что он довольно-таки часто ловит прямо на стадии компиляции ошибки, которые, в противном случае, ты бы ловил в дебаггере.

          Но, тем не менее, перед серьёзным использованием стоит всё-таки сделать какой-нибудь pet project, так как первоначальное привыкание — довольно-таки тяжело.

          Пока что ничего такого, что я мог бы сделать в C++ и хотел бы сделать в Rust, но не мог, я не обнаружил (а вот привести пример вещей которые я мог бы сделать в C++, но не хочу, чтобы их мог сделать кто-то ещё и потому рад, что их нету в Rust я могу легко), хотя некоторые вещи, которые мне иметь хотелось, потребовали залезания в Rustonomicon.

        • warlock13
          /#23171878

          Ничего серьёзного я на нём не писал, но всякие полезные и бесполезные вещи для себя и не только — да. Общался в соотв. каналах в дискорде, в том числе в канале с кодревью.

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

          • 0xd34df00d
            /#23173532

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

            Вот сидишь ты с этой агдой, сидишь, избегаешь даже postulate такого простого и известно консистентного с нужной мне теорией типов факта, как экстенсиональность функций, чтобы можно было добавить {-# OPTIONS --safe #-} и быть прям совсем-совсем уверенным в своём коде, а потом приходишь на хабр… Забавно, короче.

    • Gryphon88
      /#23170118

      Мне кажется, что мы наконец должны отказаться от сишной модели абстрактного вычислителя и при создании языка явно учитывать:
      1. Память не плоская: есть кэши, есть видеопамять, есть оперативка.
      2. Процессоров несколько, возможно, что и с разными задержками доступа к памяти
      3. Многопоточная программа это норма.
      Я вот хочу удобный язык с data flow парадигмой.

      • creker
        /#23170208

        Память не плоская: есть кэши, есть видеопамять, есть оперативка.

        Ой не стал бы я это в язык тащить. В железе все постоянно меняется. Сегодня у нас есть видеопамять и оперативка, а завтра у нас видеопамять примаплена в адресное пространство программы и кэш когерентна с процом. Это уже грядет с CXL. Сегодня у нас память рядом на шине, а завтра в соседней серверной стойке (это тоже скоро будет). Кэши тоже постоянно меняются, уровни добавляются, убираются, кэши разбиваются на блоки с разной скоростью доступа. Не говоря о всякой персистентной памяти.

        Многопоточная программа это норма.

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

        • Gryphon88
          /#23170218

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

          если делаешь многоядерную многопроцессорную железку, то она должна быть кэшкогерентной
          Нахрен. Вот просто нахрен. Кэшкогерентность это тоже часть легенды о плоской памяти. Вы листали даташит на интеловские процы, этот милый талмуд на 10к+ страниц? Из-за обилия хаускипинга процы получаются дорогие, сложные, и нельзя посчитать за сколько в тактах выполнится твоя программа. Если у нас будет язык, удобно позволяющий работу с независимыми данными, не так важно, SIMD или MIMD, то и у процов получится сбросить часть груза легаси.

          • creker
            /#23171534

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

            Кэшкогерентность это тоже часть легенды о плоской памяти

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

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

            • Gryphon88
              /#23172028

              Мы говорим о высокопроизводительном системном языке с прямым управлением памятью. Ну и рассматривать язык отдельно от его компилятора и, возможно, VM — неверно, так что когда я говорю «язык», я имею в виду всю связку.

              Заниматься когерентностью кешей на уровне софта?
              Я предлагаю её вообще отбросить на уровне L1 и L2 как минимум, просто вешать лок на данные: копирование, фрагментация и монопольное использование. Наиграется — вернёт в копию по индексу :)
              Каким образом языку об этом всем заботиться? Я даже не представляю, как это может выглядеть
              Есть какой-никакой компилятор под EPIC, под стековые машины, под мультиклет, есть, правда, пока в академии, автоматические балансировщики между CPU и GPU. Я бы не взялся, но задача не выглядит нерешаемой.
              При этом никто не мешает затачиваться под кэши в текущих языках.
              Это на пальцах прикидывать, с профилировщиком и дебаггером сидеть, потому как нет такой сущности на уровне языка (собсно, и ключевое слово register в С. Не, через асм можно в кеш влезть, но это чудовищно геморройно.
              Производительность умрет, мы наоборот пытаемся как можно больше в железо убрать, потому что на текущих скоростях никакой софт не справляется.
              Наоборот, железо не догружается и простаивает: программы по умолчанию однопоточны и при этом упираемся в контролер памяти. Т.е. сидит одинокое ядро и ждёт данные.

              • creker
                /#23172110

                Мы говорим о высокопроизводительном системном языке с прямым управлением памятью. Ну и рассматривать язык отдельно от его компилятора и, возможно, VM — неверно, так что когда я говорю «язык», я имею в виду всю связку.

                Ну в таком случае все и так есть. Архитектурные оптимизации компиляторы умеют.

                Я предлагаю её вообще отбросить на уровне L1 и L2 как минимум, просто вешать лок на данные: копирование, фрагментация и монопольное использование. Наиграется — вернёт в копию по индексу :)

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

                Это на пальцах прикидывать, с профилировщиком и дебаггером сидеть

                Тем не менее все оптимизируется и работает.

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

                Если бы все было так просто. Нет, железо физически неспособно прокачать 100гбит, 400гбит. На такие скорости способны только ASIC и прочие FPGA. Ничего другого в свитчах такого класса не увидишь. Поэтому люди начали придумывать smartnic, DPU. Все облачные провайдеры сидят на этих штуках.

                • Gryphon88
                  /#23172358

                  Производители железа и интерконнектов с этим не согласны. Они для высокой производительности хотят полную когерентность в железе. При чем даже между разными устройствами через CXL протокол. Я с ними согласен.
                  Я догадываюсь, что по любой специальности можно попасть в поисковый пузырь, а потом начать форумный бой на ровном месте, но таки спрошу: вот зачем это дорогая и неуправляемая программистом процедура? Насколько я понимаю, это потому, что многопоточные приложения писать достаточно сложно, им приходится память вручную нарезать. При этом в хаскеле параллелизм значительно проще, чем в openmp — из-за ФП, который на параллелизм лучше ложится. Да, ценой перерасхода памяти, но это приемлемая жертва.

                  • creker
                    /#23172542

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

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

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

                    • Gryphon88
                      /#23172698

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

                      Это код хаскела весь такой красивый функциональный, а внутри может твориться черти что.
                      Мне этого вполне достаточно, тем более, что внутри gcc тоже адЪ и Израиль :)

                      • creker
                        /#23173286

                        Я просто к тому, что очень мало кода, который реально параллельный и независимый. Намного больше кода обмазано мьютексами и атомиками. И там заниматься еще синхронизацией кэшей — ну нафиг. Поэтому у нас вот и получается. Для общей логики сложные процы с кэшами и всем на свете, а для скорости ASIC и FPGA, где вообще все другое. И сейчас еще тренд делать решения вроде DPU и smartnic, когда на борту есть и то, и то другое, для контрол и дата плейна соответственно.

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

                        • 0xd34df00d
                          /#23173552

                          И там заниматься еще синхронизацией кэшей — ну нафиг.

                          Но relaxed-семантика и адовое переупорядочивание операций а-ля alpha нередко вполне себе достаточны для корректности, и более сильные (и, вероятно, более тормозящие) гарантии x86 не всегда нужны.


                          Я думал, разговор вообще именно про это.

                          • creker
                            /#23173800

                            Не, если речь про расслабить модель памяти, то я только за. Я думаю арм успеешен не в последнюю очередь именно благодаря этому. Это и на код практически никак не влияет. А вот убирать когерентность кэшей как тут предлагали — нафиг.

                        • khim
                          /#23173588

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

                          Есть у кого-то реальные исследования? Я боюсь, что в разных областях ситуация будет сильно разной.

                          И даже дело не только в HPC: игры сейчас содержат несколько тредов, которые синхронизируются буквально раз в кадр, в остальное время они считают вообще разные вещи, скажем физику разлетающихся обломков на экране и пресловутый “геймплей”, который будет ровно тем же, даже если взрывы на экране вообще замрут.

                          Программить это чудо было такой пыткой, что проект закопали.

                          Проект закопали потому что было мало эксклюзивов, а на кроссплатформенных играх все эти дополнительные ядра простаивали, а в результате картинка на XBox 360 была красивее.

                          Экслюцивы на PS3 были отличными, только их мало было.

                          • creker
                            /#23173830

                            Это чисто умозрительное заключение, но думаю оно вполне очевидно. Большинство кода пишется вообще однопоточное. Маленькая часть кода многопоточная и еще меньшая часть многотопочная в смысле shared nothing, когда потокам не нужно между собой общаться. Понятное дело, что HPC и игры это последний случай, но это маленькая часть всего софта, а язык у нас все таки общего назначения. Надо язык под эти конкретные применения — вот там можно затачиваться на какие угодно кэши.

                            Проект закопали потому что было мало эксклюзивов, а на кроссплатформенных играх все эти дополнительные ядра простаивали, а в результате картинка на XBox 360 была красивее.

                            Не, там было все сложнее. Может в начале целл и был причиной, но потом с ним освоились и уперлись в другие ограничения архитектуры пс3 — его убогий гпу и архитектуру памяти. У xbox 360 был чуть ли не GPGPU, UMA и EDRAM, который творил чудеса.

                            А закопали проект вообще все. И сони, и ibm. Проц реально было очень сложно использовать. Он был конечно быстрый, но оно того не стоило. Отсутствие предсказателя переходов и внеочередного исполнения тоже не помогало, но этим страдал и xbox 360.

                        • Gryphon88
                          /#23178288

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

                          • creker
                            /#23178338

                            Кэши у GPU есть — тот же RDNA имеет L1 и L2. Но это все же специализированные процессоры, на которых по-другому просто и не пишут.

                            • Gryphon88
                              /#23178356

                              Хорошо, скажем иначе: кэши у них не обязательно есть :) Боль вызывает то, что язык не поддерживает по умолчанию многопоточность, и приходится вместо
                              a=b+c
                              писать
                              for (size_t k = 0; k < n ; k++){
                              a[k]=b[k]+c[k]
                              }

                              • creker
                                /#23178402

                                Ну так то и CPU без кешей жить может, но все современные их используют. Что нвидия, что амд, у GPU кэши многоуровневые уже хрен знает с каких времен. Даже мобилки я вот смотрю тоже по несколько уровней кэшей имеют. Тут скорее вопрос, а что у вас за хитрый GPU такой без кэшей?

                    • 0xd34df00d
                      /#23173542

                      Что до ФП, хаскел имеет параллельный GC. Ему без примитивов синхронизации и атомиков никуда, а это все полагается на когерентность кэшей как раз, чтобы оно работало быстро и корректно.

                      Зачем? Никто не мешает поделить nursing area (это область для gen0) на чанки, чтобы каждому ядру достался свой чанк, и собирать внутри каждого чанка мусор последовательно. В gen1, да, так уже не получится, но gen1 — это и так обход всего хипа.

                      • creker
                        /#23173900

                        Хм, а как строить граф живых объектов полностью параллельно? В любом случае структура данных будет общая. Ведь между чанками все равно будут зависимости. Судя по коду GC, синхронизация там повсюду какая-то происходит. Тоже самое в аллокаторе. Даже глобальный лок один используется на весь storage manager, в том числе манипуляции с nurseries. И это даже с учетом, что у хаскеля есть какие-то capabilities, которые позволяют использовать независимые друг от друга nurseries для каждого из них.

                        • 0xd34df00d
                          /#23173986

                          Хм, а как строить граф живых объектов полностью параллельно?

                          А вы вспомните, что язык-то иммутабельный (я тут немного вру, но это пока неважно), поэтому строить граф очень легко, ибо он — DAG. Вы просто берёте и начинаете транзитивно копировать gen0 в gen1. В gen1 ссылок на объекты из gen0 заведомо нет (объекты там были созданы раньше, поэтому указывать на то, что появилось позже, они физически не могут), поэтому что не скопировалось, то просто убиваем.


                          Поэтому много мелкоживущего мусора (который не успевает уйти в gen1) — это даже хорошо.


                          И это даже с учетом, что у хаскеля есть какие-то capabilities, которые позволяют использовать независимые друг от друга nurseries для каждого из них.

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

        • victor_1212
          /#23170238

          вот, таки начинаем к интересным вопросам подходить, imho по мере усложнения все хуже получается, если продолжать разделять на hw-fw-sw, типа делаешь hw описание контроллера прерываний, тут бы и логику обработки прерываний добавить уместно, а не тащить это отдельно в sw компилятор, такие мечты примерно, а что в железе быстрее меняется это ведь от архитектуры, типа mb обеспечивает интерфейс картам, по-другому не умеем, и вообще шина есть еще один bottleneck, так примерно

          ps
          >В 70х никто о кэшах не думал, доступ к памяти и к регистру был примерно одинаков по цене в тактах, а сейчас на системном уровне хорошо бы учитывать в явном виде.

          правильно пишите, только в 70x тоже думали, и еще как, лично присутствовал,
          пух и перья летели, когда cache memory для виртуальной памяти обсуждался :)

          • creker
            /#23171564

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

            • victor_1212
              /#23172810

              вполне понимаю вас, только эта самая абстрактная машина языка не сильно движется последнее время, если не сказать сильнее, вопрос сколько это может продолжаться?
              ps
              imho, возможности технологии уже другие

              • creker
                /#23173302

                А надо ей двигаться? Я вот честно не вижу причин. Железо развивается, код под него пишется. Т.е. прогресс идет и никем не блокируется. Вот у нас появился fungible DPU, где как раз data flow архитектура, строится граф обработки данных. И все это на mips и С библиотеках. И ничего, могут себе позволить сотни гигабит ворочить этим всем благодаря тому, что С только управляет, а реальная обработка делается специальными фиксированными блоками логики. Т.е. имеем ту самую синергию железа и софта. И никакой x86 или arm сервак с сотней ядер такое не может.

                • victor_1212
                  /#23173518

                  если ради движения только, то однозначно нет, вопрос есть ли в этом потребность?
                  вашу точку зрения кажется понял, «потребности нет»,
                  теперь про fungible, это интересная архитектура, для data centers эффективная, тогда если принять будущее = cloud computing, то получается один ответ, но если к примеру будущее >> cloud computing, то imho другой, как будет на самом деле конечно никто не знает, но я бы поставил на «будущее >> cloud»

                  • creker
                    /#23174164

                    Пока да, все будущее у нас диктуют облачные провайдеры и HPC. Они пишут стандарты и под них делается передовое железо. Пока что тренд на дезагрегацию всего и вся, а соединялось бы все ethernet фабрикой. И видно, что обычные процессоры не справляются со скоростями этих фабрик, поэтому имеем повсюду ASIC и FPGA. Поэтому я пока не вижу потребности новых языков. Процессоры общего назначения у нас и так не тянут, поэтому на них перекладываются задачи только управлять этими ASIC и FPGA, а на это много ресурсов не надо. Где надо много вычислений, то делается это опять же на них или GPU, TPU, DPU. Т.е. все вычисления уходят с центральных процессоров. Хочется распределенную файловую систему — на DPU ее. Хочешь базу — на DPU или computational storage. На cpu остается код общего назначения, для которого текущие языки вполне себе работают.

        • cepera_ang
          /#23170422

          Ой не стал бы я это в язык тащить.

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


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


          Будущее за end-2-end оптимизацией систем, от задачи к железу. Все интересные задачи, требующие сложных решений всё равно без этого не решаются (сюда я включаю всякие нейронки, графику/видео, гигантские графы и прочую геномику), обычный десктопные/бизнес софт и не требует таких извращений особо. Что мы и наблюдаем в реальности — все гиганты создают свои стэки, начиная с процессоров/специализированных ускорителей, продолжая компиляторами и заканчивая самой задачей. Как пример какой-нибудь Halide/TVM/Glow.

          • creker
            /#23171590

            Правильно, и задача этого всего лежит на компиляторе. Язык тут никаким местом. Собственно, компиляторы это давно умеют, но мало кто компилирует под конкретные платформы, т.к. людям важнее переносимость и универсальность кода. Разве что в рантайме выбирать оптимизированные ветки кода. JIT проще в этом плане.

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

            • victor_1212
              /#23173358

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

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

              • creker
                /#23173504

                что именно может/должен делать компилятор

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

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


                Не понимаю я, зачем стандарт языка должен это все учитывать. Язык, если он общего назначения, пусть работает на своей абстрактной машине с линейным адресным пространством, многотопочностью, мгновенным доступом к памяти, кэш когерентностью будто кэшей и нет вообще и т.д. и т.п. А дальше пусть компилятор разбирается, как это все перенести на целевую платформу наиболее эффективно. Собственно, сейчас именно так все и работает. И чего-то принципиальной проблемы в этом не вижу. Где совсем плохо, софтовые реализации заменяются на аппаратные. Как вот AES. У нас нынче гетерогенные вычисления в моде все таки.

                добавил бы усложняется и кастомизируется

                Усложняется конечно, поэтому сейчас тренд в удалении абстракций в софте и предоставлении большего доступа к железу. Как вот DX12, Vulkan, DPDK и прочие. Язык тут никому не мешает использовать железо на полную катушку.

                • victor_1212
                  /#23173548

                  спасибо, думаю что вас понял вполне

          • victor_1212
            /#23172760

            >cepera_ang
            именно, imho рано или поздно это будет сделано, вопрос только когда и кем, т.е. архитектура hw и sw будут объединены, можно спросить знакомых из huawei, что они думают :)

      • victor_1212
        /#23172906

        >Gryphon88

        >1. Память не плоская: есть кэши, есть видеопамять, есть оперативка.
        >2. Процессоров несколько, возможно, что и с разными задержками доступа к памяти
        >3. Многопоточная программа это норма.

        4. явное указание под управлением какого image (os) процессор работает, типа создания «объект linuxXYZ» и его клонирования, перезагрузки и пр.,
        5. для критических приложений также синхронизации результатов (контрольные точки процессоров), голосования (типа 2 из 3), reset/restart при необходимости любого hw или sw объекта

        • Gryphon88
          /#23172972

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

          • victor_1212
            /#23173084

            не совсем понимаю «осеспецифичность» — требуется таки указать какой image на target где именно работает, и в этом случае получается часть описания проекта, а не языка, или не так?
            ps
            4, 5 все для target конечно

          • victor_1212
            /#23173304

            что-то есть в вашем вопросе, что беспокоит, типа надо подумать

            • Gryphon88
              /#23178284

              Возможно, я не понял вашего предложения, поясните, пожалуйста.

  15. warlock13
    /#23169368

    Например, если нужно получить значение элемента массива, имея два смещения, одно из которых может быть отрицательным числом, то при программировании на C можно воспользоваться такой конструкцией: arr[off1 + off2].

    Хорошо вам. Я на такое не отважусь, ибо не возмусь предсказать достаточно уверенно, устроит ли меня как программиста результат такой конструкции. Только где-то с 95% вероятностью, и после не такого уж простого анализа. В Rust проще, но, видимо, не вам: вы так лихо и не задумываясь написали все эти as… Надеюсь вы написали в документации к вашему коду, каким условиям должны удовлетворять offset1 и offset2 (предположим, что это входные параметры публичной функции), чтобы гарантированно не получить панику?

    • Cerberuser
      /#23170374

      И при этом, заметьте, всё-таки панику, а не доступ за границей выделенной памяти. Так что что-то в Rust и в этом смысле проще.

      • khim
        /#23171864

        В Rust есть две гениальные вещи, которые тесно связаны между собой: borrow checker и unsafe.

        Причём unsafe, на самом деле, гениальнее borrow checker'а.

        Да, оно родилось потому, что некоторые вещи нельзя сделать без того, чтобы сказать borrow checker'у “постой в сторонке, а я пока поработаю” (без чего даже двусвязный список не написать), но сама идея в одном языке иметь и “безопасную” часть на которой пишется 90% (а то и 99%) кода и “небезопасную” — это гениально.

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

        В языках, где unsafe часть это другой язык (C#, Java, JavaScript или даже Python) часто такое вытворяют, чтобы не писать две строки unsafe кода, что волосы дыбом встают.

        • creker
          /#23172140

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

          • khim
            /#23172492

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

            Последний раз, когда я с ним общался нужно выло модуль на C делать. Но, может быть, что-то и изменилось. Давайте рассмотрим простейший пример: в новом CPU появилась новая ассемблерная инструкция ADDPD и я хочу с её помощью сложить два массива (понятно, что реально сейчас появляются более экзотические инструкции, но идея та же). Как мне это сделать? В Rust есть Asm (хотя пока не в стабильной ветке, но над этим работают): https://doc.rust-lang.org/unstable-book/library-features/asm.html

            Что C# предлагает?

            • creker
              /#23172554

              До такой степени опускаться я не думал, но быстрый гуглеж дает решение даже для этого stackoverflow.com/questions/18836120/using-c-inline-assembly-in-c-sharp Я думал речь о ручном управлении память и взаимодействии с unmanaged окружение. В шарпах для этого тоже есть unsafe с кучей инструментов.

  16. game-lover
    /#23171418

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

    В GCC/G++ вы можете через -std выбрать необходимую редакция.
    -std=с++<необходимая редакция>

    • kovserg
      /#23171860

      А как по исходному коду определить какая редакция ему нужна?

      • IGR2014
        /#23172774

        А как это сделать в чистом C? Вот и ответ)

  17. buratino
    /#23174386

    Добавлю свои полкопейки.

    Почти ничего не сказано про отладку и отладчик. При работе с Си (или Си++ без теплейтов, перегрузки операций и прочих премудростей) в отладчике все просто и понятно. И выполнение всегда начинается с main(), и и до вызова main() зависнуть или трапнуться или там улететь в вектор прерывания с Fault в названии невозможно. В случае С++ подобные приключения случаются легко и непринуждённо, что особенно доставляет при разработке микроконтроллеров, которые управляют даже не обязательно ядреными реакторами, а любыми устройствами, которые работают на волшебном дыме, после выхода которого устройства перестают работать.

  18. saipr
    /#23176406 / -2

    В итоге хочу сказать, что люблю язык C

    Я тоже люблю C и, прочитав статью, вспомнил полемику в "Введение в Си. Послание из прошлого столетия"