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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Появилась в С11 вместе с основными примитивами. Даже атомики наконец добавили.
Если мы хотим, чтобы С был языком низкого уровня и переносимым ассемблером
Вы, может быть, этого и хотите, но не является 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).
Запустил в VS, выставил уровень оптимизации O2, получил результат 2 2. Что я упустил?

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

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

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

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

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

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

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

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


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 этот случай не ловит.

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

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

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

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

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

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

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.

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


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

Если у clang'а оторвать знание realloc, он будет вести себя как gcc: https://godbolt.org/z/zGTTWWTKh

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

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

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

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

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

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

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

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

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

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

То есть проверить 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 не бывает, если оно туда пролезло, то это ошибка компилятора, которую нужно исправить).

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

Как вы вообще можете представить себе реализацию гарантии что программы с 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++ минное поле — вся программа и разработчикам языка, похоже, вообще наплевать как и что программисты должны делать, чтобы она работала.

Вот только ключевое решение там нифига не разрешает компиляторам делать то, что они делают с этой программой. Оно звучит, извините, вот как:
нет, почитайте до конца. Решение звучит вот как:
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 начнет работать через ремаппинг страниц, и может случиться такая штука, когда после него указатель с тем же битовым представлением начнет маппиться в другой участок физической памяти, даже если физически данные никто не двигал.

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

Прочитать до конца что, извините? Решение, которое не вступило в силу? Да…, довольно-таки страшненький законопроект. Слава богу, что это — ещё не закон. Посмотрим что они там понапишут в проектах изменений 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 начнет работать через ремаппинг страниц, и может случиться такая штука, когда после него указатель с тем же битовым представлением начнет маппиться в другой участок физической памяти, даже если физически данные никто не двигал.

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

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

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, то варианты у тебя весьма ограничены.

Вот только решает это не компилятор, а операционная система, извините.
по поводу «не обязан» — это решает не компилятор и не ОС, а контракт метода 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…
Кроме того clang ломает программы с UB
вообще-то изменение const поля это UB

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

по поводу «не обязан» — это решает не компилятор и не ОС, а контракт метода 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;

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

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

Решает всё-таки 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)
В стандарте wording такой:
В главе про UB
The behavior is undefined in the following circumstances:

— The value of a pointer that refers to space deallocated by a call to the free or realloc function is used (7.20.3).
Его в принципе можно интерпретировать и тем и другим способом.

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

Так или иначе, не думайте что я не могу понять вашу попаболь. Просто лично мне кажется что разработчикам компиляторов чуть-чуть виднее. Вот вы жалуетесь на пару кейсов, а они такое каждый день обсуждают. И имеют замеры влияния всех этих «ломающих» оптимизаций на перф.
Круче всех поступает MSVC, этот вообще, блин, лапочка. Он умудряется что-то найти и “наоптимизировать” вот в такой вот программе: godbolt.org/z/cYr3he4xz
а вот это точно бага, надо репортить.
Я вас уверяю: если вы так поставите вопрос, то хорошо если несколько процентов наберёте тех, кто правильно ответит, не заклядывая в стандарт.
я понимаю когда вы хаите расхождения в стандарте и компиляторах, это логично. А вот когда они в чем-то согласны, виноват не кто иной как пользователь. Иррелевантно тому, каким он там себе представляет поведение на конкретной архитектуре. Вот взять например ваш кейс с изменением const int'а. Он по стандарту имеет право лежать в статической памяти, мутабельный доступ к которой на многих архитектурах выльется во вполне реальный access violation. И с точки зрения работоспособности программы такой краш не лучше додумки компилятора.

Вот так выглядит дока на 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++ стало невозможно…саботажники, пожалуй, самое правильное слово.

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf
насколько я помню в конструкции int x; int* ptr = &x + 1; ptr это даже не one past the end указатель, т.к. x это не массив. Соответственно его даже вычислять UB, не то что сравнивать/разыменовывать.

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

Я называю саботажем то, что называется саботажем. Если вы считаете, что единственный способ ускорить программу — это заставить человека потратить совершенно дикое количество времени на борьбу с компилятором для того, чтобы получить ускорение на 5-10%, то у меня для вас плохие новости: нет, это ни разу не так.

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

Посмотрите на проекты, разработчики который всерьёз озабочены эффективностью. Ядро Linux, PostgreSQL, Chrome (Blink и V8) и кучу всего ещё. Да они через одного собираются со всеми возможными ключами выключения оптимизации-через-UB, какие только могут найти!

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

Программу, которая ускоряется от подобной идиотской трактовки realloc'а - в студию. Покажите мне хотя бы одного такого бенефециара, блин.

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

Не покажите. Нету их. Разработчиков компиляторов волнует две вещи:

  1. Смогут ли они показать ускорение на бенчмарках.

  2. Смогут ли они протащить изменение через комитет по стандартизации.

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

А когда они пытаются сделать что-то, что людям реально нужно - что им говорят? Правильно: ваше изменение бенчмарки не ускоряет, а потому — идите… туда, где солнце не светит.

Вот типичный пример: https://reviews.llvm.org/D68414

Оно решает проблему с туплами (clang, как известно творит полное непотребство, если их возращать из функций: https://reviews.llvm.org/D68414 ), но поскольку бенчмарки это не ускоряет и в реальных программах эта конструкция не встречается, то нам пофиг.

Ну да, разумеется в реальных программах эта конструкция не всречается! Когда ты пытаешься использовать std::tuple для возврата значений и выясняешь, что у тебя скорость программы проседает раза в полтора — ты переходишь на указатели и всё. А какие варианты?

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

Ну как вам сказать, чтобы не обидеть. Нет, не оценивали. Вообще, никак. То есть да, тот пропозал, который сломал нафиг realloc содержит массу бенчмарков, исследований и рассуждений. Вот только примеры realloc из реальных программ там даже не фигурируют. Они там пытаются перенести аллокации на стек. То есть код типа такого:

int* p=(int*)malloc(10*sizeof(int));
...
free(p);

превратить в такой:

int p[10];
...

А realloc они там ломают так, походя, за компанию, потому что могут.

Ну да, если вы в одной функции сначала выделяете память через malloc, потом через realloc, а подом делаете free — они могут чего-то там ускорить. В теории, так как в конечную версию эта оптимизация не попала. П|опало только изменение, которое ломает пользовательские программы и больше ничего. Но вы вообще видели хоть одну программу, которая так делала бы?

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

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

насколько я помню в конструкции int x; int* ptr = &x + 1; ptr это даже не one past the end указатель, т.к. x это не массив. Соответственно его даже вычислять UB, не то что сравнивать/разыменовывать.

Там нет никакой подобной конструкции. One past the end возник в качестве обоснования того безумия, что они с realloc'ом творят. Когда спросил “какого фига? что вам вообще даёт право считать, что после p == q указатели могут быть разными?”, напомнил, что никаких pointer provenance в стандарте нет и процитировал:

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.

На что получил ответ: “ну вот жи! последний вариант! там как раз один указатель валидный, другой не валидный — значит это оно и есть”.

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

Но, блинский же фиг, даже если вы его поменяете, добавите в C23 или в C25, с какого перепугу это должно повлиять на валидность кода, написанного для существующих стандартов?

Так что этот код, скорее всего, валиден и в C99, и в C11, и в C18 — но там действительно разные пункты стандарта противоречат друг другу, так как одно место в C99 поправили, другое нет.

А C89 я взял просто потому что там вообще нет того места, на которое они опираются в своих рассуждениях: уничтожения старого объекта и создания нового, на том же месте и с тем же адресом. Там речь идёт о возможном перемещении объекта. Которое может произвойти или не произойти, и уж если оно не произошло, то вообще непоятно откуда там UB может взяться.

Посмотрите на проекты, разработчики который всерьёз озабочены эффективностью. Ядро Linux, PostgreSQL, Chrome (Blink и V8) и кучу всего ещё. Да они через одного собираются со всеми возможными ключами выключения оптимизации-через-UB, какие только могут найти!
потому что давным давно там была написана куча чересчур хитрых хаков через UB которые никто не хочет фиксить? Так то будь хром процентов на 20 быстрее весь мир вздохнул бы спокойнее. Например довольно тяжело писать без UB код на интринсиках. Вот только не надо утверждать что нельзя накрыть нужными флагами соответствующие TU, а не весь проект
Программу, которая ускоряется от подобной идиотской трактовки realloc'а — в студию.

Не покажите. Нету их. Разработчиков компиляторов волнует две вещи:

Смогут ли они показать ускорение на бенчмарках.
вы уж определитесь, могут они показать ускорение или нет. Потому что компилятор не знает, где ему нужно компилировать бенчмарк, а где продовую тулзу. Соответственно одни и те же оптимизации он будет пытаться применять и там и там. Например производительность корутинных генераторов сильно зависит от оптимизации аллокаций.
А realloc они там ломают так, походя, за компанию, потому что могут.
предполагая парадоксальным предположение что в каком-то левом участке кода кто-то не нагенерил (да хоть через rand()) указатель, который нечаянно совпадет по значению с результатом новой аллокации? Вы действительно хотите доказать что это не логично?
Но вы вообще видели хоть одну программу, которая так делала бы?
вообще я натыкался на оптимизированные аллокации. Редко правда происходят, мало UB сломали вестимо...
Там нет никакой подобной конструкции
вы сами дали ссылку на документ где фигурирует вот такой код:
тык
#include<stdio.h>
#include<string.h>

int y=2, x=1;
int  main() {
  int*p = &x + 1;
  int*q = &y;
  printf("Addresses: p=%p q=%p\n" ,(void*)p,(void*)q);
  if(memcmp(&p, &q,sizeof(p)) == 0) {
    *p = 11;   // does  this  have  undefined  behaviour?
    printf("x=%d y=%d *p=%d *q=%d\n",x,y,*p,*q);
  }
}

И я лишь объяснил почему там UB
Но, блинский же фиг, даже если вы его поменяете, добавите в C23 или в C25, с какого перепугу это должно повлиять на валидность кода, написанного для существующих стандартов?
в этом я с вами согласен.

Так то будь хром процентов на 20 быстреемедленнее весь мир вздохнул бы спокойнее.

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

потому что давным давно там была написана куча чересчур хитрых хаков через UB которые никто не хочет фиксить?

Именно! Потому что какой-нибудь NaN-boxing позволяет выиграть процентов 20 производительсноти. Куда больше чем все оптимизации, основанные на strict aliasing вместе взятые. А использовать NaN-boxing в коде, который компилируется вы режиме strict aliasing банально неудобно. Время на написание того же гкода возрастает в несколько раз.

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

Вот только не надо утверждать что нельзя накрыть нужными флагами соответствующие TU, а не весь проект

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

Например производительность корутинных генераторов сильно зависит от оптимизации аллокаций.

Ну да, повезло вам. Радуйтесь, что какая-то оптимизация всё-таки попала в один из бенчмарков и её приняли. А вот зато поддержку меток в качестве аргументов в ассемблерные вставки в Clang динамили несколько лет, пока Линус не выпилил режим, где она не требуется.

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

Нет.

Вы действительно хотите доказать что это не логично?

Это нелогично и если я всуну тада rand(), то clang всё сделает правильно: https://godbolt.org/z/1rqEche8Y

В том-то и дело, что такой программе:

int main() {
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    p = (int*)rand();
    if (p == q) {
        *p = 1;
        *q = 2;
        printf("%d %d\n", *p, *q);
    }
}

clang допускает, что указатель, который возвращается из rand(), то он может, случайно, совпасть с стем, что возращается из realloc — и в этом случае он печатает 2, 2, как и должен.

А вот тут:

int main() {
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    if (p == q) {
        *p = 1;
        *q = 2;
        printf("%d %d\n", *p, *q);
    }
}

Это уже невозможно. Потому что функция realloc специально помечена особо. С явной целью считать p и q в подобной ситуации разными.

Это, на минуточку, для функции у которой прямо в спецификации написано:

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.

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

Понимаете, да? В описании функции явно написано, что она может вернуть тот же указатель, какой был на входе, мы помечаем эти два указателя, как “непересекающиеся”, игнорируем проверку на равенство, когда нас спрашивают “какого фига” указываем на pointer provenance (непринятый драфт, который ещё и не известно будет ли вообще принят), на возражение “но в текущем стандарте этого ужаса ещё нет” — изобретаем отмазку с past-by-one указателем, а когда поднимается C89 стандарт — то объяснется, что K&R устарел и вам придётся жрать что дают пользоваться тем, что саботажники наработали.

Вы уж извините, но саботажники — это ещё мягко.

вообще я натыкался на оптимизированные аллокации.

С realloc'ом? Где? Как покажите! Хочу! Ещё раз повторяю: у них там сломан, целенаправленно сломан, конкретно realloc!

Сюда гляньте: https://godbolt.org/z/qPh87aceT

Я говорю: прекратить ломать realloc — и, о чудо, clang берёт под козырёк и прекращает его ломать.

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

А потому что могём!

P.S. Если вы можете показать где подобная оптимизация realloc что-то там ускоряет - будет интересно посмотреть.

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

(непринятый драфт, который ещё и не известно будет ли вообще принят)
еще раз: value указателя один, лайфтайм объекта за ним другой. Я уже объяснял почему формально это разные указатели и цитировал ту часть стандарта си, которая это указывает (хоть и неоднозначно). То, что это поведение перекочевало в режим компиляции С89 меня тоже не радует.
С realloc'ом? Где? Как покажите! Хочу! Ещё раз повторяю: у них там сломан, целенаправленно сломан, конкретно realloc!
затронут не только realloc, а вообще аллоцирующие функции.
P.S. Если вы можете показать где подобная оптимизация realloc что-то там ускоряет — будет интересно посмотреть.
конкретно про realloc я не говорил. Про оптимизации пар аллокаций malloc+free я уже упоминал.

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

конкретно про realloc я не говорил

Зато я говорил.

Про оптимизации пар аллокаций malloc+free я уже упоминал.

А они тут причём? GCC про них тоже знает и тоже умеет оптимизировать. realloc для этого ломать ну вот совершенно необязательно.

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

Нет. Это вы игнорируете тот факт, что противоположная интерпретация ни разу не менее натянутая.

Давайте перечислим факты, которые мы обсуждаем.

  1. В компиляторе clang имеется “оптимизация”, которая добавлена туда специально, чтобы ломать программы на C.

  2. Данная “оптимизация” не ускоряет ни одного известного бенчмарка.

  3. Данная “оптимизация” не ускоряет ни одной известной программы.

  4. Никто до сих пор не привёл ни одной, даже синтетической программы, которую данная “оптимизация” ускоряет.

  5. Валидность данной “оптимизация” опирается на противоречия в трактовке разных пунктов рсуществующих стандартов и она очевидно невалидна для C89.

  6. Разработчики компиляторов отказываются убрать эту “оптимизация” и пункты 2 и 3 их не волнуют.

С чем конкретно из всего этого набора спорите?

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

Хотя, в общем, пунктов #1, #2 и #3 уже вполне достаточно, чтобы называть разработчиков Clang саботажниками.

А они тут причём? GCC про них тоже знает и тоже умеет оптимизировать. realloc для этого ломать ну вот совершенно необязательно.
вообще скорее обязательно. В том смысле, что reallloc может вести себя и как malloc, и как free
С чем конкретно из всего этого набора спорите?
у вас пункты #2, #3, #4 являются не фактами а предположениями

В том смысле, что reallloc может вести себя и как malloc, и как free

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

Ну нужно malloc/free изводить ломать realloc не нужно, вот вам реальный живой пример: https://godbolt.org/z/chhdv5xns

И даже в clang просьба оставить realloc в покое на эти оптимизации не влияет: https://godbolt.org/z/WW133fYfK

у вас пункты #2, #3, #4 являются не фактами а предположениями

Нет, они в настоящее время являются фактами. Там не зря слово “известных” написано.

Это же вы утверждали, что саботажники чего-то там оценивали, не я.

Ну и хде? То, что они “оценили” и “наоптимизировали”?

ускорение на 5-10%, то у меня для вас плохие новости: нет, это ни разу не так.

Если вы считаете, что C++ оптимизатор ускоряет максимум на 5-10%, то у меня для вас плохие новости: нет, это не так. Возьмите библиотеку Eigen и напишите простенькую программу для решение системы линейных уравнений. И сравните производительность при -O0 и -O3. Разница будет в разы!, а не на 5-10%.

Разница будет в разы!, а не на 5-10%.

Я что-то запутался: вы что с чем сравнивать собрались-то? У вас тут четыре объекта: Eigen, простенькая программа, -O0, -O3…

Кроме того 5-10% — это оценка не всех оптимизаций вообще, а оптимизаций, которые опираются на неочевидные, для программистов, UB.

То, что использовать неинициализированные переменные не стоит — понимают все (ну или почти все) программисты. То, что, если i не инициализирована, то i || !i может, внезапно, оказаться ложным плонимают уже не все (всё-таки законы логики никто не отменял), но да фиг с ним. на практике это всё равно не очень хорошая идея.

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

И вот тут — я нифига не уверен, что вы можете высосать из компилятора больше 5-10%.

Собственно тот факт, что в 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 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.

Вместо этого саботажники занялись строго противоположным: не providing a definition of the officially undefined behavior (там где это разумно и имеет смысл), а попытками расширить это понятие и превратить валидные программы в невалидные.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вы уж меня извините, но ни-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 не слишком популярен и его разрабатывают программисты для программистов - всё хорошо, а вот что будет когда туда компиляторщики придут - пока неясно.

Вы уж меня извините, но ни -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'ам "на авось" там не место. А в прикладной логике супер-оптимизации не нужны.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это неверно. Способы корректно скомпилировать C89 код существуют. Насчёт C99 и C11 точно не знаю, мне как то они в полной реализации не нужны, но думаю тоже существуют (-O0). Если же вам нужно, чтобы один и тот же компилятор с одним и тем же набором флагов корректно компилировал и C89 и C11, да при этом ещё и топовые оптимизации проводил - ну, видимо такого нет. Но это требование намного более сильное, чем просто "написать корректную программу на Си". А переход на другой язык вообще никак не влияет на поддержку новых стандартов Си компиляторами.

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

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

Подходящий вс же будет тот, который лучше способствует решению поставленной задачи. А вы задачу (утилитарную, а не идеологическую) не сформулировали. Где-то может и Rust лучше. Лично мне для системных и прикладных, но критичных к производительности задач лучший кандидат - Си (кроме тех ныне редких мест где ассемблер), по совокупности причин. Теоретические проблемы с UB я обхожу как описано выше, в итоге ни разу, кроме специальных тестовых примеров, с ними не сталкивался. С артефактом компилирования сталкивался ровно один раз: gcc оптимизировал выражение strlen(x)*k с нулевым k, выкинув вызов strlen, в результате чего прога не падала при x==NULL и стала падать только спустя несколько лет при переходе на более новый компилятор, ну это уже не теоретическая проблема а явный пропущеный баг был. Какая там оптимизация стояла не помню.

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

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

И, в общем, с этим можно было жить. Вот документ, соблюдай что в нём написано и твоей программе ничего не угрожает. Достаточно приемлемый подход.

И он был работоспособен, пока не выяснилось, что “гениальные” идеи саботажников в стандарт не вписываются и они решили что их и стандарт тоже ограничивать не должен.

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

У любого бесплатного продукта есть потенциальный конкурент: форк того же самого продукта.

Как почти крайний - форкнуть gcc, хотя я не уверен что это проще.

В данном случае конкурентом clang и clang++ является rustc.

Со временем может gccrs подтянется или вообще свой компилятор напишется.

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

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

Кроме того, в компиляторах бывают просто баги.

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

Но это требование намного более сильное, чем просто "написать корректную программу на Си".

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

Если такую программу написать невозможно, то, стало быть, написать программу на C невозможно.

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

А вы задачу (утилитарную, а не идеологическую) не сформулировали.

Утилитарная задача: написать код для решения некоей задачи (неважно какой, у меня разные задачи) так, чтобы его можно было гарантированно скомпилировать и запустить через 30-40 лет. Где-то в районе 2050го-2060го года, то бишь. Используя акутальные компиляторы и железо для того года.

Ну вот как сейчас можно какоц-нибудь TeX83 скомпилировать и запустить. Да, ту самую версию из 1983го года. Да, без изменений.

Кнут для этого разработал целую систему программирования: https://en.wikipedia.org/wiki/Web_(programming_system)

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

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

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

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

Так что, фразы "Си превратили" смысла не имеют, потому что Си сам по себе это не что-то строго конкретное и никогда им не был. Вам могут не нравится конкретные его редакции/реализации/вариации, надо указывать какие.

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

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

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

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

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

Учитывая сказанное в начале этого коммента: надо уточнить, на какой вариации языка эта программа корректная. И компилировать только теми компиляторами, у которых заявлена поддержка именно этой вариации. Потом уже делать выводы. Да, судя по всему заявленная поддержка C99 у современных компиляторов с -O2 сломана, но это частный случай, и не повод делать максимально общий вывод.

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

Не всё так плохо, подмножество можно расширить по чуть урезанного C89.

Проще на другой язык, тогда уже, перейти.

Ну, это кому как, личные предпочтения.

так, чтобы его можно было гарантированно скомпилировать и запустить через 30-40 лет. Где-то в районе 2050го-2060го года, то бишь. Используя акутальные компиляторы и железо для того года.

Тут ни Rust, ни любой другой современный язык гарантий не даст. Никто не знает, какое там будет "актуальное железо" через 40 лет, будет ли оно вообще идеологически совместимо с текущим. Может там везде квантовые компьютеры будут и другая парадигма программирования, ну а старый софт - в эмуляторе запустится, да, вместе с gcc 4.2 для его компиляции. Если же кардинальных перемен не случится, то думаю приведённый выше пример будет и там работать.

Да, ту самую версию из 1983го года. Да, без изменений.

Ну это совсем другое. Кроме того, компилятор для K&R C тоже никто не мешает сделать, кому он только нужен будет кроме как для компиляции древностей?

Тут ни Rust, ни любой другой современный язык гарантий не даст.

100% гарантии, могут быть только на кладбище. Но это не значит, что нет никакой разницы между языком, поддерживаемым одним программистом, Rust'ом, C или, скажем, Python'ом.

Ну это совсем другое.

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

Заметьте, что изначальная система, разработанная Кнутом была основана на конвертировании исходников TeX'а (и METAFONT'а) в Pascal. Потому другие люди написали конвертор в C. Но во всех случаях задача ставилась ровно та, которую ставлю и я: поддержка того кода, который был написан 30 с лишним лет назад без каких-либо модификаций.

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

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

Мне не интересен эмулятор, извините. Опять-таки аналогия с TeX'ом: есть TeX, который уж лет 20 не менялся совсем, а до того менялся очень слабо, а есть XeTeX — современная верстого жеия того же TeXа (современная по меркам этого мира, разумеется, первая версия вышла в 2004м, последняя — в 2018м).

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

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

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

Или вы про то, что с какой-то там ничтожной вероятностью космические лучши могут что-то там нарушить? Я готов с этим смириться.

А всё остальное, что в современных процессорах бывает — неспецифицированное поведение. Это совсем другой зверь.

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

Но ни на одном современном процессоре у вас условие i || !i не даст вам 0. А в том, во что превратили C… пожалуйста: https://godbolt.org/z/GoG7zvxWv

Повторюсь, большинству хватает уровня документированности и предсказуемости имеющихся компиляторов, а 100% идеал всё равно невозможен.

Нет, не хватает. Если бы хватало — вам не нужно было бы рассусоливать про -O0 и gcc 4.4.

Так что, фразы "Си превратили" смысла не имеют, потому что Си сам по себе это не что-то строго конкретное и никогда им не был. Вам могут не нравится конкретные его редакции/реализации/вариации, надо указывать какие.

Принято. Пример компил/тора ну, скажем, с поддержкой RISC-V и без всей этой мути на тему pointer provenance — в студию. Тогда можно будет об альтернативе тому, что делают саботажники говорить предметно.

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

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

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

Можно, конечно, скачать откуда-нибудь Turbo C и в DosBox все программы запускать, но…зачем?

Есть же языки для которых совместимость — важнее скорости. И немало: Go, Haskell, Rust.

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

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

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

ДЫк они уже это сделали.. “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” — это не моя придумка, это прямая цитата.

Но это не значит, что нет никакой разницы между языком, поддерживаемым одним программистом, Rust'ом, C или, скажем, Python'ом.

Rust — язык одной компании
Python — язык одного программиста с соавторами, плавно превращается в язык одного сообщества
C — общественное достояние, если можно так выразиться


Разница, разумеется, есть. На мой личный взгляд — в пользу C.


поддержка того кода, который был написан 30 с лишним лет назад без каких-либо модификаций.

Зато был модифицирован "компилятор". Такое можно с любым языком провернуть. И повторюсь, нет никакой проблемы организовать сейчас компиляцию K&R C кода полувековой давности на современном железе. Но кому это нужно?


Приходится искать что-нибудь, что разработали другие.

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


А всё остальное, что в современных процессорах бывает — неспецифицированное поведение.

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


Нет, не хватает. Если бы хватало — вам не нужно было бы рассусоливать про -O0 и gcc 4.4.

Большинство тупо ставит -O2 и не парится. И логическую адекватность своего кода проверять не пытаются. До дефектов компилятора им дела тем более нет. Я, разумеется, эту ситуацию не одобряю, но надо признать, что такой факт имеется. И именно для этого большинства обычно всё и делается. Но я не пойму ваше упорное нежелание признавать gcc -O0 или gcc -O1 как способ компиляции, постоянно ссылаетесь на проблемы -O2 только и обобщаете их на "весь язык".


Пример компил/тора ну, скажем, с поддержкой RISC-V и без всей этой мути на тему pointer provenance — в студию

Не в курсе. Но быстро нашлось такое: https://github.com/riscv/riscv-gnu-toolchain Там нету режима -O0?


Есть же языки для которых совместимость — важнее скорости.

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


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

Ну так речь не про C++. Про C++ соглашусь — там указатели (идеологически) это не просто адреса. Хотя технически это и адреса. А вот почему противопоставили ему именно K&R C — не знаю.

Rust — язык одной компании
Немножко, всё-таки, не совсем так.

Python — язык одного программиста с соавторами, плавно превращается в язык одного сообщества
Ну, плюс-минус.

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

Rust помоложе, но у него уже тоже есть альтернативные версии.

Но кому это нужно?
Мне. Я не хочу каждые 2-3 года переписывать свой код только потому, что саботажники захотели всё “типа ускорить”.

Какое-то время они рассказывали сказки про то, что, типа “читайте стандарты и всё будет хорошо”. Но это времена прошли, теперь нам явно говорят: K&R C мёртв, а если вы хотите что-то делать с современным C (или C++), то будьте головы к тому, что правила игры будут меняться — в том числе “задним числом”.

Мне “такой хоккей” не очень нужен.

Rust имеет немалые шансы загнуться вместе с Мозиллой.
Хрустального шара у меня нет, но вряд ли. Не знаю что там с ним делает Microsoft, но Google его уже поддерживает на Android и пишет на нём операционку.

Это вселяет куда больше надежды чем рассказы про “общественное достояние”.

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

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

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

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

Но я не пойму ваше упорное нежелание признавать gcc -O0 или gcc -O1 как способ компиляции, постоянно ссылаетесь на проблемы -O2 только и обобщаете их на «весь язык».
Давайте я вам приведу факт, после которого вы, возможно, меня поймёте (а может и нет). Я, в некотором смысле, противоположен тем деятелям, которые “логическую адекватность своего кода проверять не пытаются”.

Я, так же, как и Кнут, не умею писать программы без ошибок. В программе, которую я написал на моём предыдущем месте работы за 5 лет после моего ухода ошибки были найдены. Где-то с полдюжины. Из них одна бы спровоцирована тем, что у Intel и AMD, оказывается, разные идеи о том, как устроен x86-64 код, ещё одна была моей ошибкой, и все оставшиеся — это ошибки, которые могут проявиться потенциально из-за того, что там есть ситуации, которые стандарт признаёт на UB (к реальным проблемам у реальныъ пользователей они не привели).

Так вот: у режимов -O0 или -O1 нет никакой спецификации, которая бы позволяла мне надеяться на то, что моя программа будет работать без ошибок. А вот у -O2 такая спецификация есть.

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

Не в курсе. Но быстро нашлось такое: github.com/riscv/riscv-gnu-toolchain Там нету режима -O0?
См. выше что я думаю про предложения использовать -O0.

Использование -O0 — это страусиная политика “да, саботажники сюда пока не добрались, потому давайте строить наш замок на песке в этом уголочке пляжа”.

Не бойтесь — они доберутся. В -O1 мы уже имеем i || !i == 0, так что и в -O0 никакой уверенности нету. Да собственно уже кой-какие трюки в clang -O0 не работают.

Да, это “плохой код”, разумеется так на надо делать… но если понятия “плохой код” и “хороший код” всё время меняется, то где гарантия, что тот код, который сегодня всем кажется “хорошим” завтра не объявят “плохим”?

Ну так речь не про C++. Про C++ соглашусь — там указатели (идеологически) это не просто адреса. Хотя технически это и адреса. А вот почему противопоставили ему именно K&R C — не знаю.
А потому что речь шла, внезапно, о программе на C, компилирующейся с ключиками -std=c89 -Wall -Wextra
и все оставшиеся — это ошибки, которые могут проявиться потенциально из-за того, что там есть ситуации, которые стандарт признаёт на UB
То есть вы написали UB, оно работало-работало и перестало, потому что обработка такого UB компилятором поменяла поведение. И вы вините в этом разработчиков компиляторов, а признавать свою вину в написании кода с UB не хотите. Я вас верно понял?
То есть вы написали UB, оно работало-работало и перестало,
Нет, не перестало, до пользователей оно не дошло, его в review обнаружили.

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

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

Вместо того, чтобы последовать явной рекомендации комитета по стандартизации (the implementor may augment the language by providing a definition of the officially undefined behavior) или даже, может быть, внести предложение об облегчении жизни программистам (о ужас, как можно) — они добавили костыль, который оставил в покое GCC и GlibC, но чтобы их разработчикам было стыдно — вставили ещё и предупреждение (видимо с рассчётом вынуть, со временем, костыль и начать-таки ломать программы по настоящему).

В моём коде это предупреждение тоже сработало — и код поправили.

За предупреждение им спасибо (хорошо хоть не сразу всё поломали), но вопрос отстаётся: компилятор является инструментом для программистов или это программисты являются рабами компилятора?

Зачем вместо того, что рекомендовал комитет по стандартизации (превращать UB, для которых, на данной платформе, можно придумать хорошую семантику в implementation-specific behavior) превращать UB в “божью кару”: конструкцию, которая 99% разработчиков кажется понятной и очевидной, но при этом трактуется компилятором как UB?
вот не далее как сегодня запускаю я нашу приложуху над некоторыми данными, получаю ответ за 8 минут 15 секунд. Пересобираю в релизе, запускаю над теми же данными, получаю 21 секунду. Вот убедите меня теперь что оптимизации не нужны.

Вот пользователь хочет менять значение const int через const_cast — отключаем статические константы. Пользователь хочет передавать параметры через неинициализированные переменные — значит нельзя передавать аргументы в регистрах и оптимизировать неиспользуемые значения. Пользователь может быть хочет случайно угадывать адреса функций — значит инлайнить их тоже нельзя, ровно как и стрипать за неиспользуемостью. А может быть пользователь хочет читать указатель из адреса 0xFFFFFFFFFFFD13, ведь он наверняка знает что там что-то полезное лежит? То есть любой доступ должен быть через memcpy, ведь невыровненное чтение тоже UB. Ах, вру, не через memcpy, а через memmove, там же UB если диапазоны пересекаются. А вдруг пользователь хочет пытаться выделять память до тех пор, пока снова не попадет в страницу с освобожденным блоком и не прочитает оттуда данные? Значит и аллокации оптимизировать нельзя. Ах, да, самое главное чуть не забыл. Надо же еще каждую арифметическую операцию над знаковыми перепроверять на переполнение и выдавать диагностическое сообщение, то есть арифметические оптимизации тоже нельзя. Вообще ничего нельзя, давайте все оптимизации выключим, быстродействие ведь волнует всего сколько-то там процентов кодеров, ну подумаешь замедлится в 40 раз (из примера выше) и почти никого не зааффектит. Ну уж точно зааффектит меньше, чем если сломается один хитрый хак через UB на пару MLOC. Я вас правильно понимаю? Может быть чего упустил?

Я вас правильно понимаю?

Неправильно.

Может быть чего упустил?

Да.

Ну уж точно зааффектит меньше, чем если сломается один хитрый хак через UB на пару MLOC.

Вот это вот, как выражался один великий русский педагог, называется “обыкновенная российская припадочная философия — либо всё, либо ничегоc”.

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

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

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

Ну действительно, кому придёт в голову идея использовать переменную вне области её видимости? Кому нужно использовать неинициализированную переменную? Даже о курьёзе о том, что i || !i может быть false, если переменная неинициализирована, многие не знают: ну действительно, зачем вам может хотеться, чтобы тут было true? Хотите true, напишите j = true и будет вам счастье. Писать j = i || !i (где i не инициализирована) не только не нужно, но и нелогично.

Однако со временем саботажникам этого стало мало и они начали “осваивать” всё более и более “нелогичные UB”. Такие UB, про которые стандарт говорит, что это UB, но которые в реальных программах никогда не были UB (ну, например, если вы передаёте this, равный nullptr в невиртуальную функцию — откуда тут взяться UB? vtable не задействована, в чём проблема-то?).

Причём что самое смешное: чем более нелогичен испольуемый для саботажа UB, тем меньше он даёт “ускорения” (в кавычках, потому что многие “самые свеженькие” UB, которые саботажники откопали в стандарте, ускоряют только бенчмарки, если вообще что-то ускоряют… а реальные программы они замедляют: теперь мне приходится писать не ptr + addr, а ptr ? ptr + addr : (decltype(ptr))addr, что на некоторых компиляторах работает так же, как и раньше, а на некоторых - медленнее) .

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

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

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

Такие UB, про которые стандарт говорит, что это UB, но которые в реальных программах никогда не были UB
ну так если кто-то заложился на никем не гарантированное поведение, которое явно в стандарте прописано как UB, значит сам себе злобный буратино.
ну, например, если вы передаёте this, равный nullptr в невиртуальную функцию — откуда тут взяться UB? vtable не задействована, в чём проблема-то?
в том, что компилятор, имея на это абсолютно полное право, считает что this никогда не может быть равен nullptr.
Причём что самое смешное: чем более нелогичен испольуемый для саботажа UB, тем меньше он даёт “ускорения”
тут вам конечно же виднее, вы же сделали столько real life замеров/sarcasm>
теперь мне приходится писать не ptr + addr, а ptr? ptr + addr: (decltype(ptr))addr, что на некоторых компиляторах работает так же, как и раньше, а на некоторых — медленнее) .
вы действительно храните объекты по nullptr адресу? Вы так мстите работодателю или коллегам?
Вы мне до сих пор так и не привели ни одного примера, где целенаправленно сломанный realloc хоть в какой-то программе хоть чего-то ускорил.
во-первых, у меня такого примера нет. Не потому что его не существует, а потому что его нет конкретно у меня. Я пишу на плюсах, здесь realloc редкий гость. В гугле порыться можете и сами

Во-вторых, я уже говорил и неоднократно, что pointer provenance это не только про realloc, это еще и про malloc, free, new, delete и иже с ними. Давайте простой пример: тык. Здесь msvc ожидаемо жует резину и выдает портянку, в то время как clang и gcc отлично справляются с вычислением того, что раз p и q от разных аллокаций, то они никогда не алиасятся, а значит результат никогда не будет {2, 2}, а значит можно вернуть {1, 2}. Это и есть та самая пресловутая оптимизация на базе pointer provenance. Вам какой выхлоп кажется более эффективным, у msvc или у gcc/clang?
помогать программисту писать эффективные программы
пока что все примеры которые вы привели, кроме конкретно realloc, это примеры когда программист написал не «эффективную», а «некорректную» программу. И я к каждому из этих примеров привел по категории оптимизаций, которую отсутствие такого UB выключит. Если вы хотите чтобы у вас некорректные программы не меняли поведение от оптимизаций, компильте с -O0. Напомню еще раз, что именно это является значением по умолчанию.

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

Так стандарт-таки священная скриждаль, на которую нужнло молиться? Или нет?

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

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

вы действительно храните объекты по nullptr адресу? Вы так мстите работодателю или коллегам?

Я не храню объекты по нулевому адресу. Просто внезапно, у меня не всегда есть объект.. Иногда у меня есть только адрес. Так ARM EABI устроен. Его просто люди писали не проникшиеся новомодной религией.

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

Здесь msvc ожидаемо жует резину и выдает портянку, в то время как clang и gcc отлично справляются с вычислением того, что раз p и q от разных аллокаций, то они никогда не алиасятся, а значит результат никогда не будет {2, 2}, а значит можно вернуть {1, 2}.

Вау. Как круто-то. А потом берём реальную программу и получаем some metrics get better, some get worse, but all of them are within 5% of each other.

Впрочем подозреваю, что вы, как обычно, откпопали чушь: пара new/free в clang/gcc исчезает не потому, что у них есть pointer provenance, а в msvc нет, а потому что у них есть эта конкретная оптимизация, а у msvc нет.

пока что все примеры которые вы привели, кроме конкретно realloc, это примеры когда программист написал не «эффективную», а «некорректную» программу.

Некорректная она только с точки зрения стандарта. С точки зрения CPU она вполне корректна и комитет по стандартизации, напоминаю, явно рекомендовал таки случаи переводить из UB в implementation-specific behavior.

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

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

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

Так стандарт-таки священная скриждаль, на которую нужнло молиться? Или нет?
Одно дело когда стандарт дает гарантию, а компилятор её ломает — такого естественно быть не должно. Пока что вы привели ровно один такой пример, и тот натянутый, но допустим.

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

Пожалуйста, различайте эти ситуации. Во второй виноваты вы и только вы.
В этом случае адрес просто указывает расположение объекта в памяти и nullptr + addr отлично этот случай обрабатывает. Вернее обрабатывал, пока саботажники до него не добрались.
А это может быть связано с тем, что конструкции ptr+addr и ptr? ptr + addr: (decltype(ptr))addr вообще-то не эквивалентны?
Вау. Как круто-то. А потом берём реальную программу и получаем some metrics get better, some get worse, but all of them are within 5% of each other.
The official MSVC builds used LTCG and PGO, while the Clang builds currently use neither of these. — clang выдает примерно одинаковый результат несмотря на то, что в MSVC включено больше оптмизаций. Что касается стабильности — We A/B-tested stability as well and found no difference between the two build configurations.
С точки зрения CPU она вполне корректна
напомню что Си должен работать не на одном конкретном CPU, а на любом асбтрактном вычислительном устройстве.
Ну то есть примера нет ни у саботажников, ни у вас, но рыться в гугле должен я?
вы думаете у «саботажников» нет такого примера только потому, что плохо рылись в гугле.

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

Весь proposal посвящён этим примерам, #$%@#%: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

Там куча примеров, где компиляторы ломают формально валидный код. Вы этот документ вообще, #$%@#%, открывали?

Вот это вот видели:

These GCC and ICC outcomes would not be correct with respect to a concrete semantics, and so to make the existing compiler behaviour sound it is necessary for this program to be deemed to have undefined behaviour.

Если это — не пример того, что “стандарт дает гарантию, а компилятор её ломает”, то что тогда будет таким примером?

На чистом, #$%@#%, английском языке написано: стандарт даёт гарантии, наши компиляторы их не соблюдают, давайте править стандарт.

Пожалуйста, различайте эти ситуации. Во второй виноваты вы и только вы.

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

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

А вот если какое-то ограничение стандарта кажется сабтажникам неудобным и “to make the existing compiler behaviour sound it is necessary for this program to be deemed to have undefined behaviour”… всегда пожалуйста.

Либо стандарт - это закон, и его обязаны соблюдать все, и саботажники и разработчики, либо это филькина грамота и тогда у разработчиков столько же моральных прав на требование не трактовать this = nullptr как UB, как у саботажников - прав на добавление новых UB.

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

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: the implementor may augment the language by providing a definition of the officially undefined behavior.

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

А это может быть связано с тем, что конструкции ptr+addr и ptr? ptr + addr: (decltype(ptr))addr вообще-то не эквивалентны?

Не порите чушь, ей больно: https://godbolt.org/z/d5vovv11z

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

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

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

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

Я с ними предметно разговаривал - у них нет.

Собственно ссылку на этот самый документ с pointer provenance они мне предоставили. Когда я спростил: какого чёрта они говорят о каком-то понятии, которого в стандарте нет и никогда не было.

Мне ответили “да, нету… но мы скоро добавим”.

Вот собственно тут я к ним уважение и потерял. Когда они сотни тысяч программистов заставляют зубрить сотни бессмысленных правил потому что “незнание закона не освобождает от отвественности” - это нормально. А когда у них, видите ли, возникают проблемы с компиляцией корректных программ, тогда “to make the existing compiler behaviour sound it is necessary for this program to be deemed to have undefined behaviour”?

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

Там куча примеров, где компиляторы ломают формально валидный код. Вы этот документ вообще, #$%@#%, открывали?
я уже объяснял почему конкретно первый пример из этой доки имеет UB здесь. Это точно не является формально валидным кодом, по букве стандарта. Там куча других примеров, которые по большей части либо UB сейчас, либо их поведение и не меняют. Даже ваш пример с realloc не до конца корректен — вы не проверяете кейс !p && !q.

Тем не менее, давайте я еще раз повторю то, что я теми или иными словами уже несколько раз написал: одно дело когда стандарт дает гарантию, а компилятор её ломает — такого естественно быть не должно. Я согласен с вами в этом. Да, конкретно (и только!) в режиме -std=c89 ваш код с realloc работать должен. Можете контрибьютнуть фикс.
Либо стандарт — это закон, и его обязаны соблюдать все, и саботажники и разработчики, либо это филькина грамота и тогда у разработчиков столько же моральных прав на требование не трактовать this = nullptr как UB, как у саботажников — прав на добавление новых UB.
вы до сих пор не можете понять что я пишу, это просто ужасно. Если вы, как в случае с nullptr[100500], заложились на поведение, которое и по стандарту и в компиляторе UB, то виноваты вы, и только лишь исключительно вы. А не разработчики стандарта и компилятора, которые видите ли не определили что должна делать каждая потенциальная несуразица.
уберите треть UB из стандарта, прекратите ломать людям работающие программы — а потом можно будет подумать и как pointer provenance в стандарт добавить. Опционально.
я вам уже приводил список UB, починка которых предотвратит возможность целых классов оптимизаций. Так скажите, с точки зрения быстродействия, какой смысл вообще писать на си/плюсах если финальная программа будет работать медленнее, чем если написать её на условной джаве?
Не порите чушь, ей больно: https://godbolt.org/z/d5vovv11z

Замените void на int (а использование decltype как бы намекает на возможность использования произвольного типа) и посмотрите снова.


Вот так будет правильнее (но перестанет работать для void):


p ? (p + offset) : (decltype(p))(offset * sizeof(decltype(*p)));
Нельзя так выразится. Разработку C осуществляют вполне конкретные люди.

"Разработка C" (речь не про компилятор, а про спецификацию языка, писаную ли не писаную) это обобщённое понятие по уже выше указанным причинам.
Если уточнять, есть комитет (вроде бы, один, но никто не мешает появиться равноценному второму), есть авторы gcc, clang, msvc, icc, pcc и ещё кучи менее известных компиляторов.
Любой, кто сделает свой компилятор и как-то окажется популярным — станет влиять на то, что понимается под "Си". Как минимум тем, что есть компилятор Си с его кастомными фичами, как максимум тем что его кастомные фичи будут позаимствованы другими компиляторами (например, gcc) и/или попадут в очередную версию стандарта от комитета.
Если же кто-то сделает альтернативный питон, то в лучшем случае его запомнят как "альтернативный питон от такого-то автора", а вообще скорее всего ему придётся придумывать другое название.


Мне. Я не хочу каждые 2-3 года переписывать свой код только потому, что саботажники захотели всё “типа ускорить”.

Ну, компилятор K&R C написать и поддерживать весьма легко (без оптимизатора, но кажется его наличия нет в задаче). А весь остальной свой код можно будет писать на K&R C и таким образом не переписывать. Как идея?


не сможете запускать никаких программ, кроме тех, что напишите сами.

Такая ОС будет бесполезна, разумеется поддержка имеющегося софта нужна. Ну и js с веба в том числе. Но это оффтоп.


Так вот: у режимов -O0 или -O1 нет никакой спецификаци

Да, формально нету. Я просто предполагаю что неформальная спецификация -O0 будет и дальше такой же. Возможно, зря. Время покажет, но пока что проблем это не создало, а кто-то тратит силы на изучение других языков по теоретическим причинам. Хотя нет, один раз помню сломали memcpy — вместо последовательного копирования от меньших к большим адресам вставили туда какие-то оптимизации. Хотя это не про компилятор а про библиотеку. Ну, я не считаю что трата времени на выявление этой проблемы больше, чем трата времени на другой язык.


Да собственно уже кой-какие трюки в clang -O0 не работают.

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


А потому что речь шла, внезапно, о программе на C,

Тогда непонятно откуда там отсылка к C++. Видимо, то сообщение писал кто-то кто совсем не в теме и думает что C++ это новая версия C.

Любой, кто сделает свой компилятор и как-то окажется популярным — станет влиять на то, что понимается под "Си".

А вот фиг. Microsoft попробовал - и не смог (да, я знаю, он расширял C++, не C, но тут принципиальных отличий нет).

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

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

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

Это да, но ситуация с C или C++ ровно та же. Ну вот назовите хоть один компилятор, сознданный в XXI веке и, как вы говорите, “оказавшисяй популярным”.

Я знаю только clang - и они действовали через комитет по стандартизации, а не вопреки ему.

Ну, компилятор K&R C написать и поддерживать весьма легко (без оптимизатора, но кажется его наличия нет в задаче). А весь остальной свой код можно будет писать на K&R C и таким образом не переписывать. Как идея?

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

Проще уж взять какой-нибудь Turbo C 2.0 и все ваши творения в DOSBOX запускать.

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

Такая ОС будет бесполезна, разумеется поддержка имеющегося софта нужна. Ну и js с веба в том числе. Но это оффтоп.

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

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

То же самое - со своим личным компилятором (правда там задача чуть проще: язык, которым пользуетесь только вы, всё-таки чуть полезнее, чем OS, программы под которую пишете только вы, но в любом случае делать всё одному я не готов).

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

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

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

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

Но это проблемы clang-а а не проблемы Си.

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

Если вы скажите, что ваша библиотека работает в GCC в режиме -O0, а clang её ломает - ло её за десять вёрст будут обходить.

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

А вот фиг. Microsoft попробовал — и не смог (да, я знаю, он расширял C++, не C, но тут принципиальных отличий нет).

Отличие в том что они хотели вставить привязку к платформе. Эти штуки не очень подходят для заимствования их в gcc, например.


, чем, свой, особый, диалект C или C++.

Так надо не диалект в явном виде а аккуратно небольшими порциями.


Я знаю только clang

Я тоже. Но это не доказательство.


Проще уж взять какой-нибудь Turbo C 2.0

Там ограничение по памяти, например. Да и вообще, по-моему не проще.


Поддрежку имещющегося софта вы не обеспечите

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


Они сделали синоним с memcpy на memmove для старых программ, если вы ту же самую программу пересоберёте из исходников, получите те же проблемы.

С синонимом тоже проблемы. memcpy раньше был заполнителем памяти по шаблону, например memcpy(p+16, p, 1600) размножит первые 16 байт 100 раз, проблемы могли быть разве что с шаблоном меньше машинного слова. С новым вариантом кроме как ручной цикл ничего видимо нельзя сделать.

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

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

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

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

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

Последние лет 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, то, соотвественно, он и не действует.

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

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

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


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

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


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


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

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


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

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


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

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

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

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

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

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


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

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

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

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

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


        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++?

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


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

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

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

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

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

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

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

Это напомнило мне ещё одну попытку починить шаблоны в C++ — Circle от Sean Baxter.
Вот программа на нём, которая вычисляет вектор из простых чисел во время компиляции:
github.com/seanbaxter/circle/blob/master/comprehension/braced1.cxx
… концепты в их текущей форме — это по большому счёту сахар для SFINAE. По крайней мере, я не смог опровергнуть утверждение об их изоморфизме.

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

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


Тем более, что в ряде случаев можно обойтись static_assert'ом в теле конструктора или if constexpr там же.

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

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

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

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


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

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

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

Можно, если эта функция может быть реализована в 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.

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

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

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

Раз говорим о завтипах, то чего ещё не хватает в Haskell? Данные и коданные разделены? Рекордами удобно пользоваться без линз? Если нет, то каким образом тут применять линзы?

Данные и коданные разделены?

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


Рекордами удобно пользоваться без линз?

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


Да, иметь row polymorphism круто и прикольно, но, субъективно, он не сделает мою жизнь сильно лучше (хотя я знаю тех, кто его прям жаждет).


Если нет, то каким образом тут применять линзы?

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

Offtop: может, через Liquid Haskell к 2023 завезут выборочную проверку тотальности.
Далее, что там у нас с многопоточностью?

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

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

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

В python/cpython, к слову, потоки вообще попередохли из-за неустранимых ошибок проектирования.
Потоки никакого отношения к языкам программирования не имеют, так как являются концепцией ОС и конкретного syscall ее системной библиотеки.

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


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


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


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

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

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

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

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


pthread_mutex_lock(&mutex);
GlobalVar = 42;
Мне не нужно опровергать этот тезис

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

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


Ожидаемо.

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

Это неважно

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

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

А это компенсируется легко в исторически зарекомендовавших себя языках => процитированное есть исключительно ситуативная опциональная вкусовщина.
>> Слова «модель памяти» вам о чём-нибудь говорят? Ну это так, для начала и чисто формально.

А в RUST уже завезли формальную memory model для языка?

То есть этот тот момент, когда с т.з. софистики вопрос на 10/10, но с точки зрения практики — «проклятая реальность» пока ещё слишком сильна.
А в RUST уже завезли формальную memory model для языка?
Они тупо слизали её с C++20.

Насколько это хорошо — вопрос отдельный.
А в RUST уже завезли формальную memory model для языка?

ХЗ, я не фанат раста.


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

Языку программирования желательно знать про существование потоков.
Разработчики стандарта C это понимают, и в C11 появилось ключевое слово _Thread_local, а в gcc до этого использовалось нестандартное __thread для thread-local storage.
Не спорю, это все полезно и вносит вариативность. Но новизны тут нет. Пример: pthread_key_create() -> IEEE Std 1003.1-2001
Если начинать рассуждать о времени компиляции как о значимом факторе
… то нужно взять разработчиков компиляторов и авторов Стандарта, запереть в комнате и не выпускать пока не доделают модули до нормального состояния.
Не заметил, чтобы кто-то упомянул о частном решении задачи ускорения компиляции в MS VC++ с помощью предкомпилированных заголовочных файлов (pch). Нормально так ускоряет, на порядки. Применяю на проекте, где практически всё сделано на шаблонах, и шаблоны работают вместе с дженериками C++/CLI.

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Человек собирал однажны что-то в духе ./configure; make; make install, но не зашло. Увидел в консоли ошибку из строки 9001ой Makefile'а, и расстроился.

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

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

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


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

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

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

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

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

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

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

И думаю таких примеров кучи. Собственно, о чем Линус и говорит. Им приходится компилировать ядро с кучей флагов компилятора, фактически, создавая новый диалект С с нужной им семантикой. Все потому что стандарт содержит херню откровенную.
Нахрена это было делать UB?
Это из-за различного представления отрицательных чисел до воцарения х86, зачем это тянут в стандарте после 2000 года — вообще не в курсах.
Да ок, сделайте это implementation defined и дело сделано.

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

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

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

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

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

См. статью «What every compiler writer should know about programmers»
Об этом недавно была статейка. Да, на заре Си UB означало в большей степени implementation defined — т.е. места, где стандарт не хочет мешать, а дает вольность компилятору и программисту делать так, как им нужно. Но в какой-то момент трактовку в стандарте поменяли и UB стало трактоваться совершенно иначе. Оно превратилось в универсальное оправдание для компиляторов делать какие им вздумается оптимизации и ломать код. В итоге мы имеем язык, как выше писал, который не может определиться. Он и не низко уровневый, потому что компилятор не дает использовать возможности железа из-за непредсказуемости UB, но и не высокоуровневый, потому что заставляет страдать с ручным управлением памятью и прочими штуками. Но в стандарт многопоточность и атомики мы почему-то добавили.
Запросто. Упомянутое переполнение знаковых. Нахрена это было делать 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 в стандарте позволяют делать похожие штуки.

Сделать проверку или учитывать, что может быть 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а получить, то это, извините, не язык, а минное поле.
Вы дня начала найдите процессор, который не умеет делать wrap-around.

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


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

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

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

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

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


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

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


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


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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Как и у 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

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


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

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


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

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


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

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


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

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


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

«Теперь 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++… не знаю, не пробовал.

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


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

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


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

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


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

Кстати Go в embedded очень даже успешен. github.com/f-secure-foundry/tamago Вот эта штука очень интересная, но есть и другие. Сейчас вот вторая попытка заапастримить. Там изменений очень мало требуется в рантайме.
Ну, знаете, там хотя и баре-метал, но все же это для верхнего сегмента эмбеда пока что. Понятно, что на МК наверное тоже натягивается, но насколько посильно для унтерменшей сделать туда BSP?
Уж не знаю какой это сегмент, но проект этот делался конкретно для USB токенов. Наверное можно еще меньше, но для меня верхний сегмент эмбеда это всякие расбери, джетсоны и прочие здоровенные железяки с мощными процами и кучей памяти.
Даже первый в списке? SoC, который можно в флэшку засунуть, как-то сложно назвать верхним сегментов. Я так понимаю именно для этой платформы делался проект — авторы про этот продукт часто рассказывают.
Процессор на ядре А7 вы считаете не относится к верхнему сегменту? Более чем. Те самые «малинка и ее друзья» по сути. Взрослый процессор, просто не самый мощный в линейке, ни разу не микроконтроллер.
Насчет Хаскеля 0xd34df00d пошутил наверное. Слишком сложно для нашего брата.

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


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

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


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

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


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

А вот оставшиеся 20% (это как правило «большой стейт реального мира вокруг») настолько сложно, что почти любая программа кроме «олимпиадной задачки на AVL-tree» будет сложнее чем её императивный аналог.

Для конкретики: предположим мне, относительному новичку в Хаскель, сегодня в 2021 году надо писать что-то с GUI.
Что для этого есть под Хаскель (хоть немного дружественного)?

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


Для конкретики: предположим мне, относительному новичку в Хаскель, сегодня в 2021 году надо писать что-то с GUI.
Что для этого есть под Хаскель (хоть немного дружественного)?

Тут, увы, подсказать ничего не могу. Я давно ничего гуёвого не писал, поэтому хз.

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

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

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

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

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

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

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

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

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

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

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

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

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

Чорт )))

Значит пора прикручивать iarcc к кейлу. Я отчего-то уверен в его большей консервативности.

Да можно наверное просто на IAR переходить тогда, это проще, а интерфейс тоже как будто из 90-х :)

Ну или оставаться на пятой версии компилятора.

интерфейс тоже как будто из 90-х :)


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


Есть такое в этой сфере. Но все же много людей разумных, которые помнят, что Кнут говорил.
Простейший известный мне способ получить UB в безопасном коде: берёте Rc, выкручиваете счётчик ссылок на переполнение через mem::forget, дальше когда счётчик близок к переполнению — у вас происходит катастрофа в “безопасном Rust” с помощью “безопасного Rust”…

Так, а где там катастрофа? При переполнении счётчика вызывается abort с вполне себе определённым поведением.

Интересно когда пофиксили. Rustonomicon это приводит как пример получения UB в безопасном коде с использованием только стандартной библиотеки, но сейчас глянул - там действительно abort нонче стоит.

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

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

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

У этих <как бы без мата> и так уже все разработчики жалуются на “минное поле из 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” и объясняют, что ещё чуть-чуть и они добавят-таки ещё кучку UB в стандарт и все те “краевые примеры”, о которых я беспокоюсь уж точно станут невалидными.

Зашибись. Восторг просто. Движение в строго противоположную сторону от того, что планировалось:

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.

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

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

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

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

Я пробежался по этому pointer provenance, но так и не понял — как это все вяжется с memory mapped девайсами? Т.е. просто кастить void* указатель в какой-то тип и читать оттуда его значение нельзя. memcpy сделать наверное можно, но непонятно, как компилятор воспримет попытку обращения к какому-то рандомному адресу, у которого provenance никакого нет? Или тут типа сами разрбирайтесь как хотитет, asm блоки делайте или флагами оптимизации отключайте?

Когда мне нужно было делать mmap и оттуда что-то читать как инты и так далее, я делал memcpy, да.

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

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

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

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

Почитайте (это блог разработчиков Rust, но примеры там на С всё равно): https://www.ralfj.de/blog/2020/12/14/provenance.html

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

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

Более того, мне было заявлено, в достаточно категоричной форме, что оно туда зашито настолько глубоко, что даже и ключик -fno-pointer-provenance сделать невозможно.

Собственно когда я это осознал… я и начал играться в Rust (пока только мелкие программки для себя).

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

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

Честно говоря, хотел на практике посмотреть что будет — но не хватило терпения. Код простой:


use std::rc::Rc;

struct Foo;

fn main() {
    let rc = Rc::new(Foo {});
    loop {
        std::mem::forget(rc.clone());
        let count = Rc::strong_count(&rc);
        if count % 1000 == 0 {
            println!("{}", count);
        }
    }
}

Я ждал минут 5 и остановился только на 1866080000. Это 1/9885291130 часть диапазона usize (на х86 которым я пользуюсь).


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


И даже в случае если вдруг у вас это выйдет то если залезть в реализацию Rc то там будет:


#[inline]
fn inc_strong(&self) {
    let strong = self.strong();

    // We want to abort on overflow instead of dropping the value.
    // The reference count will never be zero when this is called;
    // nevertheless, we insert an abort here to hint LLVM at
    // an otherwise missed optimization.
    if strong == 0 || strong == usize::MAX {
        abort();
    }
    self.strong_ref().set(strong + 1);
}

Стд не панацея — но подобные проблемы считаются багами и фиксятся (в т.ч. вами — взяли и починили если ломает).