LINUX.ORG.RU

Если вам не хватало UB в C, то вам принесли ещё

 ,


1

3

Привет, мои дорогие любители сишки!

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

— zero-sized reallocations with realloc are undefined behavior;

То есть вот это валидный код:

void *ptr = malloc(0);
free(ptr);

А вот это – UB:

void *ptr = malloc(4096);
ptr = realloc(ptr, 0); <-- хаха UB

И это несмотря на то, что в манах уже давно написано следующее:

If size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr)

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

В тред призываются известные эксперты по C: @Stanson и @alex1101, возможно они смогут нам объяснить, зачем разработчики стандарта C постоянно пытаются отстрелить себе обе ноги самыми нелепыми способами.



Последнее исправление: cumvillain (всего исправлений: 1)

Мне кажется, что это правильное решение.

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

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

Вот что, по логике должно быть для ptr = realloc(ptr, bytes), если bytes == 0?

  1. free(ptr) и ptr остается неизменным? Тогда как в дальнейшем понять, что ptr уже освобожден? Нужно проверить bytes, а раз все равно проверяем, то почему free в проверке не сделать?

  2. free(ptr) и ptr приравниваем NULL? Тогда как понять, что память ресайзнулась, если bytes не ноль? Нужно проверять, что bytes не 0, а раз все равно проверяем, то почему free в проверке не сделать?

  3. Память не освобождаем и ptr приравниваем NULL? Этот случай выглядит как отработка некорректных входных данных. Проверив результат на NULL нужно будет проверить еще и bytes на 0, чтобы понять в чем причина.

  4. Память не освобождаем и ptr остается неизменным? Этот случай выглядит как отработка некорректных входных данных. Поэтому нужно будет проверить еще и bytes на 0.

Вот и получается, что во всех вариантах все равно нужно bytes на 0 проверять, т.е. никаких удобств внесение определенного поведения на нулевой размер в realloc не даст. При аккуратном написании кода, все равно придется проверять на 0.

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

soomrack ★★★★★
()
Ответ на: комментарий от soomrack

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

С integer overflow, strict aliasing, pointer provenance и другими крутыми штуками, за которые мы так любим C, это отлично помогло. Все сразу стали писать грамотный код. Никто ни разу не обосрался даже.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

С integer overflow, strict aliasing, pointer provenance и другими крутыми штуками, за которые мы так любим C, это отлично помогло. Все сразу стали писать грамотный код.

Либо пишут как писали, либо начинают приводить свой стиль кода к стандарту понемногу. Хуже не стало, имхо. Это все маленькие шажки к улучшению ситуации.

PS: integer overflow сделать UB это очень правильно, код должен быть логичным, а не набором мутных хаков, выявленных на особенностях своей платформы.

soomrack ★★★★★
()
Ответ на: комментарий от vbr

undefined это что угодно

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

hateWin ★☆
()
Последнее исправление: hateWin (всего исправлений: 1)
Ответ на: комментарий от soomrack

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

Ололоэ! Большинство сишников стандарт не то что не читали, они его люто ненавидят. Тут прямо на этом форуме для хардкорных сишников как-то раз было шоком, что после вызова free(ptr) использование значения этого самого ptr является UB. Мне прямо весь тред отдельные личности доказывали, что это на самом деле неправильный компилятор и вообще так не должно быть.

PS: integer overflow сделать UB это очень правильно, код должен быть логичным, а не набором мутных хаков, выявленных на особенностях своей платформы.

Настолько правильно, что большинство сишных проектов – включая линуксовое ведро, GTK, GIMP и прочую залупу – тащат -fwrapv в флаги компилятора, потому что вертели это «правильно» на одном органе и хотят вполне конкретного поведения.

И это не учитывая того факта, что платформы, где знаковые типы не использовали бы 2’s complement, давно сдохли и нынешние сишные компиляторы, соответствующие стандарту, под них код даже не собирают.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от soomrack

код должен быть логичным,

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

FishHook
()
Ответ на: комментарий от cumvillain

со всем возможным энтузиазмом

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

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

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от FishHook

Странная аналогия. Мне кажется, что более уместна, что UB это когда ты в противовес на кране положил 0 плит, и пытаешься работать на этом кране. Вот это вот чистое UB.

UB, как и ID надо понимать, как «мягкое» влияние на обратную совместимость. Прописать строго нельзя, т.к. совместимость. Поэтому делают вот так, вынося вещи в UB и ID, как говорят – так писать нельзя, это плохо, убирайте это из своего кода. Конечно, хорошо бы, чтобы была бы опция компилятора, которая на все эти случаи выдавала бы ерроры или варнинги, но честно говоря, это физически нереализуемо, т.к. надо отслеживать по коду что может или не может попать в аргументы…

soomrack ★★★★★
()
Ответ на: комментарий от PPP328

Какая вообще нахрен разница, какое поведение в конкретной реализации? Это новый стандарт, новый, блэт! Если у вас не соответствует стандарту - значит вы не соответствуете стандарту. Причем тут вообще «а вот у них нет float, давайте их уберем»

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

Новый код НЕ сломается пока кто-то принудительно не поставит -std=c23.

И после этого тоже не сломается.

firkax ★★★★★
()
Ответ на: комментарий от PPP328

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

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

firkax ★★★★★
()
Ответ на: комментарий от no-such-file

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

… из функции выкидывается код возврата и все падает в сегфолт.

cumvillain
() автор топика
Ответ на: комментарий от no-such-file

Я пока не знаю компилятора, который при UB компиляет программу которая форматирует диск.

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

X512 ★★★★★
()
Ответ на: комментарий от soomrack

UB, как и ID надо понимать, как «мягкое» влияние на обратную совместимость. Прописать строго нельзя, т.к. совместимость.

Што? Ты точно знаешь что такое UB? В сишном стандарте написано, что UB – это поведение итоговой программы, если в коде встречаются такие-то и такие-то вещи. Грубо говоря, если у тебя в коде есть realloc(p, 0), то теперь компилятор свободен сделать вообще что угодно, включая:

  • выкинуть ошибку сборки;
  • насрать тебе в штаны;
  • откопать и изнасиловать твою пра-прабабушку.

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

hateyoufeel ★★★★★
()
Ответ на: комментарий от no-such-file

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

ну ладно, пусть это будет опция к компилятору

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

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

olelookoe ★★★
()
Ответ на: комментарий от no-such-file

иметь успех

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

anonymous
()
Ответ на: комментарий от X512

приведёт к поломке логики программы и загадочному поведению

А в чём загадочность, если ты об этом знаешь?

компиляторы часто рассматривают условия приводящие к UB

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

no-such-file ★★★★★
()
Ответ на: комментарий от olelookoe

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

В git-версии GHC (в релиз это не попало) когда-то был баг, из-за которого, если в собираемом модуле была ошибка типов, GHC удалял этот файл к чертям.

Оригинал от разработчика:

So glad you asked! The best ghc bug ever involved a dev version of the compiler deleting your source file if it contained a type error.

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

hateyoufeel ★★★★★
()
Ответ на: комментарий от no-such-file

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

Как это здорово! То есть скомпилял код другим компилятором и получил другое поведение программы. Такое колько в мире Си считается нормальным или еще где?

FishHook
()
Ответ на: комментарий от FishHook

Как это здорово! То есть скомпилял код другим компилятором и получил другое поведение программы. Такое колько в мире Си считается нормальным или еще где?

Сишники этим очень гордятся и говорят что это «low level».

cumvillain
() автор топика
Ответ на: комментарий от soomrack

UB, как и ID надо понимать, как «мягкое» влияние на обратную совместимость

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

hateWin ★☆
()
Последнее исправление: hateWin (всего исправлений: 1)
Ответ на: комментарий от no-such-file

А в чём загадочность, если ты об этом знаешь?

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

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

X512 ★★★★★
()
Ответ на: комментарий от soomrack

Мне кажется, что более уместна, что UB это когда ты в противовес на кране положил 0 плит

Ну а почему не -5 плит? Не корень из бесконечность плит? Аналогия - не модель, придираться к аналогиям - ну такое

FishHook
()
Ответ на: комментарий от FishHook

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

Если ты используешь UB, то да. В чём проблема? Ты заранее предупреждён.

Такое колько в мире Си считается нормальным или еще где?

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

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

#include <stdio.h>

#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX-1)

void examine(int x) {
  if(x<0) x = -x;
  if(x==INT_MIN) printf("%d==%d          \t", x, INT_MIN);
  else printf("!!!BUG!!! %d!=%d\t", x, INT_MIN);
  if(x<0) printf("%d<0           \t", x);
  else printf("!!!BUG!!! %d>=0\t", x);
}

void examine2(int x) {
  x++;
  if(x==INT_MIN) printf("%d==%d          \t", x, INT_MIN);
  else printf("!!!BUG!!! %d!=%d\t", x, INT_MIN);
  if(x<0) printf("%d<0\n", x);
  else printf("!!!BUG!!! %d>=0\n", x);
}

int main(void) {
  examine(INT_MIN);
  examine2(INT_MAX);
  return 0;
}

В зависимости от версии gcc и его флагов эта программа может работать разным образом, выводя в т.ч. строки типа -2147483648!=-2147483648 т.е. if() направляет выполнение не в ту ветку из-за того, что строчкой выше от него т.н. UB.

firkax ★★★★★
()
Ответ на: комментарий от cumvillain

Вопрос зачем делать UB то что повмеместно распространено, используется и несёт определённые удобства (например, либы позволяющие выбирать аллокатор принимают один указатель на функцию realloc, через который делают и выделение, и освобождение памяти, и изменение её размера). Почему бы не сделать это наоборот частью стандарта «по многочисленным просьбам трудящихся»?

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

KivApple ★★★★★
()
Ответ на: комментарий от hateyoufeel

UB при разработке компилятора можно воспринимать как ID.

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

Разработка компилятора, формально должна быть согласована стандарту, формально должна непротиворечить ему, фактически есть отступления. Но области реализации при UB и при ID это вне стандарта, компилятор может реализовывать их как считает правильным, это НЕ ПРОТИВОРЕЧИТ стандарту. Соотв. можно ожидать, что будут реализовывать также как и раньше.

Хочешь использовать то, что стандарт считает UB или ID – опирайся на документацию компилятора, и привязывай свою программу к конкретному компилятору.

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

soomrack ★★★★★
()
Ответ на: комментарий от no-such-file

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

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

firkax ★★★★★
()
Ответ на: комментарий от KivApple

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

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

cumvillain
() автор топика
Ответ на: комментарий от soomrack

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

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

X512 ★★★★★
()
Ответ на: комментарий от firkax

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

А потом твой код портируется на freebsd и ты огребаешь. Хаха классика сишного мирка.

cumvillain
() автор топика
Ответ на: комментарий от no-such-file

Если ты используешь UB

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

FishHook
()