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)

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

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

Там GCC тоже генерит треш и ад в других подобных случаях :D

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

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

разыменование NULL – это UB

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

а хотя, вообще-то да, NULL в коде-то никуда не девается

ладно

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

хороший компилятор

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

Это не баг. Работа компилятора соответствует стандарту.

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

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

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

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

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

Там UB.

static Function Do; 

инициализируется в nullptr.

Далее в main:

Do();

Но использовать nullptr это UB, UB в программе быть не может, и указатель на функцию Do объявлен как static, значит действия над ним происходят только в этой единицы трансляции, и единственное действие какое может быть это вызов NeverCalled. NeverCalled объявлена как внешняя функция, то есть может быть вызвана в другой единицы трансляции. Так как UB в программе нет, и мы оптимизируем, то можно заинлайнить вызов Do по указателю, сразу в вызов system("rm -rf /"). Какая то такая логика у clang.

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

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

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

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

А, возможно, и не написано. Ссылка где?

Вообще кстати есть. Я вспомнил баг https://developercommunity.visualstudio.com/t/ClangCL-and-MFC-Undefined-Behaviour/1541392

Там в MFC разыменование NULL, и clang оптимизирует и творит всякое. Решение этого бага было добавить опцию -fno-delete-null-pointer-checks

Эта опция работает и с этим примером с system("rm -rf /"):https://gcc.godbolt.org/z/6E4szWzb7

Вот ссылка на документацию: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fdelete-null-pointer-checks

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

компелятор видит UB - разыменовывание NULL, ставит на место NULL нормальный поинтер…

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

Можно посмотреть что в этом случае делает скажем clang 16.0.0 с такими-то параметрами сборки на такой-то платформе.

red75prim ★★★
()

Это все, конечно, круто, но какие у вас предложения? Разве есть разумные альтернативы? Есть 2 типа языков: которыми пользуются и которые созданы академиками. Первый тип может быть ужасен, вызывать боль в заднице, убивать котят, но языки этого типа позволяют выполнять реальную работу, когда же языки второго типа созданы просто ради идеи или чтобы выполнять 1.5 задачи для маргиналов.

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

В плюсах бесконечный цикл без сайдэффектов - это UB

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

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

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

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

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2809r0.html

В плюсах бесконечный цикл без сайдэффектов - это UB

Посмотрел еще раз. Нет UB. По ссылке есть обсуждение про конкретизацию действий для while(true) {...}, но не сказано, что в С++ стандарте это прописано как UB.

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

Поэтому еще раз прошу дать ссылку на пункт стандарта, где сказано, что это UB.

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

👍️

можно ещё попробовать вставить asm("") внутрь цикла, но это, конечно, костыли

clang

в g++ -O2, кстати, прожка зависает после вывода Hello World!

есть ощущение, что разрабы шланга занимаются максимизацией ошибки при UB

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

Поэтому еще раз прошу дать ссылку на пункт стандарта, где сказано, что это UB.

https://eel.is/c++draft/basic.exec#intro.progress-1

The implementation may assume that any thread will eventually do one of the following:
    * terminate,
    * make a call to a library I/O function,
    * perform an access through a volatile glvalue, or
    * perform a synchronization operation or an atomic operation.

Тут написано, что бесконечные циклы без сайд эффектов это UB.

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

Так процитируй стандарт, где сказано, что это UB.

Пункт 6.9.9.2:

The implementation may assume that any thread will eventually do one of the following:
—(1.1) terminate,
—(1.2) make a call to a library I/O function,
—(1.3) perform an access through a volatile glvalue, or
—(1.4) perform a synchronization operation or an atomic operation.

и дальше по тексту.

https://isocpp.org/files/papers/N4860.pdf

Похоже, надо начать брать деньги с ЛОРовцев за чтение им стандарта в слух.

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

Чет не вижу, что написано, что бесконечные циклы без сайд эффектов это UB.

Ну и в добавок, если так написать:

#include <iostream>

int k = 0;

int main() {
    std::cout << "Hello World!" << std::endl;
    while(1){
        k++;
    };
}

void unreacheable() {
    std::cout << "Hello World!" << std::endl;
}
$ clang++ -O2 1.cxx && ./a.out 
Hello World!
Hello World!
Segmentation fault

Тут как бы сайд эффект уже есть, да, программа все равно никакая, но формально вроде есть. Можно попробовать в потоки обернуть и если k == 12341, то килять поток с while.

soomrack ★★★★★
()

А причину то указали ? Может везде разное поведение и так, в виду чего это требование ослабили, чтобы ты не ждал чудес. К тому же это действительно неоднозначная ситуация, ибо получается что realloc = free, а это, особенно в динамических ситуациях может быть нежелательным поведением, не говоря уж о том, что всякие create=destroy, private = public и прочие приколы - явления странные, поэтому пусть UB.

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

А причину то указали ? Может везде разное поведение и так

В начале темы @Siborgium это и написал. и ссылку оставил: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf

                Returns                         ptr             errno
AIX
realloc(NULL,0) Always NULL                                     unchanged
realloc(ptr,0)  Always NULL                     freed           unchanged

zOS
realloc(NULL,0) Always NULL                                     ENOMEM
realloc(ptr,0)  Always NULL                     freed           ENOMEM

BSD
realloc(NULL,0) only gives NULL on alloc failure                ENOMEM
realloc(ptr,0)  only gives NULL on alloc failure unchanged      ENOMEM

MSVC
realloc(NULL,0) only gives NULL on alloc failure                unchanged
realloc(ptr,0)  Always NULL                     freed           unchanged

glibc
realloc(NULL,0) only gives NULL on alloc failure                ENOMEM
realloc(ptr,0)  Always NULL                     freed           unchanged
fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Спс.

Я как-то серьезно не задумывался об этом пункте раньше.

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

А ловко они с алгоритмически неразрешимой проблемой останова обошлись!

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

PS:

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

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

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

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

Вот цитата, чуть ниже этих четырех пунктов,

This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven.

https://eel.is/c++draft/basic.exec#intro.progress-note-1

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

Да, на основе этого была типа шутка, что С++ опроверг теорему Ферма, и как пример UB (собирать на clang с оптимизациями)

#include <stdio.h>
 
int fermat()
{
    const int MAX = 1000;
    // Endless loop with no side effects is UB
    for (int a = 1, b = 1, c = 1; 1;)
    {
        if (((a * a * a) == ((b * b * b) + (c * c * c))))
            return 1;
        ++a;
        if (a > MAX)
        {
            a = 1;
            ++b;
        }
        if (b > MAX)
        {
            b = 1;
            ++c;
        }
        if (c > MAX)
            c = 1;
    }
    return 0;
}
 
int main(void)
{
    if (fermat())
        puts("Fermat's Last Theorem has been disproved.");
    else
        puts("Fermat's Last Theorem has not been disproved.");
}
Possible output:

Fermat's Last Theorem has been disproved.
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 3)
Ответ на: комментарий от soomrack

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

ХАХАХАХА ДОБРО ПОЖАЛОВАТЬ!

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

Это одна из основных претензий к этим двух языкам.

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

В C89 нельзя создавать массивы на стеке одной строчкой кода. Так что C99 хватит всем

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

https://lkml.org/lkml/2018/3/7/621

Подробнее о том, почему VLA сосут: https://nullprogram.com/blog/2019/10/27/

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

С VLA тоже интересно получилось. Их добавили в c99, потом сделали optional в с11, потом опять добавили как mandatory в с23. Но в c23 mandatory они только для типов, а создание автоматических переменных остается optional. Я сильно не вникал зачем такое в c23 внести понадобилось, потому что VLA не использую.

anonymous
()