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)

Ответ на: комментарий от sehellion

Вот этот код может вернуть тебе не NULL:

void *resize_array(void *ptr, size_t n)
{
    ptr = realloc(ptr, n);
    if (n == 0) {
        errno = 0;
        return NULL;
    }
    if (ptr == NULL)
        errno = ENOMEM;
    return ptr;
}

Просто потому что компилятор знает, что n != 0 (потому что это UB), а значит эту ветку кода можно просто выкинуть!

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

Making this behavior undefined continues to allow existing implementations to do as they please, but provides a clear warning to developers to guard against zero-byte reallocations.

То есть тридцать лет спустя, имея постоянные проблемы с int overflow, до этих клоунов никак не дойдет, что silent UB – это источник багов?

cumvillain
() автор топика

Ты у меня так подписан:

cumvillain  фанат бюрократических си-стандартов https://www.linux.org.ru/forum/development/17122046

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

Впрочем, зачем делать realloc(0) я не знаю.

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

Что касается темы - то думаю всем будет плевать.

Это вызовет баги, 100%. Возможно кто-то из-за этого даже умрет :D

Документация glibc имеет приоритет над любой графоманией (т.к. она, в отличие от графомании, первоисточник)

Помимо glibc есть другие libc (в ондроеде, например). Ну и musl.

ну а авторы gcc ни в коем случае не станут ломать glibc.

Они его регулярно ломают, ты просто не сталкивался.

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

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

Тема не твоя, но там был диалог.

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

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

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

Возможно.

И правильно там сделали UB

UB – это не ошибка. UB – возможность компилятору сделать вообще что угодно и тебе про это не сказать.

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

Почему лишён? Вот пример:

typedef struct {
  size_t len;
  char *data;
} dynamic_array;

int dynamic_array_set_len(dynamic_array *da, size_t len) {
  char *data;
  if(!(data=realloc(da->data,len)) && len) return -1;
  da->len = len;
  da->data = data;
  return 0;
}

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

firkax ★★★★★
()

Опять @cumvillain опозорился – да что ж такое.

Все про мотивацию комитета в пропозале доступно изложено. Вкратце: практически все реализации и POSIX при вызове realloc(ptr, 0) ведут себя по-разному.

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf

Classifying a call to realloc with a size of 0 as undefined behavior would allow POSIX to define the otherwise undefined behavior however they please.

https://research.nccgroup.com/2020/04/09/c-language-standards-update-zero-size-reallocations-are-undefined-behavior/

Making this behavior undefined continues to allow existing implementations to do as they please, but provides a clear warning to developers to guard against zero-byte reallocations.

Жду мантр про «UB это когда компилятор форматирует хард».

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

Было до С23

if new_size is zero, the behavior is implementation defined (null pointer may be returned (in which case the old memory block may or may not be freed), or some non-null pointer may be returned that may not be used to access storage). Such usage is deprecated

Стало в C23

if new_size is zero, the behavior is undefined. 

Так что и раньше было и deprecated и implementation defined.

https://open-std.org/JTC1/SC22/WG14/www/docs/n2396.htm#dr_400

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

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

void *resize_array(void *ptr, size_t n)
{
    if (n == 0) {
        errno = 0;
        return NULL;
    }

    ptr = realloc(ptr, n);

    if (ptr == NULL)
        errno = ENOMEM;
    return ptr;
}
anonymous
()
Ответ на: комментарий от anonymous

Вот так и утечки создают, блин. А освобождать память перед возвратом NULL кто будет делать? Почтальон Печкин, свинка Пепе, или ещё какая мифическая сущность???

 if (n == 0) {
        free(ptr);
        errno = 0;
        return NULL;
    }

Atlant ★★★★★
()

Making this behavior undefined continues to allow existing implementations to do as they please, but provides a clear warning to developers to guard against zero-byte reallocations.

Это просто чистейший фейспалм. «Вон та реализация Keil-C кривая, поэтому давайте пусть такое поведение у всех будет». Что дальше? Объявим Microsoft-C стандартом и все будем равняться на _wstrbpbstio?

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

Новый код НЕ сломается пока кто-то принудительно не поставит -std=c23. Тот же самый фейспалм был с bool. Какой смысл был его выносить в отдельный заголовок? Чтобы что? Чтобы не сломать код? Какой? Который под c89/90 компилируется? Ну так у него всё будет хорошо. Зато новый код оброс костылем.

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

Ничего не мешает конкретным реализациям конкретизировать это поведение.

Нет, тогда это было бы implementation defined (как было). А стало undefined behavior.

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

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

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

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

but provides a clear warning to developers to guard against zero-byte reallocations.

а где clear warning будет? В стандарте? Сишники его не читают. Короче, будет как всегда: куча воя что «ОЛОЛО СТАРЫЙ КОД РАБОТАЛ И СЛОМАЛСО СРАНЫЕ КОМИТЕТЧИКИ НАСРАЛИ МНЕ ПОД ДВЕРЬ!!!»

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

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

Так потеряется смысл: ptr = NULL после realloc(ptr, 0) это не ENOMEM.

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

Classifying a call to realloc with a size of 0 as undefined behavior would allow POSIX to define the otherwise undefined behavior however they please.

В итоге в одном стандарте будет написано что это UB, в другом не написано ничего, а в третьем glibc будет делать одно, а musl – другое. Все как мы любим :)

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

Вот так и утечки создают, блин. А освобождать память перед возвратом NULL кто будет делать? Почтальон Печкин, свинка Пепе, или ещё какая мифическая сущность???

Опять кто-то документацию не почитал, да?

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

В случае n == 0 realloc(ptr, n) == free(ptr).

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

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

Потому что в документации написано, что realloc(ptr, 0) это free(ptr), который errno не трогает.

cumvillain
() автор топика