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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

Как пример - использование чего-то типа 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

НЛО прилетело и опубликовало эту надпись здесь

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

Ответа я так и не получил. Ибо в стандарте никакого 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…
НЛО прилетело и опубликовало эту надпись здесь

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

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

Хм, а что он там не так соптимизировал? Вроде же честно пишет в память по одному указателю и читает из другого...

НЛО прилетело и опубликовало эту надпись здесь
Только это не проблема языка, а проблема компилятора и стандарта, который такое поведение поощряет. Я об этом ниже писал. Именно поэтому линукс приходится компилировать конкретным компилятором с конкретными флагами, потому что С превратили в непредсказуемое нечто. Когда-то он был и задумывался как «портативный ассемблер», но потом власть захватили писатели компиляторов, которым лишь бы побольше оптимизаций сделать через 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, то, соотвественно, он и не действует.

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

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

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


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

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

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

Вопрос, насколько это интересно?
НЛО прилетело и опубликовало эту надпись здесь
(хотя это немножко ложь, у плюсов в компилтайме есть глобальное мутабельное состояние)


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

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

В «доказательство» я могу привести два языка, с которых генерируются шаблоны — это «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++?

НЛО прилетело и опубликовало эту надпись здесь

Благодарю за поддержку 0xd34df00d и Sklott

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

В 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.
НЛО прилетело и опубликовало эту надпись здесь
Может: github.com/ziglang/zig/blob/master/lib/std/heap.zig#L802
Функция StackFallbackAllocator принимает comptime-аргумент size и возвращает тип-структуру, у которой поле buffer это массив байт длиной size.
НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

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

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

Ожидаемо. Системные проекты используют это уже много десятилетий. Опять-таки, это не язык, а особенности архитектуры.
НЛО прилетело и опубликовало эту надпись здесь
Реализация, напрочь игнорирующая любую системную функцию по работе с барьерами, будет абсолютно корректной реализацией C99 (и C++03).

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь
Компилятор, который вырежет эту инструкцию

… перестанет быть одним из самых востребованных.
НЛО прилетело и опубликовало эту надпись здесь
Это неважно

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

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

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

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

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

Насколько это хорошо — вопрос отдельный.
НЛО прилетело и опубликовало эту надпись здесь
Языку программирования желательно знать про существование потоков.
Разработчики стандарта 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 год), и появился именно потому, что назрела потребность в создании ЯП для написания переносимого, но эффективного кода (с каковой задачей, к слову, он вполне справляется и поныне).

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

Человек собирал однажны что-то в духе ./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.
НЛО прилетело и опубликовало эту надпись здесь
Я вот не помню, кто так не делает, вроде последние исключения перестали серийно выпускаться ещё в 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.

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

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


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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь
Только вот там 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 вы считаете не относится к верхнему сегменту? Более чем. Те самые «малинка и ее друзья» по сути. Взрослый процессор, просто не самый мощный в линейке, ни разу не микроконтроллер.
НЛО прилетело и опубликовало эту надпись здесь
не сложнее C++


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

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


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

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

Для конкретики: предположим мне, относительному новичку в Хаскель, сегодня в 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 блоки делайте или флагами оптимизации отключайте?
НЛО прилетело и опубликовало эту надпись здесь

Там этим 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);
}

Стд не панацея — но подобные проблемы считаются багами и фиксятся (в т.ч. вами — взяли и починили если ломает).


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




Почитал тред — я слоу

Я ждал минут 5 и остановился только на 1866080000. Это 1/9885291130 часть диапазона usize (на х86 которым я пользуюсь).
Соберите под i686 и всё быстро упадёт. Как нам уже сообщили всё давно пофикшено.

А на x86-64 да, это чисто теоретическая возможность.

Если у вас есть арудинка или ещё что где усайз другой то можно попробовать запустить и посмотреть на фейерверк.
Не будет феерверка. В современной версии Rc там abort стоит. Заметьте — не panic, а abort. Считается, что что-то испорчено так капитально, что лучше упасть.

Хорошо это или плохо — не знаю, но UB нет, результат предсказуем.

На х86 мне кажется это можно отнести к разряду невозможных.
Вы имели в виду x86, я думаю. На x86-64 всё падает довольно быстро.

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

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

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

Но вот в обратную сторону это не работает: если стандарт нам чего-то запретил… то это ж не повод “пооптимизировать”.

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В таком случае вы должны уметь объяснить как оптимизировать все те программы, которые мы 0xd34df00d напридлумывали. Вперёд и с песней.
Приведенный пример со сверткой констант, как раз таки оптимизируется на раз. Достаточно чтобы компилятор перестал учитывать константность при оптимизации. Может вы наивно полагаете, что свертка констант работает только для const-переменных? Тогда вы глубоко заблуждаетесь.
Не будет она работать. Так как компилятор может тупо не видеть того кода, который констранту будет модифицировать. В конце-концов я могу этот адрес записать файлик, а потом, совсем в другой функции, прочитать и константу модифицировать. И всё, и нельзя будет константы сворачивать.
Ну так в таком случае оптимизация не проводится. Компилятор оптимизирует в том случае, если он может доказать, что конечный результат оптимизируемого участка не изменится в результате этой самой оптимизации.
Далее упираемся в критерии, что именно считать тем же результатом. Например, изменение времени выполнения ни один компилятор не считает изменением результата (иногда это мешает, к счастью существуют средства объяснить компилятору, что время в данном месте тоже надо учитывать). Брать ли в расчет отсутствие UB или не брать — опциональное решение не особо влияющее на качество оптимизации даже в C.
Проблема в том, что он опирается на подобное предположение не в некоторых случаях, а почти во всех. Ну разве что 2 * 2 вы на 4 можете безболезненно заменить. И то только тогда, когда обе двойки у вас прямо в коде написаны. Если хотя бы одна из них константа — всё, уже ничего менять нельзя. И переменные в регистры со стека поднимать нельзя. И вообще много чего нельзя.
Неверно. Если вы хотите модифицировать константу просто не делайте ее константой. UB пропадет, а оптимизация, если она была с константой, останется. Можно, наверное, придумать какие-то замысловатые случаи, но они погоды не сделают.
Я много пишу на D, в том числе и довольно низкоуровнего кода, но к UB я прибегаю крайне редко (когда надо обойти несовершенство языка или баг компилятора, а не ради ручных оптимизаций). И, внезапно, все оптимизируется шикарно, особенно свертка констант меня радует.
Вы, видимо, плохо себе представляете возможности современных компиляторов. Застряли в 60-х?
НЛО прилетело и опубликовало эту надпись здесь
К чему эта странная дихотомия? Вы не допускаете ситуации, когда какие-то участки программы оптимизируются, а какие-то нет?

Фи. Логика. Закон исключённогно третьего. Нафига она кому нужна в XXI веке?

Сегодня “гуманитарная одарённость” ценится.

Где-то год назад мы уже обсуждали с одним подобным товарищем проблемы типизированных языков.

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

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

Потом, он, правда, самовыпилился. Но сам факт существования подобных личностей… ужасает.

Нам же тем, что они наваяют, потом пользоваться!

НЛО прилетело и опубликовало эту надпись здесь

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

Это просто. Любое изменение поведения, которое можно увидеть “снаружи”. Исключая размер кода и скорость работы. Af if rule: https://en.wikipedia.org/wiki/As-if_rule

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

Нет.

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

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

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

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

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

Вы вообще в логику умеете? Два и два можете сложить? А умножить?

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

А когда я сделал вы мне говорите “а… это-то… ну вы уберите из программу UB и всё будет зашибись”.

Где, блин, логика-то?

Я много пишу на D, в том числе и довольно низкоуровнего кода, но к UB я прибегаю крайне редко

Всё. Разговор окончен. Вы не то, что не понимаете, что такое UB, вы вообще не владеете логикой в объёме начальной школы.

Есть люди, умеющие “находить общий” язык с дошлкольниками, но я к ним не отношусь, увы.

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

НЛО прилетело и опубликовало эту надпись здесь
Конечно же нет. Но только в этом случае компилятор программу компилирует без ошибок, но запись в эту переменную игнорирует.
Ну так если убрать из стандарта, что модификация константы это UB, то компилятор уже не смог бы игнорировать эту запись. И он ее не игнорирует, если снять константность и тем самым вынудить компилятор не делать ошибочных предположений. То бишь убирание в данном случае UB не мешает компилятору применить свертку константы. ЧТД.
Конечно же нет. По ссылке выше со снятием константности результат меняется, и компилятору его неизменность не получится доказать при всём желании.
Нет, но он мог бы. Мы же тут гипотетическую возможность обсуждаем, а не текущее положение дел. В данном случае компилятору не нужно доказывать неизменность, ему достаточно доказать, что оптимизация не меняет конечный результат. И компилятор определенно может это сделать без опоры на предположения об отсутствии UB. И он это делает, если убрать константность. То бишь, попытка надуть компилятор выполнив изменение через указатель не сработала, он отлично просекает такие «хитрости» и в состоянии сделать правильную свертку.
НЛО прилетело и опубликовало эту надпись здесь
Именно! Про то и речь! В данном случае он может убрать эту запись, выполнив соответствующую «оптимизацию» именно потому, что это UB.
Ну так с этим и боремся вроде.
Ок, убрал: gcc.godbolt.org/z/751E151M4
Где свёртка константы?
Непонятно, что вы хотели сказать этим примером. Очевидно, что причина в сложности кода функции. Эта сложность, видимо, достаточно большая, чтобы компилятор не смог применить свертку. Помешала ему именно сложность кода, а не то, что вы убрали UB.
Ведь если сделать код чуть-чуть проще, то свертка снова начинает работать: gcc.godbolt.org/z/o13W3Y9bP

У меня сложилось впечатление, что вы нарочно поэтапно усложняли функцию добиваясь «отказа» оптимизатора. Правда непонятно зачем.
Добавьте const в примере выше. Где там правильная свёртка?
Ну так потому что компилятор опирается на понятие UB.

Такое впечатление, что вы либо троллите, либо вообще не читали, о чем собственно идет дискуссия и пишете что-то на своей волне.
НЛО прилетело и опубликовало эту надпись здесь
Если я верну обратно const, то функция превратится в простую return 0. Кстати, ack достаточно простая для того, чтобы компилятор видел, что она не имеет сайд-эффектов.
Ага, превратится. Доказательства отсутствия сайд-эффектов недостаточно для свертки констант. Необходимо, чтобы вычисление этой константы требовало бы не слишком болшого количество ресурсов.
Кстати, как вам такой constant propagation?
Великолепный образчик то ли бага, то ли безумия. Разрешить вылезти за пределы массива, и затем просто выкинуть этот кусок кода. Чудненько. В более поздних версиях ситуация стала получше, но все еще недостаточно хороша.

Именно. Про то и речь!
Так речь-то не про то. Я же говорю, вы не читали дискуссию.
Нет, я просто пользуюсь формальной логикой, отталкиваясь от тезиса, что понятие UB и предположение об отсутствии UB в коде программы позволяет компилятору делать некоторые оптимизации.
Значит верно второе мое предположение, что вы на своей волне.

Спор-то не о том, опираются ли текущие реализации компиляторов C/C++ на понятие UB. Очевидно, что в некоторых случаях опираются. С этим я не спорю и не спорил изначально.

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

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

сложнее замены int n = 2 * 2; на int n = 4;

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

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

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

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

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

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

Вообще вы тут не правы, а khim просто хитрит.

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

Поэтому у вас ровно два варианта:
— не проводить оптимизации
— рассчитывать на то, программист не получает доступ к вашей переменной через «достаточно хитрый alias».

На практике выбран второй вариант.
Анализ алиасов указателей в C-коде алгоритмически неразрешим.
Что вот совсем никогда не разрешим? Спорное утверждение. Даже банальное отсутствие принудительных преобразований типов на участке кода говорит об отсутствии нарушений strict aliasing.
И насколько часто в среднестатистических программах на C встречаются места с потенциальным UB связанным с нарушением strict aliasing?
А в программах на C++?
Мой поинт в том, что в оптимизирующем компиляторе C / C++ вопрос не в том «вводить UB или не вводить», а в том «насколько агрессивно полагаться на отсутствие UB в компилируемом коде».
В том числе из-за алгоритмической неразрешимости (на практике мы просто куда раньше упрёмся во время компиляции) доказательства отсутствия UB.

>> Что вот совсем никогда не разрешим?

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

>> и часто в программах…
Вот смотрите, с т.з. компилятора: если в функцию пришло два внешних указателя — то без шансов что-то про них доказать (в том числе часть случаев — доказать можно было бы возможно, но для этого надо заново запустить межпроцедурный анализ, а уже нельзя, т.к. он делается на другом представлении). Остаётся:
— ничего не оптимизировать;
— дублировать код для случаев «непересекающихся» и «пересекающихся» объектов;
— работать, как будто нет UB;
— взять «rust», с более ограниченным набором возможностей в rust-safe, чем C.

Поэтому опция «сделаем вид что UB в коде нет» — единственная возможная (на принципиальном уровне) для компиляторо-писателей С/С++.
Агрессивность — можно было бы и снизить.
Вот смотрите, с т.з. компилятора: если в функцию пришло два внешних указателя — то без шансов что-то про них доказать (в том числе часть случаев — доказать можно было бы возможно, но для этого надо заново запустить межпроцедурный анализ, а уже нельзя, т.к. он делается на другом представлении). Остаётся:
— ничего не оптимизировать;
— дублировать код для случаев «непересекающихся» и «пересекающихся» объектов;
— работать, как будто нет UB;
— взять «rust», с более ограниченным набором возможностей в rust-safe, чем C.
Я думаю, вы исходите из ложной предпосылки, что невозможность доказать отсутствие нарушения strict aliasing будет блокировать остальные оптимизации. Я не согласен с этим.
Вы можете сами проверить свой тезис указав компилятору GCC опцию -fno-strict-aliasing запретив компилятору предполагать, что указатели разных типов обязательно указывают на разные данные.

Оптимизация основанная на strict aliasing вообще практически бесполезна. А в тех редких случаях когда она играет важную роль, можно оптимизировать вручную, вплоть до кода на ассемблере.
Оптимизация основанная на strict aliasing вообще практически бесполезна.
зависит. Если у вас сишный код, на каждом шагу жонглирующий (unsigned) char* и сериализующий/десериализующий пустое в порожнее, то да, толку маловато будет. Тем временем потенциальная возможность включить в rust повсеместный strict aliasing является одним из важнейших его selling point'ом в вопросах производительности относительно с++.
А в тех редких случаях когда она играет важную роль, можно оптимизировать вручную, вплоть до кода на ассемблере.
оптимизировать вручную на ассемблере можно всегда, только вы эту оптимизацию получите не условно бесплатно, а ценой кучи человекочасов квалифицированных разрабов. При этом она еще получится и менее переносимой, и менее стабильной.
зависит. Если у вас сишный код, на каждом шагу жонглирующий (unsigned) char* и сериализующий/десериализующий пустое в порожнее, то да, толку маловато будет. Тем временем потенциальная возможность включить в rust повсеместный strict aliasing является одним из важнейших его selling point'ом в вопросах производительности относительно с++.
Все-таки хотелось бы каких-то реальных примеров. То есть берем некую реальную программу и компилируем ее с оптимизацией strict aliasing и без. И сравниваем производительность.
Если верить изначальному тезису, то производительность с опциями -O3 -f-no-strict-aliasing будет разительно отличаться от просто -O3 и будет приближаться к -O0
При этом она еще получится и менее переносимой, и менее стабильной.
Начиная с некоторого размера кода она окажется ещё и тупо менее эффективной.

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

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

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

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

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

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

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

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

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

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

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

Rust получится.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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


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


Для меня, гораздо более очевидный и читаемый код, будет такой
if x == i32::MAX. Тут намерения выражены более явно, и я бы никогда не написал первый вариант в угоду второму.


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


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


И наконец-то про тот код на С++, во-первых decltype и std::numeric_limits действительно выглядят много-словно, т.е. если бы решение выглядело if x == typeof(x)::MAX, то мне оно все ещё казалось бы лучше. Задача же обрабатывать signed и unsigned в одной функции сразу, в Си не стоит потому-что там нету темплейтов.


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


Имхо, во всех вариантах намерения выражены более явно и понятно, чем в первом, а эра while (*d++=*s++) ушла. Может эта эра и осталось в эмбеддед, но компиляторы оттуда уходят получается...


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

Имхо, во всех вариантах намерения выражены более явно и понятно, чем в первом, а эра while (*d++=*s++) ушла. Может эта эра и осталось в эмбеддед, но компиляторы оттуда уходят получается...
Не нужна эта эра и в эмбеддед. Она там остается исключительно потому что там в основном работают консервативные инженеры в возрасте, уверенные, что они всегда умнее компилятора, а время остановилось. Я же по своему опыту в эмбеддед могу сказать, что почти всегда нет никакой необходимости в ручных оптимизациях, компилятор сам все делает не хуже, а местами и лучше человека.

А что если тип у икса не int32_t? В си (и с++) это компилятор проглотит без ошибки и без предупреждений, в итоге при рефакторинге типа икса будет баг в программе.

Я правильно понял, что в такой программе бага не будет:

int64_t x;
if (x == INT_MAX) {
  ...
}

Это ж просто офигительно, но… как?

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

А если у бабушки было бы известно что, то она была бы дедушкой, извините. Мы не обсуждаем Java, Ruby или даже Haskell. Речь идёт про C (ну и чуть-чуть про C++).

Вариант if x + 1 < x лучше как раз тем, что он не зависит от типа, с которым вы работаете, Любой целый тип, со знаком или без знака обрабатывается одинаково.

Задача же обрабатывать signed и unsigned в одной функции сразу, в Си не стоит потому-что там нету темплейтов.

Однако кто-то туг про рефакторинг песни пел… не напомните, кто?

Что касается темплейтов, то там ситуация чудесна и удивительна: в стандарте темплейтов нет, но во всех реализациях — они, в том или ином виде, есть. А потому что без них tgmath.h не делается: https://en.cppreference.com/w/c/numeric/tgmath

И в любом случае вопрос не в том, удобнее ли if x + 1 < x, чем другие варианты, а в то, что для чисел без знака это всё ещё работает, а для чисел со знаком — нет. Притом, что я не знаю ни одного выпускаемого сегодня процессора, который бы умел это отлавливать для чисел без знака, но не для чисел со знаком.

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

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

Ну типа -Wsign-compare может чуть чуть помочь.

Чем она поможет, если вы вместо LONG_LONG_MAX (как должно быть для 64-битной платформы) написали INT_MAX?

Абсолютно законная (и абсолютно бессмысленная) деятельность.

Может PVS-Studio чем-то поможет, но зачем? Зачем заменять простую и надёжную конструкцию на что-то более сложное и менее надёжное?

Потому что в стандарте так написано? Ok, принимается, но только если стандарт чего-то стоит.

А если стандарт разработчика к чему-то обязывает, а саботажника — нет, то блин, какого чёрта?

Вы там все этодругина, что ли, перепили?

Поэтому и написано "чуть чуть". В том месте я изначально намекал на раст, где таких неявных кастов между i32/i64 нету ;)

А причём тут Rust? Там проблем нет, так как и UB нет: https://godbolt.org/z/75114898c

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

Ни в коем случае. Просто я не понимаю зачем вы оправдываете заведомый, простите, говнокод и негодуете когда оптимизация его ломает.
Что за наезды? Я вам снейчас с десяток компиляторов (из 80х-90х) подгоню, где он будет отлично работать.
Вам просто повезло, что он работает так как вы ожидаете. Программа с переполнением стека тоже может годами работать, а потом внезапно в ней что-то начнет ломаться, потому что авторы компилятора что-то там перекроили в генераторе кода. И вина в этом именно автора кода переполняющего стек, а не авторов компилятора.
Работающий код, очевидно. Со вполне простой и понятной семантикой. Программирование с стиле фортрановских common block'ов. А чё такого то?
Использование неинициализированных переменных содержащих мусор, вы называете «понятной семантикой»? Действительно, а что такого, когда результат вашей программы зависит, грубо говоря, от положения звезд на небе?
Вот именно тот, который выдаёт GCC с -O0. Это, собственно то, что первые лет 10 существования C все компиляторы выдавали.
А на каком основании вы решили, что GCC с -O0 выдает верное решение, а с -O2 неверное? Конкретно в вашем примере GCC с -O0 выдал 9. Но стоило мне в функции foo j = 3 исправить на j = 2 и GCC с -O0 начал выдавать 6. Так 6 или 9 или еще какое-нибудь число из диапазона 32битного знакового целого?

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

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

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

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

И вина в этом именно автора кода переполняющего стек, а не авторов компилятора.

Потому что, внезапно, переполнение стека - это тоже UB.

Использование неинициализированных переменных содержащих мусор, вы называете «понятной семантикой»?

Здравствуйте, я ваша тётя. Кто сказал что они неинициализированы? Они в одной функции инициализируются, в другой используются. В чём проблема?

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

Причём тут звёзды на небе вообще? Они зависят от раскладки переменных на стеке. В ранних яызках (в 60е) эта информация вообще в мануалах писалась (и программы на основе этих подходов писались), потом это стало “дурным тоном”… но это ж вы тут завели гармонь, что программы с UB умеете оптимизировать, не я.

А на каком основании вы решили, что GCC с -O0 выдает верное решение, а с -O2 неверное?

На основании здравого смысла, очевидно.

Но стоило мне в функции foo j = 3 исправить на j = 2 и GCC с -O0 начал выдавать 6. Так 6 или 9 или еще какое-нибудь число из диапазона 32битного знакового целого?

Вы издеваетесь или как? Разумеется если вы замените j на 2, то результат будет 6.

Фишка этой программы в том, что тут нет “неиницилизированных переменных”. Они инициализируются. В функции foo. А потом используются. В функции bar.

Поскольку переменные инициализированы, то мы знаем ответ.

Это был нормальный и поддерживаемый стиль в 60е. Более того, на калькуляторах этот стиль и сегодня используют, почитайте про ans: http://tibasicdev.wikidot.com/68k:ans

А про то, что программы с UB вы умеете как-то оптимизировать — это ж вы сказки поёте, не я.

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

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

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

Что-нибудь типа:

void foo(int* a, int* b, int* c, int i, int j, int k) {
  a[i] = b[j] + c[k];
}

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

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

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

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

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

И такой подход можно было бы “понять и простить”, если бы… если бы хоть где-нибудь был бы полный и исчерпываюший список этих самых UB!

Но, внезапно, оказывается, что такого списка нет и саботажники требуют, чтобы ваша программа не содержала UB — но при этом отказываются объяснить что же, чёрт побери такое эти UB!

В C++ стандарте списка UB вообще нет, в C стандарте он есть, но если верить саботажникам, то имеются ещё и какие-то мистические, нигде пока не описанные real and valid emergent property that every compiler vendor ever agrees on.

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

Кафка отдыхает.

Так я об этом и говорю. Как только компилятор считает что UB и пытается это использовать надо об этом сообщать.
Например даже int a=0x12345; может быть UB. Но это UB (int 16 бит) компилятор пока еще ругается что число не влазит, а когда использует всякую дичь которая позволяет выйти из бесконечного цикла (исходя из того что на какой-то архитектуре, отличной от целевой, это может вызвать исключение), просто молчит и генерит всякие непотребства.

ps: придётся пользоваться старыми, проверенными компиляторами, а не этим слабо предсказуемыми. Которые в борьбе за оптимизацию ради оптимизации, оптимизируют здравый смысл ибо UB.

Например даже int a=0x12345; может быть UB.

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

Но это UB (int 16 бит) компилятор пока еще ругается что число не влазит.

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

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

Погодите? Какие непотребства он генерит? Этот случай очень чётко описан в стандарте:

An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of 1 a for statement) its expression-3, may be assumed by the implementation to terminate

Тут тоже нет никакого UB, всё очень чётко а аккуратно прописано: если цикл ничего не меняет “во внешнем мире”, то его можно удалить. И всё.

Хотите бесконечного цикла — используйтеfor(;;); илиwhile(true);

нафига вам больше бесконечных циклов?

Или вы про то, что вы пытаетесь из цикла смотреть на какой-то глобал без volatile ? Ну это, как бы, да, UB, но я бы не сказал, что это какое-то особо страшное UB. Без барьеров и прочего у вас сама железяка может устроить вам кузкину мать, даже без компилятора: https://preshing.com/20120515/memory-reordering-caught-in-the-act/

ps: придётся пользоваться старыми, проверенными компиляторами, а не этим слабо предсказуемыми. Которые в борьбе за оптимизацию ради оптимизации, оптимизируют здравый смысл ибо UB.

Проблема в том, что эти “слабопредсказуемые” это где-то примерно начиная с GCC 2.95, вышедшего в прошлом веке (скорее даже egcs).

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

Здравствуйте, я ваша тётя. Кто сказал что они неинициализированы? Они в одной функции инициализируются, в другой используются. В чём проблема?
С каких это пор в C это называется инициализацией? Вы опять изобретаете свою собственную терминологию и применяете ее в качестве аргументов.
Вы издеваетесь или как? Разумеется если вы замените j на 2, то результат будет 6.
Это как раз вы издеваетесь. Потому что это совершенно не разумеется. По крайней мере в языке C версии этого тысячелетия.
Фишка этой программы в том, что тут нет “неиницилизированных переменных”. Они инициализируются. В функции foo. А потом используются. В функции bar
Единственная фишка этой программы в том, что она ломается от малейшего чиха. Даже если компилятор и его режимы заморожены намертво.
Поскольку переменные инициализированы, то мы знаем ответ.
В том-то и дело, что мы не знаем ответ, мы можем только догадываться и надеятся, что он всегда будет таким.
Это был нормальный и поддерживаемый стиль в 60е. Более того, на калькуляторах этот стиль и сегодня используют, почитайте про ans: tibasicdev.wikidot.com/68k:ans
А причем тут C в 2021 году?
А про то, что программы с UB вы умеете как-то оптимизировать — это ж вы сказки поёте, не я.
По-моему, вы ударились в чистую демагогию. Рассуждая по-вашему мы приходим к выводу, что оптимизации опираются на все что угодно: на отсутствие багов в железе, на допустимый температурный режим работы процессора, на наличие мозга и отсутствие кривых рук у программиста в конце-концов. Вы только посмотрите, сенсация, оптимизации сущестуют только благодаря говнокоду родом из 60-х. Принимаем вашу логику и терминологию и идем еще дальше, супер тезис: корректность любой программы опирается на баги (ну то есть на их отсутствие).
Оптимизируйте. Ну или расскажите, хотя бы, как вы собрались это делать, чтобы подобный класс программ не ломался.
Я уже рассказывал, но вы отказываетесь понимать.

С каких это пор в C это называется инициализацией?

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

В обычном, классисческом, “C с запрещённывм UB”, это — UB. Конкретно это UB — описано в разделе 6.2.4 Storage durations of objects:

If an object is referred to outside of its lifetime, the behavior is undefined

Соотвественно компилятор может полагаться на то, что программист такого не делает.

В новом, модном, молодёжном “C с разрешённым UB” это — нормальная, валидная конструкция. Почему, как вы считаете, она незаконна?

По крайней мере в языке C версии этого тысячелетия.

Конечно в языке C этого тысячелетия с запрещённым UB эта конструкция запрещена! Потому что это UB.

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

Единственная фишка этой программы в том, что она ломается от малейшего чиха. Даже если компилятор и его режимы заморожены намертво.

Слушайте, вы в школе учились да? Про “доказазательства от противного слышали” ? А вообще про логику?

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

Отлично, берём стандарт, вычёркиваем оттуда все упоминания UB. Пишем максимально кривую программу, которая соотвествует этому новому стандарту “C с разрешёнными UB”. Оптимизируем. Если не получается — ваш постулат опровергнут.

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

Приведённая программа — одна из таких. Не самая ужасная какую можно придумать, но да, довольно-таки подленькая.

В соответствии с вашим постулатом её можно будет без проблем оптимизировать.

Ну так оптимизируйте! Эту соптимизируете, что-нибудь другое напишем, понаворочанней.

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

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

Стандарта “C с разрешённым UB” не существует.

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

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

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

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

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

Вау! В голове у пациента обнаружен мозг! Ды, вы правы, оптимизации действительно на всё это опираются!

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

Если у программиста кривые ручки и он пишет программы с UB (вот одну такую мы сейчас подробно обсуждаем) — то это тоже не бага в опитимизаторе, это “бага” в голове у программиста, да.

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

Ну вот я вас и спрашиваю: а как вы это собрались оптимизировать?

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

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

Он столь же правдив, как и очевиден…

Я уже рассказывал, но вы отказываетесь понимать.

Да я такой. Бред сивой кобылы, в котором отсуствует логика я “понимать” отказываюсь.

И да, я знаю, что у гуманитариев очень цениться умение пороть чушь с умным видом.

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

С тех пор, как вы сказали “От любого UB только вред. В нём нет пользы”.
И как из этого следует ваше определение инициализации?
Соотвественно компилятор может полагаться на то, что программист такого не делает.
А может и не полагаться. Текущий C полагается, но если вдруг перестанет, он не станет от этого неоптимизируемым.
Вау! В голове у пациента обнаружен мозг! Ды, вы правы, оптимизации действительно на всё это опираются!
Вау! У пациента обнаружен переход на личности Только вот такое обобщение ваших тезисов абсолютно бесполезно и бессмысленно. Как в том анекдоте про Холмса на воздушном шаре и математика.
Вы тут уже многократно завляли бред: что программы на C можно оптимизировать, не опираясь на отсутствие в них UB.
Возможно какое-то тотальное непонимание. Может я недостаточно ясно выражаюсь, не знаю. Попробую еще раз.

Компилятору на данный момент разрешено опираться на предположение об отсутствии UB, игнорируя реальное положение дел. Например, он видит const и делает предположение, что переменная не будет далее меняться, а она на самом деле меняется. Но введение UB в стандарт разрешило компилятору плевать на реальность. Fail.
А теперь выбрасываем из стандарта вообще само понятие UB. Все, нет никакого UB. Все разрешено, кроме непосредственно ошибок компиляции.
Что получаем. Компилятор больше не может делать никаких предположений, он обязан применять оптимизации только в тех случаях, в которых он в состоянии доказать, что ничего кроме времени выполнения и размера кода не поменяется. Если доказать не может (в принципе невозможно или слабый анализатор кода или просто ради скорости компиляции, не важно), то данная часть программы не оптимизируется, а выполняется в точности как написано в исходнике. Win.

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

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

И как из этого следует ваше определение инициализации?

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

Да, описания того, что там произойдёт — тоже нет. Но “мы-то знаем”, что они лягут на стек в то же место. Точно так же, как “мы-то знаем”, что INT_MAX, увеличенный на единицу превратится в INT_MIN.

Текущий C полагается, но если вдруг перестанет, он не станет от этого неоптимизируемым.

Отлично. Рассказывайте как оптимизировать предложенную вам программу.

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

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

Ещё раз: я не оправдываю “говнокод”. Я спрашиваю: как вы, собственно, собрались его оптимизировать?

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

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

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

int main() {
  exit(0);
}

Ну и генерить всегда одинаковый бинарник. А чё-такого-то?

А теперь выбрасываем из стандарта вообще само понятие UB. Все, нет никакого UB. Все разрешено, кроме непосредственно ошибок компиляции.

Отлично. То есть вот то, мы с 0xd34df00d понаизобретали — тоже разрешенj. Чем эти примеры плохи? Ошибок компиляции там нет, UB вы из языка выкинули.

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

Отлично. Принято. Теперь рассматриваем функцию:

int foo() {
  int i = 3, j = 3;
  return i + j;
}

В соотвествии с правилами игры — её можно скомпилировать с оптимизацией. После этого в ней можно добавить пару таких функций:

int bar() {
  int i, j;
  return i * j;
}

int main() {
  int foo_result = foo();
  int bar_result = bar();
  printf("%d\n", foo_result);
  printf("%d\n", bar_result);
}

И получить предсказуемый выхлоп: 9.

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

Вопрос: что и как вы там наоптимизировали в первой функции? Ну вот какой там будет ассемблерный код? Как он обеспечит работоспрособность второго фрагмента?

Заметьте: раздельная компиляция функций — в стандарте более, чем разрешена, это вообще основной вариант.

Кстати, почему так много вроде бы неглупых людей, искренне считающих оскорблением слово «гуманитарий»?

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

О.Рябцева―Как вы собираетесь…? Мы договорились: восемь миллионов в стране.

В.Шендерович―Сколько?

О.Рябцева―Восемь? Я могу ошибаться.

В.Шендерович―Кого?

О.Рябцева―Миллионов в стране людей, говорю. Да, нет?

В.Шендерович―Кого?

О.Рябцева―Людей, людей. Как вы хотите, чтобы все люди договорились об этом.

В.Шендерович―В какой стране восемь миллионов?

О.Рябцева―В России, я про Россию говорю.

В.Шендерович―В России – восемь… Да, конечно, вы правы… Уточните потом.

О.Рябцева―У меня проблемы с цифрами, я гуманитарий

Вот откуда-то отсюда.

Настоящий гуманитарий, умеющий-таки в логику, это, как раз-таки страшная сила. Но они своей “гуманитарностью” обычно не кичатся, а скорее обижаются, как Гумилёв: ну, какой же я интеллигент — у меня профессия есть, и я Родину люблю!

Запрет на определение переменной в одной функции с последующим использованием в другой — это следствие запрета на использование переменной вне определённого в языке “времени жизни”. И только. Никакого другого ограничения, запрещающего нам делать это в языке C нет.
давайте начнем с того, что в языке си нет никакой гарантии на то, что ваши int i, j; будут иметь такие значения, а не какие бы то ни было другие. Неинициализированные переменные, как-никак. А т.к. этой гарантии нет, закладываться на любые значения бессмысленно

А причём тут гарантии? Какие-то гарантии язык C даёт только если ваша программа не вызывает UB. Нам же предложено сделать вот это вот:

А теперь выбрасываем из стандарта вообще само понятие UB. Все, нет никакого UB. Все разрешено, кроме непосредственно ошибок компиляции.

В этом случае нам придётся как-то говорить о таких вот случаях. А иначе что мы собрались обсуждать? Программу на C, которая не вызывает UB и поведение которой, по этой причине, полностью определено существующим стандартом? Ну какое же это “все разрешено, кроме непосредственно ошибок компиляции”? Это обычная программа на C будет.

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

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

в языке си нет никакой гарантии на то, что ваши int i, j; будут иметь такие значения, а не какие бы то ни было другие. Неинициализированные переменные, как-никак.

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

Если i,j не инициализированные это значит что там может быть любое допустимое значение для int

Наивный, наивный, бесконечно наивный чукотский вьнош. А вот так не хотите: https://godbolt.org/z/GoG7zvxWv

int foo() {
    int i;
    return i || !i;
}

int main() {
    printf("i || !i: %d\n", foo());
}

Вот такой выхлоп:

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
i || !i: 0

Так вот именно такая логичная логика пронизывает новые стандарты и считается нормой.

Да нет, с логикой там всё нормально. Вон, посмотрите, как на мою простенькую идею инициализировать переменную в одной функции, а поиспользовать в другой @japplegame отреагировал. А проде как и не компилятор.

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

Но небольшой процент… ну вот нифига они не очевидные. Ни разу. Скажем почему я не могу сделать такое вот: (char*)NULL + addr. Ну вот почему это UB, с какого-такого перепугу?

Почему не объявить их implementation-defined и не поддержать? Ах, ну да, бенчмарки. Можно 0.0001% выиграть. Да, 0.0001% на бенчмарках — это святое.

Заметьте, что это строго противоположное деяние по сранвнию с тем, как язык C планировали развивать:

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

Но… бенчмарки! Бенчмарки да… KPI, статьи в журналах, премии, всё ж от них.

А пользователи? Да чего про них думать-то. Пусть плачут и страдают.

int is_compiler_acceptable() {
  int i; return i || !i;
}

Класс! Надо во все либы добавлять при инициализации.
НЛО прилетело и опубликовало эту надпись здесь

Рячь шла не про возможность/невозможность оптимизации программы с UB, а просто с понятии “валидная оптимизация”.

Если отнести к “наблюдаемым эффектам” скорость работы и размер кода, то вообще никакие оптимизации окажутся невозможными.

А уж возможны ли “валидная оптимизации” для программ с UB —это уже второй вопорос.

А хотел придумать пример, когда даже замена “2 * 2” на 4 ломает программу, но оказалось, что этот вариант невозможно проверить, так как даже старый древний Turbo C 1.0 даже в режиме без оптимизаций меняет-таки “2 * 2” на 4.

А ничего менее оптимизирующего у меня нету.

НЛО прилетело и опубликовало эту надпись здесь

Что-нибудь там про флаги процессора, OF, CF, вот это всё. Выполнение умножения в этом конкретном случае ЕМНИП их обнулит, и на это можно завязаться.

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

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

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

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

Ваша категоричность совершенно не в тему.
Критерии допустимости изменений результата могут варьироваться в зависимости от конкретных требований программиста. Поэтому оно может быть «верно», а может быть и «неверно». Общий случай рассматривать бессмысленно.
В подавляющем большинстве случаев изменение времени выполнения и размера кода являются приемлемыми. Более того они являются целью оптимизации.
Я уже упоминал ранее, что на практике случаются ситуации, когда изменения времени выполнения оказываются недопустимыми. Для таких случаев в компиляторах предусмотрены различные конструкции вплоть до ассемблерной вставки.
Ничего не понял. Почему вы банальность, достойную Капитана Очевидность называете супертезисом?
Потому что с точки зрения русского языка эта банальность некорректна. «Опираться на баги» и «опираться на отсутствие багов» — прямо противоположные утверждения. В общем-то это был камень в форму вашего первоначального изречения:
Вообще-то, внезапно, все оптимизации (ну или почти все) опираются на UB.
Но, вероятно, камень был несправедливым, потому что вы имели в виду само понятие UB. То есть оптимизации опираются на сам факт присутствия понятия UB в стандарте языка. С этим трудно спорить, возможно так и есть. Ведь это может сильно упростить реализацию оптимизаций в компиляторе. Особенно это было важно как раз много лет назад, когда и компьютеры были относительно маломощными.

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

Потому что с точки зрения русского языка эта банальность некорректна. «Опираться на баги» и «опираться на отсутствие багов» — прямо противоположные утверждения.

Ясно. “Гуманитарная одарённость” есть, знания русского языка нет. Wikipedia, может помочь: https://ru.wikipedia.org/wiki/%D0%AD%D0%BD%D0%B0%D0%BD%D1%82%D0%B8%D0%BE%D1%81%D0%B5%D0%BC%D0%B8%D1%8F

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

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

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

Но на форумах штатных корректоров нет, превращать разговорный язык в литературный некому.

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

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

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

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

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

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

Если UB из C или C++ выкинуть, то в нём окажется разрешёнными масса разных странных и загадочных конструкций. Можно будет ходить в другие функции и использовать их локальные переменные, можно будет использовать освобождённую память (ну да, только чуть-чуть, пока ты другую переменную не заведёшь, а чё-такого-то…) и вообще можно будет делать массу странных и загадочных вещей, которые приведут к тому, что [почти] любая попытка как-то оптмизировать код приведёт к тому, что он будет [потенциально] ломать какой-то говнокод где-то в другом месте (не забывайте, что в типичном случае C и/или C++ компилятор всей программы целиком не видит).

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

Однако он, скорее всего, будет иметь мало общего с C. Больше на Rust будет похож, скорее всего.

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

Для этого придётся серьёзно изменить язык, прежде всего.

Но, по моим прикидкам это уже сделано.

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

Спасибо japplegame за настойчивость, ибо я только сейчас понял, что вы хотели сказать в своих более ранних сообщениях.
Радует, что кто-то понял, о чем я говорю.
Ясно. “Гуманитарная одарённость” есть, знания русского языка нет. Wikipedia, может помочь: ru.wikipedia.org/wiki/%D0%AD%D0%BD%D0%B0%D0%BD%D1%82%
Вы бы сами прочитали свою статью. Описываемое там явление не имеет ничего общего с тем, что происходит у вас.
Сегодня это во много раз важнее, чем “много лет назад”. Именно потому, что раньше процессоры были маломощными и вам не нужно было, скажем, стараться кешировать значения в регистрах.
Это отдельная дискуссия, мне она не особо интересна, если честно. Могу только сказать, что разница в два раза это очень даже серьезно. Ради нее определенно стоит оптимизировать. Ну и существуют довольно ресурсоемкие оптимизации (та же свертка констант) для которых ваша аргументация не работает.
Не упростить, а “сделать возможным”. Ибо в случае с C и C++ огромнейшее количнество, как вы выражатесь, “говнокода” не может появиться в программе только потому, что попытка его использовать будет считаться UB.
Не согласен. Люди не говнокодят не из страха перед UB (хотя и это тоже немного влияет), а потому что говнокод сложно поддерживать. Если убрать понятие UB, то компилятор перестанет оптимизировать говнокод (туда ему и дорога), а нормальный чистый код будет оптимизироваться прекрасно.
Даже и близко нет. Все (ну или почти все) оптимизации в реально существующих компиляторах опираются на отсуствие UB.
В данном случае я говорил не о компиляторах C/C++, а о бэкендах, например GCC/LLVM, которые умеют кучу оптимизаций для самых разных языков, как с опорой на отсутствие UB так и без него. То есть все уже сделано, достаточно просто желания разработчика компилятора.
Скажем без запрета на обращение к переменным вне их области видимости (что происходит в примере, который вызвал у вас такую неприязнь) мало чего вообще можно наоптимизировать
Почему это вы так решили? Еще раз. Компилятор в состоянии доказать, что вот в этом вот куске программы нет никакого обращения к переменным вне области видимости, и соответственно его можно безопасно оптимизировать.
Понимаете?

Если ваша программа сплошь и рядом состоит из непрозрачных «грязных» хаков, то да, соглашусь, компилятор практически ничего не сможет соптимизировать. Но в реальности люди уже давно так не пишут. Даже в программах с жестким реальным временем, даже если каждый такт и байт на счету.
Поэтому практически любой современный код может быть проверен компилятором на отсутствие «сюрпризов» и соптимизирован. Проверен не так как сейчас в C/C++: «мамой кланус тут нэт UB», а с математическим доказательством.
Переделывать язык тотально не обязательно. Просто в случае Rust компилятор будет оптимизировать немного чаще, чем в C/C++.
НЛО прилетело и опубликовало эту надпись здесь
Трудно сказать. Всякое в жизни случается. Но думаю, что в подавляющем большинстве случаев толку от этой оптимизации с гулькин нос. КМК во многих случаях компилятор способен проанализировав код доказать (а не понадеятся на честное слово), что нарушения strict aliasing не было и сделать соответствующую оптимизацию.

Трудно сказать. Всякое в жизни случается.

И вы вот на этом собрались компилятор строить?

Ну… успехов. Только без меня.

НЛО прилетело и опубликовало эту надпись здесь

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

Вы дебил или играете такового на Хабре?

Да, конечно, если сделать поиск с заменой и заменить в стандарте понятие “undefined behavior” на “говнокод”, то, разумеется, оптимизирущий компилятор, опирающиймся не на отсуствие “undefined behavior”, а на отсуствие “говнокода” сделать будет можно.

Только вы нифига не сделали “компилятор, не опирающийся на отсуствие undefined behavior”, вы просто словоблудием занимаетесь.

Слово “говнокод” можно заменить обратно на “undefined behavior” и ваш компилятор снова начнёт зависеть от его отсуствия.

В данном случае я говорил не о компиляторах C/C++, а о бэкендах, например GCC/LLVM, которые умеют кучу оптимизаций для самых разных языков, как с опорой на отсутствие UB так и без него.

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

По другому — только пипхолы (типа замены x*5 на lea EAX, [EAX+4*EAX]). Это мизерная часть всех оптимизаций.

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

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

Ну заменили вы два слова “undefined behavior” на одно слово “говнокод”, что это принципиально изменило-то?

Вы всерьёз думаете, что если то же самое явление назвать по другому, то оно исчезнет?

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

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

Слушайте, дискуссия, по-моему, уже на десятый круг скоро пойдёт. Ну какая разница как вы undefined behavior называете?

Вы можете это назвать “undefined behavior”, “говнокод”, “грязные хаки” или даже “бульгуль-хурмуль”. От названия ничего не меняется: это синтаксически корректный код, который, как считает ваш компилятор-оптимизатор, в программе отсутствует.

А уж поиск с заменой произвести и заменить “бульгуль-хурмуль” обратно на “undefined behavior” в любом текстовом редакторе — не проблема.

Проверен не так как сейчас в C/C++: «мамой кланус тут нэт UB», а с математическим доказательством.

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

Примеры вам приводили.

Переделывать язык тотально не обязательно.

Обязательно. Вы своими рассказами ровно это и делаете. Выкинув понятие “undefined behavior” вы тут же добавляете в него понятия “говнокод” и/или “грязные хаки”. Иначе “не срастается”.

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

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

Нет. В Rust попытка обратится к неинициализированной переменной — это ошибка компиляции.

Даже вот такое вот:

let x;
if false {
  foo(x);
}

Это всё равно ошибка компиляции.

В C и C++, вместо этого есть обязательство, навешанное на программиста, не делать в программе “бульгуль-хурмуль” (раз уж вам название “undefined behavior” не нравится).

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

А то, что вы считаете, что замена названия “undefined behavior” на “бульгуль-хурмуль” позволяет вам что-то там “оптимизировать, не опираясь на отсуствие в программе undefined behavior”… так это больше говорит о вашем владении зачатками логики, чем о C или C++.

Да, конечно, если сделать поиск с заменой и заменить в стандарте понятие “undefined behavior” на “говнокод”, то, разумеется, оптимизирущий компилятор, опирающиймся не на отсуствие “undefined behavior”, а на отсуствие “говнокода” сделать будет можно.
ШТА? «Да что ты, чёрт возьми, такое несешь?»
Ладно, я полагаю, что все желающие смогли ознакомиться с моими и вашими аргументами и сделать соответствующие выводы. Дальнейшую дискуссию с вами считаю бессмысленной. Оставайтесь при своем мнении.
В своё время, когда компьютеры по возможностям соответствовали современным нетоповым микроконтроллерам, был зоопарк архитектур и не существовало IDE, наличие кучи UB было оправданной: программист писал код на бумажке, держа в голове те или особенности, и фактически вклад программиста и компилятора в преобразование текста программы в исполняемый код был примерно одинаковый. Ну просто не мог тогдашний комп прожевать полноценный компилятор. А зачем сейчас UB оставляют — без понятия.
Сейчас UB позволяет компилятору делать кучу трюков для того, чтобы сделать код ещё быстрее, исходя из предположения, что программист знает, что делает, и что во время реального исполнения UB никогда не случится — так можно убрать лишние проверки, загрузки из памяти, записи в таблицах исключений или вовсе выкинуть кусок кода. Проблема лишь в том, что это не всегда совпадает с ожиданиями программиста, ведь компилятор тут делает ставку на то, что человек не ошибся.

Если бы UB было однозначным злом, то рынок бы давно захватил компилятор, который спасает программиста от этой «проблемы». Сейчас же мы имеем UBSan и кучу флажков (всякие -fwrapv, -fno-delete-null-pointer-checks, -fno-strict-aliasing).
Вместо компилятора мы имеем языки, которые захватили мир и не страдают всей этой фигней с UB. Для С/С++ уже скорее всего время упущено. Убирать UB из них поломает обратную совместимость. Да и это чисто идеологическая проблема в том числе — за стандартами и компиляторами стоят люди, которым UB и возможность отыграть жалкие доли процентов производительности похоже важнее всего остального. Благо появился раст, который может быть быстрее без всех этих проблем.
НЛО прилетело и опубликовало эту надпись здесь
Я думаю проблема будет намного сложнее. Даже если в теории можно заменить UB на то, что фактически и так происходило и закрепить это в стандарте, это все равно сломает кучу кода. И людям будет пофиг, что они типа сами виноваты, у них UB в коде и никто им ничего не гарантирует. Если куча кода зависит от какого-то ошибочного поведение, это поведение становится стандартом. Я думаю там будет гора таких проблем, которые погрузят всю экосистему в хаос.
Линус регулярно сталкивался с тем, что новая версия gcc генерирует не тот код при сборке ядра, какой ему бы хотелось. И ничего страшного не происходит, экосистема в хаос не погружается — он просто ругается в LKML, добавляет очередной ключик в KBUILD_CFLAGS и продолжает использовать gcc.
С glibc тоже была похожая история, когда появилась новая версия memcpy, копирующая память в другом направлении на некоторых процессорах. Это сломало кучу кода (в частности плагин Flash), Линус снова ругался, но тоже ничего страшного не случилось — все по-прежнему используют glibc.
Можно сначала реализовать какое-то ожидаемое программистами поведение в качестве UB в одном компиляторе (вряд ли это сложнее, чем спроектировать новый язык и сделать для него хороший оптимизирующий компилятор). Для gcc существенную часть этой работы можно сделать, просто изменив настройки по-умолчанию.
Это маленькие примеры изменения поведения. А при изменении стандарта поведение изменится кардинально и у всех. Я думаю тут хаос и наступит, когда повсюду начнет ломаться тонна кода. Да, это все решаемо, но усилий и времени это будет стоить очень много. По сути, получится чуть ли не ситуация как питоном 2 и 3. Вроде бы код совместимый, а вроде и нет.
Си простой, но порой в нем не хватает современных фич. И система типов слишком слабая. Если писать именно на чистом Си, то некоторые ошибки, обнаруживаемые в С++, в Си компилятор не ловит.

С++ слишком сложный. Причем дело не в количестве фич — к примеру у C# их тоже много; дело в какой-то вывернутости наизнанку, в результате чего в коде больших проектов на С++ обязательно найдется «дописывание компилятора» — такие супер-пупер-универсальные навороченные шаблоны или макросы, призванные решить какие-то сверхабстрактные задачи, никакого отношения к целевой задаче не имеющие. Достаточно посмотреть добрую половину библиотек Boost, куда попадают такие образчики. А ведь кроме Буста, люди практически в каждом проекте свои такие вот велосипеды пишут…

Это все говорит о том, что навороты С++ идут куда-то не туда. Вероятно, из-за обратной совместимости — сначала с Си, а затем и с предыдущими версиями С++ — ничего уже не сделать. В том числе поэтому я понемногу проектирую свой язык программирования, не менее низкоуровневый чем Си, не такой сверхстрогий как Rust, и включающий практически всё из большинства современных языков, но в максимально продуманном и интегрированном виде.
Разработку нового языка программирования я бы советовал начинать не с компилятора, а с плагинов к популярным IDE и менеджера пакетов.
Именно они обеспечивают 90% продуктивности, а весь синтаксический сахар — от силы 10%.
Потому что самый быстро создаваемый код — это код, который уже написан. Реиспользование кода должно ставиться во главу угла в современном мире. Прошли времена, когда каждый писал софт от и до. Поэтому если инструментарий языка не позволяет быстро собрать программу из готовых кусочков, а IDE — быстро разбираться в этих кусочках, если вдруг к ним нет внятной документации, то такой язык в конечном итоге будет неинтересен даже собственному создателю. В нём просто нет смысла в XXI веке.
Разработку нового языка программирования я бы советовал начинать не с компилятора, а с плагинов к популярным IDE и менеджера пакетов.
Именно они обеспечивают 90% продуктивности, а весь синтаксический сахар — от силы 10%.

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

Хотите сказать, что автодополнение — костыль? Что же в таком случае является правильным решением той проблемы, которую решает автодополнение? Неужто однобуквенные идентификаторы?

Да ладно бы автодополнение идентификаторов просто чтобы не писать длинные имена каждый раз и не делать опечаток. Это решается каким-нибудь тупым ctags успешно.


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


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

Хотите сказать, что автодополнение — костыль?
А что, Вам автодополнение обеспечивает 90% производительности? Ну тогда да, костыль.
Современное программирование — это не столько про написание собственного кода, сколько про чтение чужого. В XXI веке писать собственные велосипеды уже непродуктивно, поскольку реализации большинства алгоритмических задач уже не только написаны, но и отполированы до блеска.
Поэтому и продуктивность определяется главным образом тем, насколько быстро программист может читать чужой код, ориентироваться в нём и прикручивать к собственному проекту.
При таком раскладе IDE даёт просто чудовищный буст к продуктивности, поскольку упрощает ориентирование по коду.
C, C++ просто инструменты для создания sw продуктов, у обоих есть положительные и отрицательные качества и своя область применения, в смысле синтаксиса, в контексте информатики как науки, imho оба языка достаточно простые, если бы нужно было делать новый язык программирования, начал бы с изучения algol-68, не для того, чтобы его расширить, а просто для самообучения, чтобы использовать как reference point,
ps
пожалуй главное, что не нравится в C++ это иллюзия объектно ориентированного языка,
чем симпатичен C — это своей честностью «типа недалеко ушел от ассемблера и этим горжусь», и широтой применения
В IIRC C++

может быть «Если я правильно помню то в C++»… ( IIRC это аббревиатура if i remember correctly )

Интересно ход мыслей переводчика. Подумал наверное что это програмистский термин =)

Апд. Или я пытаюсь понять ход мыслей АИ сейчас...

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

Всегда очень много слов про всякие UB, неявные преобразования и прочие «i++ + ++i». Да наплевать. Я даже не знаю как работает *p++, просто всегда пишу *(p++) или (*p)++. Си — язык инженеров, а не снобов-перфекционистов.
НЛО прилетело и опубликовало эту надпись здесь
Надо проверять в компилтайме. Как это на С сделать?

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

struct mem {
    char data[0];
}

Как мне это сделать в молодежном питоне?

Это до первого ночного звонка или дебаггинга длиной в неделю из-за того, что компилятор что-то наоптимизировал в вашем инженерном коде.

Число прецедентов стремится к 0 в системной разработке. Скорее будешь неделями искать какого «волшебного» бита в регистре не хватает и о чем молчит документация вендора.
НЛО прилетело и опубликовало эту надпись здесь
Не, не обратно. Я про питон даже не вспоминал, так что непонятно, причём он тут.

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

То есть, С — это язык для проектов

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

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

а вот компиляторы на С я бы точно писать не стал

Как бы вам сказать… Есть такой, например. Применяется широко и исходно на Си. А вот когда себя из исходников соберет, вот тогда и плюсами может побаловаться. Для утилитарной библиотеки, например.
НЛО прилетело и опубликовало эту надпись здесь
Но вы же его длину так или иначе знаете, пусть и в рантайме? Тогда, в теории, вы можете статически проверить, что вы никогда не выйдете за границы этого сегмента. Даже если вы длину узнаёте только в рантайме.


В общем случае не знаю. Жестко дан только максимум буфера, а не его фактическое состояние. Пример:

Исходный буфер (размечен структурой выше + uint64_t len):
[ <-------------------- len --------------------> ]

Сейчас в нем сообщение поместилось:
[ header(sz<len/2) ][ data(sz<len/2) ]

а завтра уже нет:
[ header(size<len/2) ][ <-------- data(size>len*2) -------> ]

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

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

А вот если вам нужно сделать компилятор какого-нибудь предметно-специфичного языка, или proof of concept, или что такое, то писать это на сях — чистой воды мазохизм

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

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

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

А зачем вам обязательно сырые указатели-то? Большинство проблем уходит если рядом с указателем хранить размер буфера.


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

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

Тут случаем нет ремарки вроде: «пока не пришел какой-нибудь garbage collector»?

От языка зависит. В C# и питоне такой ремарки нет (конкретно для буфера). В Rust нет никакого GC.

В смысле в C# нет? Там же mark and sweep сборщик. Чем больше живых объект, тем больше ресурсов тратит сборщик мусора. Ему этот буфер надо каждый раз найти и пометить живым. Подозреваю, что в питоне будет тоже самое, раз там есть mark and sweep сборщик для разрешения циклических ссылок.

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

У стандартной реализации Python есть mark and sweep сборщик, но он не так давно стал обязательным. В разных мелких версиях для микроконтроллеров его нету.

Потому лишь последний и внедряется в линь в экспериментальном порядке. Буфер же тут не единственный дискуссионный момент.
НЛО прилетело и опубликовало эту надпись здесь
И текущий size вы не знаете? Даже в рантайме? Как вы тогда понимаете, сколько там от заголовка, а сколько — от тела?

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

Думаю, что все это не так важно. Не вижу путей как тут должен компилятор в compile-time контролировать границы.

Я вообще начал с того, что непонятно, что вы считаете системным уровнем

Парой постов ниже отписался.
НЛО прилетело и опубликовало эту надпись здесь
Что значит системного уровня? Ядро и драйвера? Да, кроме раста нет ничего. Но нынче системное программирование это в том числе базы данных, компиляторы и прочий подобный софт. Уж это все пишут на каких угодно языках и С там никому не сдался.
Хороший вопрос, спасибо. Ответ зависит от того, что считать системой. Если программные системы — то и СУБД можно считать системным уровнем. Если говорить о программно-аппаратных системах, то я бы сказал, что всё, что выходит за прикладные интерфейсы ядра/драйвера — прикладной уровень. Про драйверный код ремарка не случайна, поскольку он есть в LLVM и отсутствует в GCC. Граница немного плавает.

В дискуссии я исхожу из второго подхода.
Число прецедентов стремится к 0 в системной разработке
Серьёзно? Расскажите мне про компанию, где бы это было совмещено с ежемесячным обновлением компилятора (как, скажем, Android разрабатывается).

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

Но это значит, что вы пишите не на C, на конкретно «GCC C версии x.y.z сборки Васи Пупкина».
Серьёзно? Расскажите мне про компанию, где бы это было совмещено с ежемесячным обновлением компилятора (как, скажем, Android разрабатывается).


Вы хотите чтобы я еще и антитезис себе подобрал, серьезно?

Для начала стоит понять зачем всё это в системной разработке. Микрокод, драйвер, BSP,… пишутся под конкретное окружение и адаптируется под конкретные вариации инструментов. В будущем появляется необходимость перенести наработки в другое окружение => портируем, адаптируем и т.п.

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

У нас с вами разное обычно. У нас в конторе используется GCC 4.4.2 (отходит в небытие), 4.8.3, 8.3.0. В дополнение к нему LLVM/clang, версию увы не помню, поскольку он пока в экспериментальной стадии. И МЦСТ/LCC версий аутентичных версиям CPU. При этом компиляторно зависимые макросы нужны преимущественно последнему, так как отдельные builtins не поддержаны.

Вы уж меня извините, но вопрос стоял не: а хотители ли вы заменить компилятор, а можете ли вы это сделать.

Если не можете - значит ваша программа имеет очень мало отношения к языкам C и/или C++.

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

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

В 90е было можно: просто вкорячиваешь туда через PEEK/POKE машинный код и вызываешь. Делов-то. У конкретного интерпретатора все структуры по фиксированным адресам (они даже в книжках в те годы печатались).

Всё? BASIC у нас теперь стал высокоуровневым ассеблером, да?

Вы уж меня извините, но вопрос стоял не: а хотители ли вы заменить компилятор, а можете ли вы это сделать

Не вижу таких проблем в реальных проектах. Регулярно собираем объектники LLVM и линкуем в GCC. Нативно компилируем постоянно и свои проекты и опенсорс. Без примеров говорить тут особо не о чем, больше похоже на какие-то фобии.

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

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


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


template<size_t N>
struct mem {
    char data[N];
}

// …

struct buffer {
    size_t len;
    mem<len> *ptr;
}
На C/C++ можно, ибо контроль границ отсутствует. Дискуссия о контроле границ массивов. Выше пример давал — границы приходят извне.

Какая разница — извне они приходят или нет? Важно что перед созданием буфера они в любом случае известны.

Мне кажется, инженер должен знать, как работает его инструмент.

А инженер это фигак фигак и в продакшн? В моей компании полно легаси С кода, но проблема не в этом, проблема в том что каждый новый кусок С кода который попадает мне на ревью содержит ошибки работы с памятью. При этом не важно сколько опыта у человека - КАЖДЫЙ пуллреквест или течет или сегфолтится при определенных условиях. А почему? Потому что писать нормальную обработку ошибок на С это попаболь. Поэтому даже людей которые не особо в C++ рубят я по возможности заставляю писать С++ код. Естественно там можно тоже накосячить но правильно написанный код хотя бы имеет шансы влезть в экран.

«Нормальная» обработка ошибок — это эксепшены поди? Эти прыжки в обход структуры кода наподобие всеми оплёваного goto? Спасибо, не надо. Посмотрел код возврата, принял решение что делать дальше. Просто, наглядно, надёжно.
гг, хорошо что мне никакие снобы не командуют на чём писать. Я и асм юзаю более чем регулярно. На асме у меня куча вещей, которые на ЯВУ не влезли бы в доступные такты в разы.
Просто, наглядно, надёжно.

Код освобождения ресурсов у вас тоже накопирован повсюду, чтобы было «Просто, наглядно, надёжно»? Да, к сожалению, goto единственный способ сделать нормальную обработку ошибок в С. Потому что проблема не в обработке, а в очистке ресурсов. Си не нужны исключения, ему нужен defer.

gcc аттрибут cleanup.

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

gcc это не С. Но да, в проекте для iOS я прекрасно пользуюсь расширениями для реализации полноценного defer. Просто потому что это iOS и думать о других компиляторах и платформах мне не нужно.

Про необщность ответа было в дважды протраченном тексте :-)

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


auto arg = something();
int result = call_me_will_return_int(arg);

и то же на С


// это же С так что объявляем всё сверху, конечно сейчас это не обязательно но всё ещё повсеместно встречается
int result;
double arg1;
int error_code;
// 300 строк спустя
error_code = call_me_will_return_int(arg, &result);
if(error_code != OK)
{
  LOG_ERROR_CODE(error_code);
  // return ? 
  // а вот нет, пиши ифы до самого конца - снизу руками чистится память
}

Одно только


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


Ну и про "такты", если вы пишете под МК то возможно вы правы, я пишу под Xeon и я отыграл 8% времени исполнения кода переписав его с СИ на плюсы. И это был уже оптимизированный до меня код числодробилки откручивающийся на 16мс. Разумеется не смена языка дала прирост, но те оптимизации которые С++ позволил сделать раздули бы СИ код раза в 4, в то время как результирущий С++ код стал меньше в 3 раза.

Не возможно, а именно что правы, порой под МК лучше вообще побольше ассемблерных вставок сделать, чем материться от того, что память кончилась. Такой просчёт тактов и объёмов памяти даёт возможность поставить более слабый и дешевый(не в реалиях повидлового дефицита нынешнего правда) МК, что даёт неплохую экономию на серии, что позволяет либо сэкономить на цене комплектухи, либо за те же деньги поставить более качественную пасивку и поднять срок службы.
Увы, но сейчас становится модным «а давайте Cortex-M4 втулим в термостат, наговнокодим и приколхозим питон в мк», что очень сильно огорчает.

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

Согласен, для ответственного применения(а КМК доменная печь от кривой работы термостата может испортиться до того, что дешевле будет новую поставить/построить) я бы поставил industrial grade, может даже ещё более старый (8051 чем не вариант) и написал бы на ассемблере, добавив мажоритарное резервирование в аппаратной части. В случае авиационного применения(естественно не доменной печи) — может быть даже на разных архитектурах МК.
Мне один чел рассказывал, какие в аэрокосмической отрасли на Западе нынче используются подходы к программированию. Главная установка: «не писать код».

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

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

Кроме того, используется тестирование вида «Hardware in the loop». Для каждого блока управления создаётся испытательный стенд, который подаёт ему на вход тест-сигналы и сравнивает выходные сигналы с эталонными.

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

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

А вот по этим критериям уже не факт что графические языки программирования выигрывают.

если у вашей платформы нет системы раскрутки стека


Перед такими заявлениями почитайте про setjmp(). Для порядка. К слову, исключения в плюсах до сих пор местами сделаны через него.

А кто сказал что я о нём не знаю? От setjmp до раскрутки стека как от меня до луны.


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

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

Вот я что то сомневаюсь, пруфы есть?

Погуглите «SJLJ» в контексте разных компиляторов. Не единственный вариат, как я и сказал, но не лишенный своих преимуществ.

А, так вы о деталях реализации раскрутки стека, я как то и не понял когда вы функцию setjmp() упомянули, это разные вещи всё таки.
Принципиально вообще никакой разницы какой там механизм раскрутки стека, главное что он есть, SJLJ есть только в С++, в Си ничего подобного нет, и память будет течь если накосячить, вот пример: https://godbolt.org/z/4ra1obh6G

вместо goto можно же использовать отдельные методы, да и читабельность повысится.

void SomeProcess(int* data) {
    if (Func1(data) == 1) {
        return;
    }
    ....
    if (Func3(data) == 3) {
        return;
    }
}

int main() {
    int *data = (int*)malloc(100 * sizeof(int));
    SomeProcess(data);
    free(data);
}
А как быть, если какой-то функции нужно получить несколько ресурсов, и получение любого из них может сломаться?
Например, память и файловый дескриптор
void SomeProcess(int *data) {
FILE *f = OpenFile(1);
void *mem = AllocateMemory();
Func1(mem, data);
Func2(f, mem);
CloseFile(f);

f = OpenFile(2);
Func3(mem);
Func2(f, mem);
CloseFile(f);
FreeMemory(mem);
}

вариант для вашего примера
void MemProcess(void *mem) {
	FILE *f = OpenFile(1);
	if (FileErr(f)) {
		return;
	}
	Func1(mem, data);
	Func2(f, mem);
	CloseFile(f);

	f = OpenFile(2);
	if (FileErr(f)) {
		return;
	}
	Func3(mem);
	Func2(f, mem);	
}

void SomeProcess(int *data) {
	void *mem = AllocateMemory();
	MemProcess(mem);
	FreeMemory(mem);
}
К сожалению, неэквивалентный результат. Мне стоило уточнить, что я брал принятый в Go порядок аргументов — сначала приёмник, потом источник.
Func1 пишет в mem и читает f. Func2 пишет в f и читает mem. Func3 пишет в mem.
извините, но я не понял вас.
Основной посыл, что вместо использования goto, аллоцировать/открывать до отдельного метода, а освобождать/закрывать после него. Это позволяет внутри метода не писать сложные if-ы, и выходить из метода в произвольном месте
Такой подход не сработает, если для работы метода нужно больше одного ресурса, и аллокация каждого из них может сломаться. Если любой из ресурсов не удалось выделить, то не надо выполнять полезную работу и надо освободить все те ресурсы, которые уже удалось выделить.

Вот пример реального кода:
git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/accessibility/speakup/kobjects.c?h=v5.12.12#n1004
НЛО прилетело и опубликовало эту надпись здесь
Это Maybe, Either и прочие трихомонады.
Вроде можно, но получается дороговато: много прыжков и выделения памяти.

А ещё компилятор по рукам не ударит если вы вызовете divexn без всех этих обёрток.

А почему автор заявляет, что любит C, и не любит кресты и при этом ноет на тему sequence points и aliasing? При чем вообще кресты в этих двух вещах? Это же чисто сишные вещи, всегда ими были и остаются (оставим за бортом тему реализации в компиляторах, не факт что без крестов было бы лучше).

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

И вообще, как известно, программисты делятся на две категории: тех, кто ненавидит C++ и пишет не на нём, и тех, кто ненавидит C++ и пишет на нём.

Кто постоянно пишет на С++, скажите, а в обратной совместимости с С остался какой-то смысл? В 1993 он был, но, имхо, достаточно быстро потерялся.
да, т.к. многие плюсовые проекты притягивают сишные либы as is, часто в виде исходников. Плюс к тому необходимость в обратной совместимости самих плюсов тоже никуда не девается.
Мне кажется, что С++ тащит очень много легаси, за счёт чего он такой эклектичный. Как думаете, есть вариант, что дропнут обратную совместимость без костылей, чтобы упростить и упорядочить язык?

Это обсуждают уже лет 8, в 23м стандарте это не произойдёт.
Есть надежда на модули — если они взлетят это может стать границей раздела старого и нового кода, но я бы раньше 30го года не загадывал.

но я бы раньше 30го года не загадывал.


Вы шутите что ли? Я своими глазами читал утверждение, что взят коммитмент на переделку стандартной библиотеки на модули к С++23…

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


#include <iostream>
import <iostream>

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

Да и собираются просто перенести всю стандартную библиотеку в модули


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

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

- есть, конечно. Кажется, это называется "Раст"... Ну, или C# - как варинат. Go еще не дорос...

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

Ну, или C# — как варинат. Go еще не дорос...
Ни C#, ни Go не годятся, так как полагаются на сборщик мусора.

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

Ничего, привык за пару месяцев. Более того, мне понравилось! Оказалось, без контейнеров и умных указателей прекрасно можно жить; более того, нет расхода ресурсов контроллера на все эти модные вещи! Сейчас работаю с довольно большими проектами на сотни тысяч строк C-кода. Ни одного malloc! Вся память выделяется или статически, или на стеке, или из пулов большими блоками.

А когда пришлось снова писать код для PC, и была возможность опять взять C++ с его умными указателями и контейнерами — я отказался от этого. Прикинул и понял, что все задачи можно решить теми же приёмами, которые используются в микроконтроллерах. И код вовсе не потеряет от этого в красоте и лаконичности.
MichaelBorisov,, а может статью о приёмах работы? Советы опытного специалиста многого стоят.
Да я ничего не изобрёл, просто освоил приёмы, десятилетиями применявшиеся до этого в C-проектах. Многое почерпнул, изучая исходники ядра ReactOS, ещё LwIP. Это другая философия, другой мир, отличный от «плюсов».

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

Я пишу для микроконтроллеров на С++

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

А ООП можно и на C. Структуры вместо классов, вместо виртуальных функций — явная реализация Vtable (хотя мне не приходилось пока прибегать к этому приёму).
НЛО прилетело и опубликовало эту надпись здесь
по опыту, в красоте и лаконичности код таки потеряет

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

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

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

Особенно если вам нужна производительность, и в плюсах вам можно было бы использовать темплейты

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

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

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

Появление в C++11 rvalues и move-семантики привело к тому, что надёжный код без учёта лайфтаймов писать стало невероятно сложно, компилятор про них ничего не знает (в простых случаях может вывести, но в сложных случаях нужна помощь зала программиста), а если поменять логику и заставить программиста их указывать явно…

Это называется Rust и уже давно сделано.
Да. Сейчас С — это lingua franca программирования (почему, кстати, к С и такие претензии). То есть, если вы хотите связь между чем угодно и С++, то стандартный переход идёт через C. Например, если взять Haskell и С++.

Часто есть и «прямые пути», например, Rust-C++ или Ocaml-C++ (в частной лавочке). Но что угодно с чем угодно — это через С.
Ну тут в большей степени спасибо надо сказать C ABI. Наверное единственный надежный и совместимый интерфейс между языками. Из плюсов можно был бы и убрать совместимость с С на уровне кода, но оставить FFI для C ABI. Возможность именно вставить кусок С кода в плюсовый код это скорее бонус, чем необходимость, как мне кажется.

Вопрос в том, кто будет делать заголовочные файлы библиотек для этого нового C+=2? Если поддержку C из языка дропнуть, то хедеры придётся переписать. Если же её оставить, то куски на C так и продолжат использовать, просто через приседания с созданием собственного заголовочника с кодом

Вопрос в том, кто будет делать заголовочные файлы библиотек для этого нового C+=2?
Никто не будет. Нужно использовать подход Swift: информация об ABI просто зашивается в библиотеку, при сборке библиотека открывается и эта информация извлекается.

На платформах вне macOS/iOS процесс идёт, но медленно.
Именно так. С код компилируется в либу, к ней пишется header файл с прослойкой для FFI. Все работает и даже быстро. Собственно, даже вон C# и Go с этим справились. Первый даже заголовочные файлы не требует писать, все описывается атрибутами функций.

А в идеале да, как выше пишут. Вся информация должна быть в самой библиотеке. Как вот упомянутый C#. Чтобы подключить либу надо просто, внезапно, подключить либу. Вся информация из нее будет извлечена автоматически. Даже комментарии можно приложить в файле рядышком. Хедер файлы это ошибка дизайна, которую С++, по крайней мере, намерен исправить.
Первый даже заголовочные файлы не требует писать, все описывается атрибутами функций.

На самом деле это недостаток.

И в чем он заключается?

Приходится вручную конвертировать заголовочный файл в P/Invoke-декларации, по 10 раз перепроверяя всё.


Собственно, это проблема любого FFI в мире, где Си является стандартом.

Не так уж оно и страшно с учетом, что мы пытаемся снюхать managed язык со сборщиком и Си. В Go вон тоже совсем некрасивые вещи надо творить. И даже при наличии хедер файла это не помогает, т.к. прослойку все равно надо писать руками из-за упомянутого кардинального различия языков.
Кстати, в Microsoft работают над кодогенерацией для этого дела. Как для general purpose библиотек (биндинги к libClang там self-hosted), так и для Win32 API
НЛО прилетело и опубликовало эту надпись здесь
Достаточно посмотреть добрую половину библиотек Boost, куда попадают такие образчики.

А ведь в Бусте качество кода выше среднего. Груз совместимости с С это гораздо меньший груз, чем груз совместимости с имеющимся языком шаблонов. Улучшать что-то в нём наверное бессмысленно, так что даже если когда-то будет создан новый язык шаблонов для С++, поддержку старого придётся всё равно оставить…

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

Кстати о читаемости кода. Ценность читаемости и простоты понимания кода трудно переоценить.

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

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

пришлось много такого гуано видеть, и читаемость плохая тоже, C++ дает возможность код прилично засорить подобными вещами, когда команда большая и люди амбициозные бывает, выдумывают, иначе как свою незаменимость доказать, не помогает костыль, да и где его возьмешь, одни мечты :)
По большей части, когда дело доходит до выбора между C и C++, я все, что не прибито гвоздями к C++-библиотекам, пишу на C. Тут, по-моему, не указан самый главный выигрыш C — цена поддержки. Настоящее преимущество над плюсами начинается, когда берешь в руки gdb и профайлер. Особенно на чужом коде.

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

правильно конечно, справедливо везде, где серьезный debugging необходим, jtag debugging особенно, часто gdb недостаточно
Сразу скажу то, что, пожалуй, и так все знают: нет такого понятия, как «самый лучший язык программирования».

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

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

Однозначно несколько языков.

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

немного подумав, мне бы хотелось иметь единый высокоуровневый язык описания hw архитектуры платформы (включая cpu, и пр), желательно интегрированный с языком программирования, imho не вижу почему это нельзя сделать, вместе с системой генерации на основе описания сначала компилятора, и toolchain, а затем всех необходимых images, и executables

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

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

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

Такой подход к добру ну никак привести не может.

В идеале языков должно быть много, но в реальности есть только один подходящий на замену: Rust. И как сделать так, чтобы его не смогли извратить, если народ массово с C и C++ будет убегать - вопрос непонятный пока.

Конечно несколько. С++ как раз жертва желания засунуть все подряд в один язык, чтобы на нем можно было писать чуть ли не симуляцию вселенной. Не работает оно так. Поэтому у нас есть раст для ядра и драйверов и прочей системщины, чтобы заменить C/C++. Есть Go/Java/C# и прочие для бэкэнда. Javascript/Typescript для фронта.
Знаете, когда я смотрю историю джаваскрипта, мне кажется, что где-то в кресле раскачивается полубухой Брендан Эйх и приговаривает «В 95м я не это имел в виду!». С ван Россумом, имхо, сходная фигня: человек писал «универсальный клей», а тут кааак попёрло.
Че поделаешь. Ничего лучше для UI нет даже для десктоп приложений в плане мощи и универсальности HTML/CSS. Вот на бэкэнд его засунули непонятно зачем, это да. Сейчас вон flutter пытается мир захватить, может чего из него получится.
посмотрел rust наконец, описание на их сайте, приятное впечатление производит, интересно кто-нибудь его испоользовал из читающих?
что-то мне кажется, что его область применения далеко не исчерпывается ядром и драйверами, поживем посмотрим

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

Я пока писал простенькие поделки “для себя”, ничего в прод пока не запускал (он в Android сильно недавно появился просто: https://security.googleblog.com/2021/04/rust-in-android-platform.html, а я сейчас для Android код пишу по работе).

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

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

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

Мне кажется, что мы наконец должны отказаться от сишной модели абстрактного вычислителя и при создании языка явно учитывать:
1. Память не плоская: есть кэши, есть видеопамять, есть оперативка.
2. Процессоров несколько, возможно, что и с разными задержками доступа к памяти
3. Многопоточная программа это норма.
Я вот хочу удобный язык с data flow парадигмой.
Память не плоская: есть кэши, есть видеопамять, есть оперативка.

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

Многопоточная программа это норма.

Вот это точно нужно и вроде все адекватные языки это учитывают. С/С++ вон тоже дозрели. А вот все что глубже ну его нафиг. Пусть разрабы железа сразу привыкают, что если делаешь многоядерную многопроцессорную железку, то она должна быть кэшкогерентной.
Имхо, это обязательно нужно, чтобы уйти от UMA. В 70х никто о кэшах не думал, доступ к памяти и к регистру был примерно одинаков по цене в тактах, а сейчас на системном уровне хорошо бы учитывать в явном виде. И как бы мы ни плодили каналы проц-оперативка, доступ к памяти останется узким местом.
если делаешь многоядерную многопроцессорную железку, то она должна быть кэшкогерентной
Нахрен. Вот просто нахрен. Кэшкогерентность это тоже часть легенды о плоской памяти. Вы листали даташит на интеловские процы, этот милый талмуд на 10к+ страниц? Из-за обилия хаускипинга процы получаются дорогие, сложные, и нельзя посчитать за сколько в тактах выполнится твоя программа. Если у нас будет язык, удобно позволяющий работу с независимыми данными, не так важно, SIMD или MIMD, то и у процов получится сбросить часть груза легаси.
Каким образом языку об этом всем заботиться? Я даже не представляю, как это может выглядеть. Заниматься когерентностью кешей на уровне софта? Производительность умрет, мы наоборот пытаемся как можно больше в железо убрать, потому что на текущих скоростях никакой софт не справляется.

Кэшкогерентность это тоже часть легенды о плоской памяти

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

Единственный, кто может об этом всем заботиться, это компилятор и никто более. Язык программирования, если он универсальный и переносимый, всегда будет опираться на абстрактную машину.
Мы говорим о высокопроизводительном системном языке с прямым управлением памятью. Ну и рассматривать язык отдельно от его компилятора и, возможно, VM — неверно, так что когда я говорю «язык», я имею в виду всю связку.
Заниматься когерентностью кешей на уровне софта?
Я предлагаю её вообще отбросить на уровне L1 и L2 как минимум, просто вешать лок на данные: копирование, фрагментация и монопольное использование. Наиграется — вернёт в копию по индексу :)
Каким образом языку об этом всем заботиться? Я даже не представляю, как это может выглядеть
Есть какой-никакой компилятор под EPIC, под стековые машины, под мультиклет, есть, правда, пока в академии, автоматические балансировщики между CPU и GPU. Я бы не взялся, но задача не выглядит нерешаемой.
При этом никто не мешает затачиваться под кэши в текущих языках.
Это на пальцах прикидывать, с профилировщиком и дебаггером сидеть, потому как нет такой сущности на уровне языка (собсно, и ключевое слово register в С. Не, через асм можно в кеш влезть, но это чудовищно геморройно.
Производительность умрет, мы наоборот пытаемся как можно больше в железо убрать, потому что на текущих скоростях никакой софт не справляется.
Наоборот, железо не догружается и простаивает: программы по умолчанию однопоточны и при этом упираемся в контролер памяти. Т.е. сидит одинокое ядро и ждёт данные.
Мы говорим о высокопроизводительном системном языке с прямым управлением памятью. Ну и рассматривать язык отдельно от его компилятора и, возможно, VM — неверно, так что когда я говорю «язык», я имею в виду всю связку.

Ну в таком случае все и так есть. Архитектурные оптимизации компиляторы умеют.

Я предлагаю её вообще отбросить на уровне L1 и L2 как минимум, просто вешать лок на данные: копирование, фрагментация и монопольное использование. Наиграется — вернёт в копию по индексу :)

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

Это на пальцах прикидывать, с профилировщиком и дебаггером сидеть

Тем не менее все оптимизируется и работает.

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

Если бы все было так просто. Нет, железо физически неспособно прокачать 100гбит, 400гбит. На такие скорости способны только ASIC и FPGA. Ничего другого в свитчах такого класса не увидишь. Поэтому люди начали придумывать smartnic, DPU. Все облачные провайдеры сидят на этих штуках.
Производители железа и интерконнектов с этим не согласны. Они для высокой производительности хотят полную когерентность в железе. При чем даже между разными устройствами через CXL протокол. Я с ними согласен.
Я догадываюсь, что по любой специальности можно попасть в поисковый пузырь, а потом начать форумный бой на ровном месте, но таки спрошу: вот зачем это дорогая и неуправляемая программистом процедура? Насколько я понимаю, это потому, что многопоточные приложения писать достаточно сложно, им приходится память вручную нарезать. При этом в хаскеле параллелизм значительно проще, чем в openmp — из-за ФП, который на параллелизм лучше ложится. Да, ценой перерасхода памяти, но это приемлемая жертва.
Потому что еще дороже обходится то, что приходится делать сейчас. Вручную в софте и дровах гонять туда сюда данные через маленькие окошки в памяти без возможности их кэшировать ни на одной стороне. Это если мы говорим об интерконнекте между устройствами.

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

Что до ФП, хаскел имеет параллельный GC. Ему без примитивов синхронизации и атомиков никуда, а это все полагается на когерентность кэшей как раз, чтобы оно работало быстро и корректно. Что там еще компилятор и рантайм творит черт его знает. Это код хаскела весь такой красивый функциональный, а внутри может твориться черти что.
Всё-таки не понимаю, почему не считать переменную «протухшей» в момент после обращения к ней. Если нам нужна данная конкретная переменная в данный конкретный момент времени, то почему не сделать её копию? Почему при обработке массива несколькими ядрами (каждому ядру своя часть) нам должно быть важно в каком порядке части обрабатываются? Ну и формализм автоматов и data flow не ломается без когерентности.
Это код хаскела весь такой красивый функциональный, а внутри может твориться черти что.
Мне этого вполне достаточно, тем более, что внутри gcc тоже адЪ и Израиль :)
Я просто к тому, что очень мало кода, который реально параллельный и независимый. Намного больше кода обмазано мьютексами и атомиками. И там заниматься еще синхронизацией кэшей — ну нафиг. Поэтому у нас вот и получается. Для общей логики сложные процы с кэшами и всем на свете, а для скорости ASIC и FPGA, где вообще все другое. И сейчас еще тренд делать решения вроде DPU и smartnic, когда на борту есть и то, и то другое, для контрол и дата плейна соответственно.

Насколько помню IBM Cell был попыткой сделать такой вот проц для параллельной обработки данных, где есть одно PowerPC ядро обычное и куча независимых векторных ядер со своей локальной памятью на общей кольцевой шине. Программить это чудо было такой пыткой, что проект закопали.
НЛО прилетело и опубликовало эту надпись здесь
Не, если речь про расслабить модель памяти, то я только за. Я думаю арм успеешен не в последнюю очередь именно благодаря этому. Это и на код практически никак не влияет. А вот убирать когерентность кэшей как тут предлагали — нафиг.

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

Есть у кого-то реальные исследования? Я боюсь, что в разных областях ситуация будет сильно разной.

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

Программить это чудо было такой пыткой, что проект закопали.

Проект закопали потому что было мало эксклюзивов, а на кроссплатформенных играх все эти дополнительные ядра простаивали, а в результате картинка на XBox 360 была красивее.

Экслюцивы на PS3 были отличными, только их мало было.

Это чисто умозрительное заключение, но думаю оно вполне очевидно. Большинство кода пишется вообще однопоточное. Маленькая часть кода многопоточная и еще меньшая часть многотопочная в смысле shared nothing, когда потокам не нужно между собой общаться. Понятное дело, что HPC и игры это последний случай, но это маленькая часть всего софта, а язык у нас все таки общего назначения. Надо язык под эти конкретные применения — вот там можно затачиваться на какие угодно кэши.

Проект закопали потому что было мало эксклюзивов, а на кроссплатформенных играх все эти дополнительные ядра простаивали, а в результате картинка на XBox 360 была красивее.

Не, там было все сложнее. Может в начале целл и был причиной, но потом с ним освоились и уперлись в другие ограничения архитектуры пс3 — его убогий гпу и архитектуру памяти. У xbox 360 был чуть ли не GPGPU, UMA и EDRAM, который творил чудеса.

А закопали проект вообще все. И сони, и ibm. Проц реально было очень сложно использовать. Он был конечно быстрый, но оно того не стоило. Отсутствие предсказателя переходов и внеочередного исполнения тоже не помогало, но этим страдал и xbox 360.
Я думаю, что независимого кода достаточно много хотя бы за счёт частичного применения, это не упоминая игры, которые упоминали выше, и числодробилки. Ну и на GPU я вижу многоуровневую памятьи параллельные вычислители, но не вижу кэшей. Писать поначалу больно, но достаточно быстро привыкаешь.
Кэши у GPU есть — тот же RDNA имеет L1 и L2. Но это все же специализированные процессоры, на которых по-другому просто и не пишут.
Хорошо, скажем иначе: кэши у них не обязательно есть :) Боль вызывает то, что язык не поддерживает по умолчанию многопоточность, и приходится вместо
a=b+c
писать
for (size_t k = 0; k < n ; k++){
a[k]=b[k]+c[k]
}
Ну так то и CPU без кешей жить может, но все современные их используют. Что нвидия, что амд, у GPU кэши многоуровневые уже хрен знает с каких времен. Даже мобилки я вот смотрю тоже по несколько уровней кэшей имеют. Тут скорее вопрос, а что у вас за хитрый GPU такой без кэшей?
Да, виноват, устарели мои знания. Я про отсутствие кэшей был изначально неправ, а сейчас у них уже и когерентность приподняли.
НЛО прилетело и опубликовало эту надпись здесь
Хм, а как строить граф живых объектов полностью параллельно? В любом случае структура данных будет общая. Ведь между чанками все равно будут зависимости. Судя по коду GC, синхронизация там повсюду какая-то происходит. Тоже самое в аллокаторе. Даже глобальный лок один используется на весь storage manager, в том числе манипуляции с nurseries. И это даже с учетом, что у хаскеля есть какие-то capabilities, которые позволяют использовать независимые друг от друга nurseries для каждого из них.
НЛО прилетело и опубликовало эту надпись здесь
вот, таки начинаем к интересным вопросам подходить, imho по мере усложнения все хуже получается, если продолжать разделять на hw-fw-sw, типа делаешь hw описание контроллера прерываний, тут бы и логику обработки прерываний добавить уместно, а не тащить это отдельно в sw компилятор, такие мечты примерно, а что в железе быстрее меняется это ведь от архитектуры, типа mb обеспечивает интерфейс картам, по-другому не умеем, и вообще шина есть еще один bottleneck, так примерно

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

правильно пишите, только в 70x тоже думали, и еще как, лично присутствовал,
пух и перья летели, когда cache memory для виртуальной памяти обсуждался :)
Это что-то уже за гранью языков программирования. Оптимизировать код под железо никто не мешал и не мешает. Все это делают. Вопрос в том, что в абстрактной машине самого языка этого быть не должно. Иначе начнутся проблемы, когда кто-то в очередной раз захочет иерархию кэшей поменять или добавить новый тип памяти. Эти вещи именно должны быть разделены. Язык просто не должен мешать потом пользоваться преимуществами железа. Он и не мешает.
вполне понимаю вас, только эта самая абстрактная машина языка не сильно движется последнее время, если не сказать сильнее, вопрос сколько это может продолжаться?
ps
imho, возможности технологии уже другие
А надо ей двигаться? Я вот честно не вижу причин. Железо развивается, код под него пишется. Т.е. прогресс идет и никем не блокируется. Вот у нас появился fungible DPU, где как раз data flow архитектура, строится граф обработки данных. И все это на mips и С библиотеках. И ничего, могут себе позволить сотни гигабит ворочить этим всем благодаря тому, что С только управляет, а реальная обработка делается специальными фиксированными блоками логики. Т.е. имеем ту самую синергию железа и софта. И никакой x86 или arm сервак с сотней ядер такое не может.
если ради движения только, то однозначно нет, вопрос есть ли в этом потребность?
вашу точку зрения кажется понял, «потребности нет»,
теперь про fungible, это интересная архитектура, для data centers эффективная, тогда если принять будущее = cloud computing, то получается один ответ, но если к примеру будущее >> cloud computing, то imho другой, как будет на самом деле конечно никто не знает, но я бы поставил на «будущее >> cloud»
Пока да, все будущее у нас диктуют облачные провайдеры и HPC. Они пишут стандарты и под них делается передовое железо. Пока что тренд на дезагрегацию всего и вся, а соединялось бы все ethernet фабрикой. И видно, что обычные процессоры не справляются со скоростями этих фабрик, поэтому имеем повсюду ASIC и FPGA. Поэтому я пока не вижу потребности новых языков. Процессоры общего назначения у нас и так не тянут, поэтому на них перекладываются задачи только управлять этими ASIC и FPGA, а на это много ресурсов не надо. Где надо много вычислений, то делается это опять же на них или GPU, TPU, DPU. Т.е. все вычисления уходят с центральных процессоров. Хочется распределенную файловую систему — на DPU ее. Хочешь базу — на DPU или computational storage. На cpu остается код общего назначения, для которого текущие языки вполне себе работают.
НЛО прилетело и опубликовало эту надпись здесь
Правильно, и задача этого всего лежит на компиляторе. Язык тут никаким местом. Собственно, компиляторы это давно умеют, но мало кто компилирует под конкретные платформы, т.к. людям важнее переносимость и универсальность кода. Разве что в рантайме выбирать оптимизированные ветки кода. JIT проще в этом плане.

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

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

Вот это все
Пущай автотюнер сам раскладывает данные по-новому, циклы раскручивает, на тайлы бьёт.

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


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

добавил бы усложняется и кастомизируется

Усложняется конечно, поэтому сейчас тренд в удалении абстракций в софте и предоставлении большего доступа к железу. Как вот DX12, Vulkan, DPDK и прочие. Язык тут никому не мешает использовать железо на полную катушку.
спасибо, думаю что вас понял вполне
>cepera_ang
именно, imho рано или поздно это будет сделано, вопрос только когда и кем, т.е. архитектура hw и sw будут объединены, можно спросить знакомых из huawei, что они думают :)
>Gryphon88

>1. Память не плоская: есть кэши, есть видеопамять, есть оперативка.
>2. Процессоров несколько, возможно, что и с разными задержками доступа к памяти
>3. Многопоточная программа это норма.

4. явное указание под управлением какого image (os) процессор работает, типа создания «объект linuxXYZ» и его клонирования, перезагрузки и пр.,
5. для критических приложений также синхронизации результатов (контрольные точки процессоров), голосования (типа 2 из 3), reset/restart при необходимости любого hw или sw объекта
Не, ну это вы хватили. Такие вещи лучше оставить в библиотеках, что-то, возможно, в stdlib, но вгонять в язык осеспецифичность это такое себе.
не совсем понимаю «осеспецифичность» — требуется таки указать какой image на target где именно работает, и в этом случае получается часть описания проекта, а не языка, или не так?
ps
4, 5 все для target конечно
Возможно, я не понял вашего предложения, поясните, пожалуйста.
ваш вопрос толкнул мысли несколько в сторону и заставил задуматься — типа что такое «осеспецифичность» в более широком смысле, но когда писал п. п. 4,5 имелось в виду скорее противоположное (для target — НЕосеспецифичность) — контекст конкретной операционной системы рассматривается типа эквивалента stdlib, соответственно может быть разный для разных процессоров работающих в одной упряжке, в зависимости от архитектуры например, но это собственно пояснение (что имелось в виду), после вашего вопроса, понял что не вижу всех последствий, надо еще подумать
НЛО прилетело и опубликовало эту надпись здесь

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

В Rust есть две гениальные вещи, которые тесно связаны между собой: borrow checker и unsafe.

Причём unsafe, на самом деле, гениальнее borrow checker'а.

Да, оно родилось потому, что некоторые вещи нельзя сделать без того, чтобы сказать borrow checker'у “постой в сторонке, а я пока поработаю” (без чего даже двусвязный список не написать), но сама идея в одном языке иметь и “безопасную” часть на которой пишется 90% (а то и 99%) кода и “небезопасную” — это гениально.

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

В языках, где unsafe часть это другой язык (C#, Java, JavaScript или даже Python) часто такое вытворяют, чтобы не писать две строки unsafe кода, что волосы дыбом встают.

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

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

Последний раз, когда я с ним общался нужно выло модуль на C делать. Но, может быть, что-то и изменилось. Давайте рассмотрим простейший пример: в новом CPU появилась новая ассемблерная инструкция ADDPD и я хочу с её помощью сложить два массива (понятно, что реально сейчас появляются более экзотические инструкции, но идея та же). Как мне это сделать? В Rust есть Asm (хотя пока не в стабильной ветке, но над этим работают): https://doc.rust-lang.org/unstable-book/library-features/asm.html

Что C# предлагает?

До такой степени опускаться я не думал, но быстрый гуглеж дает решение даже для этого stackoverflow.com/questions/18836120/using-c-inline-assembly-in-c-sharp Я думал речь о ручном управлении память и взаимодействии с unmanaged окружение. В шарпах для этого тоже есть unsafe с кучей инструментов.
И, кроме того, тут нет стандартного способа указания того, возможности из какой редакции C++ планируется использовать в коде.

В GCC/G++ вы можете через -std выбрать необходимую редакция.
-std=с++<необходимая редакция>
А как по исходному коду определить какая редакция ему нужна?

А как это сделать в чистом C? Вот и ответ)

Шикарно, хабр превращается в сижу оверфлоу. Вместо достойного ответа на вопрос просто кинули диз, потому что ничего умнее в голове в этот момент не прозвучало...

Добавлю свои полкопейки.

Почти ничего не сказано про отладку и отладчик. При работе с Си (или Си++ без теплейтов, перегрузки операций и прочих премудростей) в отладчике все просто и понятно. И выполнение всегда начинается с main(), и и до вызова main() зависнуть или трапнуться или там улететь в вектор прерывания с Fault в названии невозможно. В случае С++ подобные приключения случаются легко и непринуждённо, что особенно доставляет при разработке микроконтроллеров, которые управляют даже не обязательно ядреными реакторами, а любыми устройствами, которые работают на волшебном дыме, после выхода которого устройства перестают работать.
>> Время от времени мне попадаются новости о том, что кто-то в одиночку (и за приемлемое время) написал рабочий компилятор C.

Крайне расплывчатая фраза.
а) Я написал интерпретатор C (с веб-мордой для студентов) — я написал компилятор?
б) Я написал компилятор C в байт-код (потому, что не хочу заморачиваться с форматом ELF) и к нему интерпретатор — я написал компилятор C?
в) Я написал компилятор, но он создаёт только stand-alone бинарник (и не умеет использовать *.so-библиотеки ) — я написал комилятор С?
г) Я написал компилятор, но не работающий в 0м кольце защиты, я написал компилятор?
… пошли дальше…
Я написал «парсер-лексер-синтаксер — генератор IR» и отдал всё это LLVM — я написал компилятор?


========================
Что вообще значит «написать компилятор языка C», если из 4 обязательных этапов (7 с оптимизациями разных типов) можно 0 написать самостоятельно, а можно все 7?

1) Парсер -> лексер -> синтаксер =>
*2) глобальный анализ/оптимизация
3) генерация IR =>
*4) платформо-независимые оптимизации =>
*5) платформо-зависимые оптимизации =>
6) планирование с распределением регистров =>
7) кодогенерация.
6) планирование с распределением регистров

Можно ж всё на стек класть. Собственно, компилятор Go до недавних пор так и делал.

«всё» на стек — это в смысле хранение нераспределённых виртуальных регистров (тех, которым места не хватило) на стеке или это в смысле все регистры кроме непосредственно нужных для очередной инструкции на стеке?

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

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

Иногда было 2-3 прохода, чтобы какие-то дополнительные улучшения сделать.

В виде, внезапно, отдельных бинарников! Скачайте, насладитесь.

Какой, к бесу, IR? Какое планирование регистров? Вы о чём? Увидели переменную — засунули в слот в стеке, увидели register-переменную — засунули в DI, другую в SI, если видим третью — ошибка компиляции.

Ну или вот вам самый наиканоничнейший из всех каноничных компиляторов.

Где там все ваши фазы?
старые компиляторы работали в условиях ограниченной памяти и неизбежно были много-проходные, типа — препроцессор, лексика, синтаксический разбор, распределение памяти, генерация кода, оптимизация, также использовали стандартные промежуточные представления общие для нескольких языков, теперь когда памяти практически неограничено, в принципе можно все сделать за один проход, и обычно говорят о фазах компиляции, но это больше вопрос терминологии, сколько проходов использует gcc сейчас не знаю, думаю, что вы можете легко найти сами, но еще лет 10-12 назад было 7 проходов (по памяти)
ps
>Где там все ваши фазы?

c0, c1, c2 как-то так (по памяти) original Ritchie, посмотрите makefile

Слушайте, это уже выходит за всякие рамки.

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

Вот только никакого отношения к тем стадиям, что описал @WASD1 они не имеют. Там нет ни IR, ни планирщика регистров. Это всё уже в 80е появилось, в 70е без этого обходились. Железо не позволяли такой роскошью заниматься. А то, что @WASD1засунул в одну первую стадию там, зато, разделено на два отдельных процесса. По той же причине.

Плохая же у вас память. GCC, так-то, может выдать список проходов: https://godbolt.org/z/16jYGqGqK

Их там больше 7 и даже больше 77.

А вот зато компионент в GCC меньше 7 (front-end, tree-ssa, rtl - это если больгие обсуждать, каждый из компонент делится на кучу частей поменьше).

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

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

вообще советую извиниться перед WASD1, и смотреть makefile внимательно, по поводу 7, поищите gnu.org, там есть наверняка, если сомневаетесь

Ещё один “слышавший звон, но не знающий где он”. Ну вот вам ваш Makefile: https://github.com/gcc-mirror/gcc/blob/master/gcc/Makefile.in

Чего мне там искать-то?

Если вы про stage1/stag2/stage3/stage4 xgcc, то во-первых стадий там всего 3 (иногда 4), а во-вторых они совсем о другом.

stage1 xgcc - это компилятор, собранный с помощью другого компилятора (AIX cc какого-нибудь)

stage2 xgcc - это компилятор, собранный с помощью stage1 xgcc

stage3 xgcc - это компилятор, собранный с помощью stage2 xgcc

Это сделано для борьбы с известной бедой, про которую Томпсон в 1984м году писал: https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_ReflectionsonTrustingTrust.pdf

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

Если был запрошен кросс-компилятор, то итоговый gcc (он же stage4 xgcc) будет собран с помощью stage3 xgcc.

Это полезная деятельность, но никакого отношения к сакральным 7 стадиям @WASD1 это всё не имеет.

>Ещё один “слышавший звон, но не знающий где он”

imho, к вам относится

>Ну или вот вам самый наиканоничнейший из всех каноничных компиляторов. Где там все ваши фазы?

там makefile и ищите, где WASD1 посоветовали смотреть, сами поленившись взглянуть

ps
7 — ищите на gnu.org

pps
думаю, что ваше хамство уже всем надоело
Там думаю речь о том, что Go до недавнего времени передавал все входы и выходы функций через стек — тупо такое соглашение вызовов у него было. Естественно сам код внутри функций пользовался регистрами вовсю. Сейчас вот переписали и теперь через стек передаются и аргументы с возвращаемыми значениями насколько хватит регистров.
Чегой-то вы всё дико переусложняете. Ну возьмите какой-нибудь GNU Mes и найдите там “планирование регистров”.

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