LINUX.ORG.RU

Авторы Си — наркоманы?

 , , ,


1

5

Столкнулся с интересным багом. После того как разобрался, что же именно происходит, меня постигло крайнее изумление! Оказывается, в языке Си тип числовой константы зависит от формата записи.

Дистиллированный пример кода, который это демонстрирует:

#include <stdbool.h>
#include <stdio.h>

#define IS_HEX(x) \
    _Generic((x), \
        unsigned int: true, \
        long: false \
    )

#define X 0x80000001
#define I 2147483649

int main(void) {
    if(X == I)
        puts("X == I");

    if(!IS_HEX(I))
        puts("I is not hexadecimal");

    if(IS_HEX(X))
        puts("X is hexadecimal");

    return 0;
}

Все три сообщения будут выведены на экран.

Зачем это сделано? Кому от этого легче? Какие оптимизации это позволяет проворачивать, кроме оптимизации отстрела ног программистам? Непонятно! В общем, стремлюсь поделиться своим негодованием здесь и предостеречь будущие поколения от наступления на эти грабли.



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

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

Не-не-не, Дэвид. Не пытайся соскочить на "вывод", ты уже подписался на "behavior"

Так что и такая программа:

bool is_new_line(char c)
{
    return c == '\n' ? true : false;
}

в твоей логике не соответствует стандарту строго.

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

Значение ‘\n’ в стандарте определено. Поэтому с ним можно сравнивать.

А вот это не соответствует стандарту строго:

bool is_new_line(char c)
{
    return c == 10 ? true : false;
}
monk ★★★★★
()
Ответ на: комментарий от shdown

А полностью избавиться от UB невозможно хотя бы потому, что нельзя описать, что происходит при out-of-bounds write.

Возможно. В Яве же избавились.

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

Значение ‘\n’ в стандарте определено.

ЧТО? ЧЕМУ? Какому числу равно ‘\n’ ?

Я напомню:

Each of these escape sequences shall produce a unique implementation-defined value

Поэтому вопрос про INT_MAX остаётся в силе. Фактически твоя трактовка стандарта запрещает программе проверять значения на границы из <limits.h>.

r--r--r--
()
Ответ на: комментарий от shdown

А в Rust вообще никто не знает, что является UB, прикинь, даже авторы!

Вот список: https://doc.rust-lang.org/reference/behavior-considered-undefined.html

И авторы допускают кучу UB в unsafe-секциях в стандартной библиотеки.

Главное, что в safe-секциях UB нет.

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

Я имел в виду, сохраняя совместимость с текущими ABI для Си и C++. Так-то конечно далеко ходить не надо — Fil-C есть.

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

ЧТО? ЧЕМУ? Какому числу равно ‘\n’ ?

Оно не равно какому-то числу. Оно равно newline character. Что вас всё в числа тянет? Один всё указатели числами считал, другой символы.

Фактически твоя трактовка стандарта запрещает программе проверять значения на границы из <limits.h>.

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

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

Each of these escape sequences shall produce a unique implementation-defined value

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

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

ЧТО? ЧЕМУ? Какому числу равно ‘\n’ ?

Оно не равно какому-то числу.

Понеслась, родная.

Each of these escape sequences shall produce a unique implementation-defined value

  1. "Value" - это что такое?

  2. Тип char у нас теперь не число?

Что вас всё в числа тянет?

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

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

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

Отлично! Мы на верном пути!!

Теперь мы вспоминаем, что у нас символ пробела и вообще все символы - это тоже

The values of the members of the execution character set are implementation-defined.

И вот мы выясняем, что любое сравнение с любым значением типа char не может быть использовано.

Следовательно, строго соответствующая стандарту программа не может использовать функции strchr, memchr, strchr, strpbrk, strrchr, strstr и все остальные для анализа "человекочитаемого текста" в любом виде.

Наша "строго соответствующая стандарту программа" становится всё интереснее и интереснее!

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

Вот список: https://doc.rust-lang.org/reference/behavior-considered-undefined.html

Хм…

Warning

The following list is not exhaustive; it may grow or shrink. There is no formal model of Rust’s semantics for what is and is not allowed in unsafe code, so there may be more behavior considered unsafe. We also reserve the right to make some of the behavior in that list defined in the future. In other words, this list does not say that anything will definitely always be undefined in all future Rust version (but we might make such commitments for some list items in the future).

:)

Breaking the pointer aliasing rules. The exact aliasing rules are not determined yet

:)

Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented.

:)

For a union, the exact validity requirements are not decided yet.

:)

Это то, о чём я говорил в том сообщениии:

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

Даже MIRI не всё ловит, потому что чёткой модели UB нету. Например, это не ловит:

    let dangling: &'static i32 = unsafe {
        let x = 42;
        std::mem::transmute(&x)
    };

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

Тип char у нас теперь не число?

Тип char содержит character. The basic character set shall be present and each character shall be encoded as a single byte.

In a character constant or string literal, members of the execution character set shall be represented by corresponding members of the source character set or by escape sequences consisting of the backslash \ followed by one or more characters.

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

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

Вот если программа соответствует стандарту строго, то вычислений над буквами она делать не должна. Единственное исключение: In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous. То есть i = c - '0' для перевода цифры в её значение разрешено.

А

char toupper(char letter)
{
  if(letter >= 97 && letter <= 122)
     return letter - 32;
  return letter;
}

уже не соответствует стандарту.

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

И вот мы выясняем, что любое сравнение с любым значением типа char не может быть использовано.

In a character constant or string literal, members of the execution character set shall be represented by corresponding members of the source character set.

Сравнение с числом не может быть использовано. strchr(str, 'w') можно, strchr(str, 65) запрещено.

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

Тип char содержит character.
К числу он имеет такое же отношение как указатель к адресу в памяти.

А стандарт почитать?

There are five standard signed integer types, designated as signed char, short int, int, long int, and long long int.

Если ты даже не знаешь, что тип char - это число, как ты уверен, что ты правильно понял стандарт?

Сравнение с числом не может быть использовано.

Нет, нет, нет, Дэвид Блейн. Вообще любое сравнение и любые другие математические операции не могут быть использованы, потому что значения implementation-defined value и "behavior" тоже получается "implementation-defined".

if ( 'x' > 'z')

Точно так же не законно в твоей трактовке.

Ты не можешь написать на своём подмножестве си утилиты sort или grep.

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

Если ты даже не знаешь, что тип char - это число, как ты уверен, что ты правильно понял стандарт?

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

if ( ‘x’ > ‘z’)

Точно так же не законно в твоей трактовке.

Если при этом будет разный результат в ветках условия, то разумеется. Если в программе, соответствующей стандарту вдруг надо отсортировать по алфавиту, надо явно указать какому символу какой порядок должен соответствовать. В стандарте есть гарантия только для ‘0’…‘9’.

Ты не можешь написать на своём подмножестве си утилиты sort или grep.

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

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

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

Кстати, для koi8-r всегда было так. Сортировка по номерам букв сортировала совсем не по алфавиту.

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

In other words, this list does not say that anything will definitely always be undefined in all future Rust version (but we might make such commitments for some list items in the future).

:)

Что непонятного? Штуки, которые считаются неопределёнными сегодня, могут быть определены в будущем.

Тем не менее, даже в таком виде это всё лучше чем тринадцать страниц UB в стандарте Си.

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

Если ты даже не знаешь, что тип char - это число, как ты уверен, что ты правильно понял стандарт?

Программист вправе использовать этот тип для хранения чисел.

Нет, не в праве, потому что у char’а неизвестная знаковость.

Но надо явно сделать массив соответствия буква->порядок

Подожди, но ты же выше явно запретил использовать числа!

И ещё ругался на меня, что я в байтах числа вижу!

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

Нет, не в праве, потому что у char’а неизвестная знаковость.

Числа от 0 до 127 хранить можно.

Подожди, но ты же выше явно запретил использовать числа!

Я их запретил использовать при работе с символами. То есть можно отдельно

char s[] = "Hello";

и отдельно

char d[] = {1, 2, 3};

Можно strlen(s) и d[0] + 32, но strlen(d) или s[0] + 32 нельзя.

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

Тем не менее, даже в таком виде это всё лучше чем тринадцать страниц UB в стандарте Си.

Чем? По тем тринадцати страницам в любой конкретной программе можно точно сказать есть UB или нет. А по «Violating assumptions of the Rust runtime» UB может быть в любом куске unsafe.

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

То, что ты так избирательно читаешь, показывает, что ты фанатик.

The following list is not exhaustive; it may grow or shrink. There is no formal model of Rust’s semantics for what is and is not allowed in unsafe code, so there may be more behavior considered unsafe

:)

Тем не менее, даже в таком виде это всё лучше чем тринадцать страниц UB в стандарте Си.

Гораздо хуже. UB в Си хотя бы помещается в голове и можно ± быть уверенным, есть UB в данном коде или нет. В Rust, во-первых, гораздо больше очень неочевидных правил, во-вторых, они сами не могут сформулировать, что есть UB сейчас, не говоря уже о том, что может стать UB в будущем.

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 2)

Не то что бы наркоманы. Я этот конкретный вопрос не могу прокомментировать, но в целом язык довольно сложный. По крайней мере есть около 200 категорий undefined behavior которые определены в стандарте явно, и порядка 80 - неявно. Более того, если проанализировать эти случаи, то выяснится что алгоритмически нельзя определить undefined или не undefined. В основном, на сколько я понимаю (это уже мои домыслы) это следствие того что просто напросто на разных платформах _ожидаемое_ поведение си программы выглядит по разному. И некоторые вещи нельзя предсказать заранее как будут реализованы. Но опять, это мои домыслы, может авторы действительно какие то вещества употребляли, не знаю, глубоко в вопрос не лез.

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

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

Алгоритмически можно разделить на «не undefined» и «возможно undefined».

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

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

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

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

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

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

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

Как ты можешь писать что в си хуже чем в расте, если не знаешь как в расте?

Выше же писали пример раста: «нарушение pointer aliasing rules это UB, но сам список этих pointer aliasing rules мы ещё до конца не придумали».

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

Можно. Про любой кусок кода или не содержит UB, например

unsigned int calc(unsigned int x, unsigned int y, unsigned int z)
{
  return x + y * z;
}

или возможно содержит

unsigned int calc2(char *s1, char *s2)
{
  return strlen(s1) + strlen(s2);
}
monk ★★★★★
()
Ответ на: комментарий от firkax

Как ты можешь писать что в си хуже чем в расте, если не знаешь как в расте?

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

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

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

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

Я надеюсь во втором случае ты понимаешь, что UB заключается в самом вызове strlen, а вовсе не в сложении и даже не в тайпкасте size_t к unsigned int-у.

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

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

Разумеется. Более того, даже

int f(int *x)
{
  if(x != NULL) return *x; else return 0;
}

может содержать UB.

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

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

Чувак, там список 13 страниц формата A4. У тебя точно он голове помещается? Весь? Ты точно в этом уверен?

они сами не могут сформулировать, что есть UB сейчас, не говоря уже о том, что может стать UB в будущем.

Прямо как в Си! Свежие стандарты добавляют UB только в путь. В C17 вызов realloc(ptr, 0) специально сделали определённым поведением, а потом в C23 сделали неопределённым. Видишь как круто сишники умеют?

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

В Rust, во-первых, гораздо больше очень неочевидных правил

Лол чего, это каких? Сильно меньше чем в сишке, и как раз очевиднее.

они сами не могут сформулировать, что есть UB сейчас,

Те что сейчас - могут и сформулировали. Неизвестно только те что возможно добавят в будущем.

Даже MIRI не всё ловит, потому что чёткой модели UB нету.

Модель есть, но нельзя однозначно и гарантированно выявить, независимо от языка, в чём и злокозненость понятия UB

А самое главное - это всё таки возможно огородить и локализовать в отличие от сишек плюсишек.

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

Вот после общения с многими вещами написанными на c,c++,Fortran..

  1. ЯВНОЕ задание разрядности ВСЕХ переменных. только int(kind=) и int_n

  2. НИКАКИХ макросов

  3. (фортрановское) ВСЕ переменные должны иметь явный тип и выполнять П1.

Извиняюсь за капс, но если этого не делать то становится больно. Рано или поздно.

Ну и небольшой бонус - если кто-то пытается использовать real/float то заставить проверить что double выдаёт тот же результат. И если не выдаёт то долго тыкать носом в IEEE 754.

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

И бонусное: всегда ставить скобки в выражениях с более 2 переменных.

FreeBSD рута недавно поймали из-за невыполнения этой практики.

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

ну я про макросы в коде не являющиеся IFDEF и прочим подобным.

Если кому нужен такой функционал уже правильней смотреть в сторону С++ как по мне.

Ну и да, autotools зло.

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

ну я про макросы в коде не являющиеся IFDEF и прочим подобным.

В смысле, избегать define? А константы? Или не делать define с параметрами? Тогда согласен, но это уже не НИКАКИХ.

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

ЯВНОЕ задание разрядности ВСЕХ переменных. только int(kind=) и int_n

А как с библиотечными функциями общаться?

??? t = time(NULL);
??? c = getc(stdin);

Какие типы вместо вопросов?

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

Не делать макросы похожими на язык.

Константы в C само собой логично что можно если удобно. Мне не удобно так просто)

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

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

Ну второе вроде бы char* ( если не прав, не обессудьте, я на голом C лет 5 ничего не трогал )

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

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

ну к счастью в том что я видел был честный 66й фортран максимум) который отлично компилируется до сих пор. Конкретно библиотека ГраФОР

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

Ну второе вроде бы char* ( если не прав, не обессудьте, я на голом C лет 5 ничего не трогал )

Второе int. Первое time_t. Ни на int32 ни на int64 заменить по-моему нельзя.

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

В том что я видел - не проверялись. Если передал целое число туда где функция ждёт аргументом дробное - всё просто молча работало не так.

firkax ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.