vitus_wagner: My photo 2005 (Default)
[personal profile] vitus_wagner
Интересно, что количество бит в самом большом целочисленном типе, поддерживаемом современными компиляторами (int128_t), уже сравнялось с максимальным значением самого маленького (signed char).

Это к вопросу о том, как интерпретировать x << -1.

Мне пришли в голову две «естественные» интерпретации:

1. x << -1 = x >> 1.
2. x << -1 = x<< ((usigned что-то)-1) = x << 255 или x << 65535 и так далее.

В обоих случаях 1 << -1 = 0.

Но не все компиляторы с этим согласны. (понятно что по стандарту это undefined behavoir).

Date: 2016-04-05 08:33 am (UTC)
From: [identity profile] nathoo.livejournal.com
А можно профанский вопрос, от человека, который никогда не писал ничего "системного"(tm) и уже очень давно не писал ничего сложнее обработки логов перловым скриптом: для чего вообще могут быть нужны такие фокусы ?
Edited Date: 2016-04-05 08:35 am (UTC)

Date: 2016-04-05 08:37 am (UTC)
livelight: (lightning)
From: [personal profile] livelight
Чтобы поприкалываться, а заодно иногда сэкономить байт и наносекунду :)
Google: Obfuscated C contest

Date: 2016-04-05 09:30 am (UTC)
From: [identity profile] nathoo.livejournal.com
Я правильно понимаю, что продолжение этой вполне здравой логики приведет нас к реизобретению паскаля ? ;-)

Date: 2016-04-05 09:40 am (UTC)
From: [identity profile] qkowlew.livejournal.com
Оно периодически случается. Ява - наиболее яркий случай.

Date: 2016-04-05 04:20 pm (UTC)
From: [identity profile] slobin.livejournal.com
Ява -- нихрена не новый Паскаль. Она могла бы стать новым Паскалем (ничего в самом ЯЗЫКЕ этому не препятствует), если бы выжила, условно говоря, линия gjc -- компиляция в отдельно стоящий исполняемый файл, без многомегабайтного рантайма и с мгновенным запуском. Ещё раз -- с Явой такое сделать можно, и было реально сделано. Но не прижилось.

А новым Паскалем окажется, я надеюсь, какой-нибудь из языков "новой волны". Более-менее неважно какой, любой из них лучше, чем сложившаяся сейчас ситуация. Да хоть бы та же няшная Гошечка. :-) (хотя у него, разумеется, свои недостатки, но у кого их нет? И, кстати, "язык слишком бедный, в нём нет того, того и вон того" к недостаткам не относится).

... Ледяные ласки огненных лисиц ...

Date: 2016-04-05 09:07 am (UTC)
From: [identity profile] qkowlew.livejournal.com
Всё, для чего в копмпиляторе не определено точное поведение, но при этом может быть написано программистом, откомпилировано и исполнено, это источник:
- ошибок обновления компилятора. Так как сегодняшняя версия компилирует один код, а завтрашняя - другой. Просто из-за того, что поведение undefined - никто не тестировал.
- ошибок обновления кода. Опять же - при редактировании собственной программы автор не задумывается о данном кусочке, ичто он изменит работу.
- ошибок несовместимости архитектур ПРОЦЕССОРА - "на x86 работаем, на x64 нет" хотя бы
- ошибок различия сред исполнения. Пример из реальной жизни - одна и та же ДОС/Win95 программа, запущенная в досбокс в современной Windows. Ещё смешнее - одна и та же программа, запущенная в английской и русской версиях операционки. Достаточно ОДНОГО национального символа в одном пути к файлу чтобы получить потрясающие спецэффекты.
Edited Date: 2016-04-05 09:08 am (UTC)

Date: 2016-04-05 09:40 am (UTC)
livelight: (lightning)
From: [personal profile] livelight
В языке Си есть и активно используется куча всего такого, для чего не определено единое для всех платформ поведение. Например, int имеет такой размер, какой удобно платформе, что уже неприятно, если вдруг данные, которые вчера влезали, сегодня не влезают, не говоря уже о записи их на внешние устройства для чтения кем-нибудь. Элементы массива struct'ов компилятор может по своему желанию выровнять на границу слова. Аналогично - какой-нибудь long внутри struct, особенно если архитектура процессора не позволяет ему адресовать невыровненные слова. Учитывая обычные для Си операции вида "привести указатель к char* , прибавить сколько-то, откастовать полученное к другому типу указателя и что-нибудь сделать с полученным", простор для непереносимостей вообще гигантский. Так что - только java, только хардкор :)

Date: 2016-04-05 12:58 pm (UTC)
From: [identity profile] metaclass.livejournal.com
stdint.h спасет от всего.

Date: 2016-04-05 01:09 pm (UTC)
livelight: (Default)
From: [personal profile] livelight
От выравниваний не спасёт :)
От разных endianness тоже, кстати.

И вот такой код

struct A {
whatever1 x;
whatever2 y;
whatever3 z;
} a[2];

write(fd, a, sizeof(a));


может нанести немало неожиданностей даже для типов whatever... из этой библиотеки.

Date: 2016-04-05 03:14 pm (UTC)
From: [identity profile] qkowlew.livejournal.com
Даже в пределах одного x86 процессора не спасает, как только вступает в действие генерация кода с адресной арифметикой. Turbo C 2.0, модель памяти Huge. Я в ней писал и компилил ФИДО тоссер и мопутствующие ему программы, если что. ;-)

Date: 2016-04-05 09:43 am (UTC)
From: [identity profile] qkowlew.livejournal.com
Если вы писали содержательную обработку логов перловым скриптом хотя бы в эпоху однобайтовых кодировок, и запускаете её сейчас, с UTF кодировками символов - вы уже можете столкнуться с такой же по идеологии, хотя и другой по "внутреннему устройству" проблемой.

Date: 2016-04-05 09:57 am (UTC)
From: [identity profile] nathoo.livejournal.com
Ммм, вспомнил, что уже читал что-то Ваше на тему рулезности хождения строем простого и читаемого кода. Собственно, нет причин для спора.
Однако, мне действительно интересно, где в современном мире могут быть всерьез нужны фокусы с булевой арифметикой ? Навскидку приходят в голову только IP адреса и маски, потому что, на сколько я понимаю, сейчас даже контроллер осещения в "умном доме" тупо дешевле сделать на x64, под линуксом и, соответственно, с полным набором языков высокого уровня.

BTW, мб вспомним фидошные времена и перейдем на ты ?

Date: 2016-04-05 11:13 am (UTC)
From: [identity profile] qkowlew.livejournal.com
Дело не в простом и читаемом коде.
Даже самый простой и читаемый код может оказаться неработоспособным просто из-за того, что входящие данные незаметно сменили кодировку "на низком уровне".

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


То есть НЕ СЛЕДУЕТ ВООБЩЕ употреблять "данных низкого уровня" и обрабтку "низкого уровня" при написании программ (на любом языке, высокого ли уровня или нет - не важно)?

И, конечно же, написанный и откомпилированный сейчас код "на языке высокого уровня" никогда не нарвётся на то, что обрабатываемые им данные существенно изменились?

Умный дом? С автоапдейтящимися прошивками в железках, ога. Спасибо.
Через 5 лет, после смены таймзоны и апдейта прошивки под оную смену (например) - что будете делать (реальный случай - размер буфера под выдаваемой некоторой прошивкой текстовое представление времени оказался недостаточен при изменении наименования таймзоны, в результате - атака по переполнению стека и креш системы)? А что будет делать клиент, если автор программы "высокого уровня" в одном из контроллеров успел помереть?

Ах, ФИДО? См. bink plus и необходимость добавить 42 в одном месте в календарном расчёте в СТАНДАРТНОЙ библиотеке в 2000 году. Раскопано Киром Злобиным.

Runtime Error в паскалевских (дада, язык высокого уровня!) программах ДОС эпохе при запуске на достаточно быстрых процессорах.

Правда, зачем думать о низком уровне? Давайте писать на высоком и не думать вообще. :)
Edited Date: 2016-04-05 11:16 am (UTC)

Date: 2016-04-05 12:34 pm (UTC)
From: [identity profile] qkowlew.livejournal.com
Проблема в "языке высокого уровня" как раз и состоит в том, что, оставаясь ТОЛЬКО на "высоком уровне", программист не имеет содержательной информации о том, какую библиотеку (из имеющихся вроде бы одинаковых) следует выбрать.

И потому выбирает "сердцем". :)

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

Собственно - так и пришлось в 2000 году патчить кусочек сишной библиотеки в bink+.

Date: 2016-04-05 12:18 pm (UTC)
From: [identity profile] nathoo.livejournal.com
>>тупо дешевле сделать на x64, под линуксом и, соответственно, с полным набором языков высокого уровня.

>То есть НЕ СЛЕДУЕТ ВООБЩЕ употреблять "данных низкого уровня" и обрабтку "низкого уровня" при написании программ (на любом языке, высокого ли уровня или нет - не важно)?

Где я это сказал ?
Я просил привести пример оправданного прменения булевой арифметики в современной программе.

> Умный дом? С автоапдейтящимися прошивками в железках, ога. Спасибо. Через 5 лет, после смены таймзоны и апдейта прошивки под оную смену (например) - что будете делать (реальный случай - размер буфера под выдаваемой некоторой прошивкой текстовое представление времени оказался недостаточен при изменении наименования таймзоны, в результате - атака по переполнению стека и креш системы)? А что будет делать клиент, если автор программы "высокого уровня" в одном из контроллеров успел помереть?

В моем случае, клиент будет лежать в гробу, глубоко безразличный к проблеме, тк умный дом он строит для себя сам. :-)
В стандартном - свяжется с саппортом, или (если это будет очень умный клиент :-) соберет нужную прошивку сам из открытых исходников.
Вообще, мне кажется, термин прошивка не совсем применим к современным устройствам. Правильнее говорить про ОС и прикладной софт. Соответственно, нормальный процесс исправления ошибок в прикладном софте. Как вариант - зайти на контроллер ssh-м и обновить соответствующий пакет.

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

> Правда, зачем думать о низком уровне? Давайте писать на высоком и не думать вообще. :)
Ну, как бы, да. Мир к тому идет. В частности, Вашими усилиями. Нет ?
Edited Date: 2016-04-05 12:20 pm (UTC)

Date: 2016-04-05 12:38 pm (UTC)
From: [identity profile] qkowlew.livejournal.com
оправданного прменения булевой арифметики в современной программе.

Почти любая реализация видеокодеков.
Содержательная обработка данных с конкретного железа.
При соответствующе документированном протоколе.
Edited Date: 2016-04-05 12:39 pm (UTC)

Date: 2016-04-05 12:41 pm (UTC)
From: [identity profile] nathoo.livejournal.com
Спасибо.
По дороге сам вспомнил про криптографию :-)

Date: 2016-04-05 03:10 pm (UTC)
From: [identity profile] besm6.livejournal.com
Во, я как раз хотел сказать.

Компактные сетевые протоколы с быстрым парсингом, кстати. Типа CBOR. Она там примитивная, но есть, и обеспечивает компактность.

Когда данных надо передать МНОГО, компактность протокола ой, важна...

Date: 2016-04-05 03:33 pm (UTC)
From: [identity profile] qkowlew.livejournal.com
Если уж вспоминать про ФИДО - то лично я в своём тоссере ФИДО почты ВСЕ таблицы подписки линков на эхи храню побитно.

AreaLink[area_off] |= here_mask1;
AreaLink[area_off] &= here_mask0;
area_off - понятное дело, адрес байта. :)

Date: 2016-04-05 03:30 pm (UTC)
From: [identity profile] slobin.livejournal.com
Справедливости ради: не путаем "низкий (битовый) уровень" и "неопределённое поведение". Хотя, будем честны, спутать легко: и там, и там проблема на практике в том, что не слишком внимательный программист может просто не обратить внимание, что оно (2) не определено или (1) определено, но не так, как он наивно надеялся. Случай с 42 относился именно к "определено, но никто не прочитал определения".

И, кстати, конкретно эта засада единственно правильным способом не засажена в языке Ада (да-да, том самом): там есть ДВЕ операции `mod` и `rem`. Если их две, то ты невольно задумаешься, какая именно из них нужна. А если у тебя только `%`, который совпадает с одной из них, причём не с той -- вопрос имеет куда более высокие шансы быть проигнорированным.

... Маркс, Кейнс и кот Матроскин ...

Date: 2016-04-05 12:11 pm (UTC)
From: [identity profile] amarao-san.livejournal.com
Вот слабая типизация Си в этом вопросе меня всегда раздражала. Ведь никто и ничто не требует от компилятора автоматически приводить типы. Никакого runtime overhead'а или уменьшения возможности стрелять себе в ногу это не вызывает.

Почему бы не требовать ручной конвертации типов? Чтобы такое нельзя было:
unsigned int x=foo();
...
signed int y = x/2;

и т.д. Т.е. строгая типизация с правом typecast'инга как душе взбредёт. Но явным.

Date: 2016-04-05 02:23 pm (UTC)
From: [identity profile] sur-kg.livejournal.com
Нормальные компилеры в случае такой фигни (неоднозначность интерпретации арифметической/логической ОПЕРАЦИИ) вроде как ругаются ворнингом.

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

Date: 2016-04-05 03:29 pm (UTC)
From: [identity profile] amarao-san.livejournal.com
Ну вот я не понимаю логику за "программист сам виноват", если добавление строгой типизации не ударит по райнтайму.

Нормальный подход (задним числом, спустя несколько десятилетий после создания языка) - сделать типизацию строгой.

То есть никакой конверции unsigned/signed. Хочешь конверсию - делай либо typecasting, либо пиши явно что делать.

Сколько бы при этом граблей прошло - словами не описать. А написать чуть больше union'ов - ни у кого бы руки не отвалились.

Заодно, кстати, и на int (x64) перешли бы по-человечески, потому что к этому моменту все бы точно знали из чего во что конвертят.

Неявное приведение типов - как открытый люк на хайвее. Внимательный водитель заметит и объедет. Невнимательный может быть проскочит, а может быть и без оси останется.

Date: 2016-04-05 03:37 pm (UTC)
From: [identity profile] sur-kg.livejournal.com
хотя с
>(задним числом, спустя несколько десятилетий после создания языка) - сделать типизацию строгой.
наверняка было бы дофига головной боли,

мне ничего не остается, как согласиться с Вашими аргументами.

Date: 2016-04-05 07:36 pm (UTC)
livelight: (lightning)
From: [personal profile] livelight
После неявного приведения типов в плюсах, где строгости ради решили ограничиться одним неявным вызовом конструктора, и на том спасибо, а то ж могли и пятью -- конверсия между signed и unsigned - это вообще мелочи жизни :)

Date: 2016-04-06 04:06 am (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan

Самый эпичный баг нестрогости системы типов в плюсах, который я сажал, был, когда я поменял тип одной переменной с int на int8_t.

int foo;
std::istringstream iss1("3");
iss1 >> foo; // foo == 3

int8_t bar;
std::istringstream iss2("3");
iss2 >> bar; // bar == 51

Date: 2016-04-06 08:38 am (UTC)
livelight: (Default)
From: [personal profile] livelight
Со вторым примером ситуация вообще другая.
Компилятор увидел, что оператора >> (int8_t &) не завезли, после чего сделал по сути вот такой код для вызова оператора >> (int &)

int8_t bar;
std::istringstream iss2("3");
int bar_replacement = (int)bar;
iss2 >> bar_replacement;


А в переменной bar осталось то, что осталось.

По-хорошему, за подмену ссылочного типа без явного каста компилятор должен бить по рукам гораздо сильнее, чем за подмену скалярного. А тут он сам занялся даже не просто подменой ссылочного типа, а подменой самой ссылки. Для оператора << это сошло бы с рук, а для >> вышло то, что вышло.
Edited Date: 2016-04-06 08:39 am (UTC)

Date: 2016-04-06 09:23 am (UTC)
livelight: (Default)
From: [personal profile] livelight
А, там внутре у него char был?
Тоже неплохо :)

Date: 2016-04-06 09:33 am (UTC)
livelight: (Default)
From: [personal profile] livelight
Насколько я помню, в стандарте большими буквами написано, что char - это ровно 1 байт, и если от этого отойти, то все стандартные способы работы с адресной арифметикой, используемые в гигабайтах уже написанного кода, полетят к чертям.
А вот какому-нибудь short short'у никто не запрещает тоже иметь размер 1 байт.

Date: 2016-04-06 10:01 am (UTC)
livelight: (Default)
From: [personal profile] livelight
Если минимальный квант адресации больше 8 бит, то тип int8_t получить вообще не получится, кроме как в битовых структурах. Но такое по указателю в operator >> точно не передашь никак.

Date: 2016-04-06 10:38 am (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan
В идеале, int8_t был бы отдельным примитивным типом. Но в стандарте написано, что это typedef на какой-нибудь знаковый целочисленный тип на усмотрение реализации. В моей реализации — typedef signed char int8_t.

Date: 2016-04-05 02:39 pm (UTC)
From: [identity profile] sur-kg.livejournal.com
По поводу того, как компилятор интерпретирует битовый сдвиг (если я все понимаю правильно и не отстал от жизни) - он тупо помещает левый и правый аргументы в регистры и генерит инструкцию shl/shr/sal/sar (в зависимости от знаковости типа ПЕРВОГО аргумента), оставляя undefined behaviour на совесть процессора.

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

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

Date: 2016-04-06 04:14 am (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan

Достаточно умный компилятор ещё замечает использование выражения в правом аргументе оператора сдвига и начинает использовать его для оптимизации всего вокруг.

if (foo < 0) {
    do_something();
}
bar << foo;

Если известно, что do_something() не бросает исключений, не завершает программу и не зацикливается, то компилятор имеет право объявить весь if мёртвым кодом и выоптимизировать.

Date: 2016-04-06 12:47 pm (UTC)
From: [identity profile] sur-kg.livejournal.com
А если do_something() изменяет значение foo?

Date: 2016-04-06 01:55 pm (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan
Согласен с уточнением.

Date: 2016-04-06 01:26 pm (UTC)
livelight: (lightning)
From: [personal profile] livelight
Это какой-то неправильный компилятор. Использование выражения справа от сдвига вовсе не гарантирует, что оно неотрицательно. А то с таким же успехом и по точно такой же логике можно вот такой вот if выоптимизировать нахрен:

if (ptr == NULL) {
do_something();
}
*ptr = 0;


Всё же прежде чем сломаться в том, что после if'а, хорошо бы сначала выполнить имеющий побочные эффекты код внутри него.

Date: 2016-04-06 01:59 pm (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan

Я пропустил условие, что foo не меняется между проверкой и использованием в правом аргументе сдвига.

Безопасностные дырки, основанные на том, что компилятор выоптимизировал что-нибудь, руководствуясь принципом «если бы это было нельзя выоптимизировать, тут в любом случае бы было UB», были.

Date: 2016-04-06 02:11 pm (UTC)
livelight: (Default)
From: [personal profile] livelight
В данном случае неважно, do_something() может и заведомо не влиять на foo или ptr.
Суть в том, что это неправильный компилятор, который заранее решает, что предусловия второй части кода выполнены, а посему закроем глаза и будем надеяться на лучшее, а значит, иф из первой части кода не нужен.

Date: 2016-04-06 03:35 pm (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan

Факт тот, что стандарт разрешает делать такие оптимизации, и что реальные компиляторы их делают.

Date: 2016-04-06 08:58 pm (UTC)
livelight: (lightning)
From: [personal profile] livelight
Я не совсем понял, что такое "post-classical compiler". Возможно, я слишком мало изучал ад и коровники, и слишком много - как оно должно быть по уму. Но все эти утверждения на тему "something unpredicatble can happen in this branch => this branch can be treated as unreachable" наводят меня на мысли, что упоролись или авторы стандарта языка Си, или автор статьи. Тут я поступаю как тот пост-классический компилятор и предполагаю таки второе.

Profile

vitus_wagner: My photo 2005 (Default)
vitus_wagner

June 2025

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 2nd, 2025 01:01 pm
Powered by Dreamwidth Studios