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)
Ответ на: комментарий от algo

Ну да, я помню

Это цикл:

DO 3 I = 1,3

А это присвоение переменной:

DO 3 I = 1.3

Говорят, из-за этой разницы Маринер-1 на Венеру не попал.

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

Си на фоне тех языков даже неплохо смотрится.

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

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

Ну да :)

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

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

Так программа пишется под конкретный стандарт, а не под «самый новый си». И этот стандарт фиксируется с помощью -std=c99.

А по поводу realloc — они просто узаконили беззаконие, которое и так было и на которое нельзя было полагаться при написании портабельных программ: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf

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

Так это и не уб. УБ это будет, когда ты разыменуешь эту ссылку.

https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-unde...

A reference or Box<T> must be aligned and non-null, it cannot be dangling, and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the metadata). Note that the last point (about pointing to a valid value) remains a subject of some debate.

Азаза, поймали растамана на незнании UB раста :)))))

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

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

Нет, конечно:

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

Это ложь.

https://doc.rust-lang.org/reference/behavior-considered-undefined.html

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.

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

Ну попробуй не разыменовать, а хотя бы распечатать адрес этой ссылки dbg!(dangling as *const i32 as usize);. Неиспользуемая переменная всё равно что не существующая.

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

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

В openssl неопытные разработчики?

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

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

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

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

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

Попробовал. И в обычном Rust, и через MIRI выполняется без проблем. И даже с println!("{}", dangling);. Так зачем мне что-то было пробовать?

Это UB (даже без использования)? UB. MIRI не ловит? Не ловит. What’s your point?

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

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

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

Так программа пишется под конкретный стандарт, а не под «самый новый си». И этот стандарт фиксируется с помощью -std=c99.

Круто. А что делать, если хочешь новый стандарт? Перелопачивать весь код?

которое и так было и на которое нельзя было полагаться при написании портабельных программ

Беззакония не было. Было «implementation-defined behaviour». Они убрали все гарантии, и теперь компилятор может, например, выкинуть всю ветку с realloc(ptr, 0) нахрен, ведь UB в корректном коде быть не может. Или будет считать, что переменная с размером никогда не будет 0, и выкинет какую-нибудь другую ветку. Сишечка славна подобными оптимизациями, стреляющими сишникам в член.

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

The Debian maintainer asked for help with code he didn't understand, but the snippets in his post to the OpenSSL list don't include enough context to understand where the MD_update calls really are in the code

The OpenSSL developers responded incorrectly, probably without actually looking at the code to see which calls were being talked about

Ага. А где опытный разработчик напоролся на UB, про которое он не знал, что это UB? Авторы OpenSSL знали, что чтение неинициализированной памяти — это UB, но данный случай ни один компилятор бы не оптимизировал, потому что он не может предсказать, что не вычитаем из файла все 100 байт.

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

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

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

size_t a = something();
// В 'hi' будут верхние 32 бита, в 'lo' — нижние, и ниипёт
uint32_t hi = a >> 32, lo = a;
?

Круто. А что делать, если хочешь новый стандарт? Перелопачивать весь код?

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

Беззакония не было. Было «implementation-defined behaviour».

Было. Посмотри таблицу, оно не укладывалось в то, что стандарт называет implementation-defined behavior.

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

Ну вообще, особо вредительский компилятор мог бы подменить RAND_add(buf,n,i) на RAND_add(buf,n,n) т.к. решил бы что n==i. Но такими компиляторами не надо пользоваться.

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

Не мог, RAND_add находится в другом translation unit. Компилятор не может знать, что оно читает до размера во втором аргументе.

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

int платформозависимый, тут либо алиас на 32 либо на 64-битный. Вроде на 32.

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

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

А где опытный разработчик напоролся на UB, про которое он не знал, что это UB?

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

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

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

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

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

P.S. В Rust тоже ломают обратную совместимость часто. Но там, справедливости ради, есть Releases. «Круто. А что делать, если хочешь новые фичи? Перелопачивать весь код?»

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

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

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

Назначить имя какой-то value не UB, а в момент формирования самой ссылки она ещё валидна. Запуск MIRI через кнопку TOOLS там вверху справа.

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

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

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

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

Интересная аргументация.

Ладно, как опытный разработчик найдёшь UB в этом коде:

time_t integral_value_holding_the_number_of_seconds_since_the_beginning_of_epoch_without_leap_seconds = time(NULL);
time_t integral_value_holding_the_number_of_seconds_since_the_beginning_of_epoch = integral_value_holding_the_number_of_seconds_since_the_beginning_of_epoch_without_leap_seconds + leap_seconds;

?

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

либо в подобных случаях кастовать в 64 когда надо.

В смысле, вместо int везде int64 делать?

А потом очередная функция хочет *int и туда указатель на int64 кидать?

Менее 32 вроде только в win16 типы были.

На x86 мир не заканчивается

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

1. На древнем компиляторе может быть лимит significant characters.

2. Неизвестно, что такое leap_seconds, какого она типа, инициализирована ли, какое значение содержит. time_t может быть знаковым, тогда там может произойти переполнение или недополнение (если time_t = int, time_t leap_seconds = INT_MIN, time(NULL) вернул -1, потому что сейчас 2039-ый год). Если оно не time_t, то уже другой разговор и причин для UB там может быть много.

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

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

Назначить имя какой-то value не UB, а в момент формирования самой ссылки она ещё валидна

Очень сомнительно. А где у авторитетных растаманов это можно уточнить?

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

Да хз. Вон, есть официальный метод для создания висячих указателей, даже ансейфа не надо: https://doc.rust-lang.org/beta/std/ptr/struct.NonNull.html#method.dangling ну или если прямо сырой надо: https://doc.rust-lang.org/std/ptr/fn.dangling.html

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

Ладно, верю. Я неопытный разработчик: п.1 не глядя в Стандарт не увидел бы.

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

учитывая возраст языков rust не доживёт до почётного(хотя бы из за времени) Сяшки

ибо Си это зерно гениев + бизнесу через ~10 лет стало интересно и окомитетили выгнав плюсы в отдельную эклезию

а rust хуже появился чем даже железная(из франции а не острова) ADA

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

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

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

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

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

Менее 32 вроде только в win16 типы были.

На AVR размер int тоже 16 бит.

Кстати, там же свое веселье с указателями. Нет, они обычные uint16_t числа, вот только адресация ОЗУ, ПЗУ (ну и EEPROM, но там-то ладно) не пересекается вообще никак. По значению указателя нельзя узнать указывает ли он на код или на данные.

В смысле, вместо int везде int64 делать?

И терять кучу тактов на ровном месте.

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

ибо Си это зерно гениев

Зерно в смысле seed (семя).

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

Если какая-то часть активно пользует int то там его и оставлять. главное чтоб вся работа с ним была в его либо большей размерности.

win16 была приведена в качестве единственного известного мне примера где int<32bit

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

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

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

с AVR у меня с 1-2 взгляда была мысль что здесь приемлем только ассемблер.

Одно время заморачивался с мегами…

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

Эм, в как же дос? Он намного популярнее чем win16, как ты его упустил?

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