Pull to refresh

Comments 121

Да я читал вашу статью.
Я просто хотел напомнить, что на Хабре исторически не любят, когда не понятно куда ведёт ссылка. А за реферал ссылки минусуют по самое небалуйся.
Хотя в остальных местах ваш подход преемлем.
SEO-параноики могут обминусоваться. :) Мне всё равно. Мы делаем интересные статьи для людей и поддерживаем ссылки. Программисты читающие наши статьи не ждут от нас, что по ссылке «методы» они попадут на порносайт рассказывающий про методы…
А как ваши редиректы помогут в случае пропажи контента по ссылке?
Тем, что мы время от времени мы отлавливаем такие ссылки. Вручную находим новое место со статьей и меняем в базе редирект.
Собственно для этого эта система и сделана. Почитайте статью.
Имхо лучше бы было бы иметь ссылки такие: www.viva64.com/go/alenacpp.blogspot.com/2005/08/unspecified-behavior-undefined.html

Во-первых я понимаю куда меня редиректят и могу в случае недоверия руками скопипастить урл, второй момент я понимаю что и где искать, если вдруг ваша редиректилка перестанет работать (что вполне возможно).
А что делать, когда ссылка уедет? Оставить как есть (старый вариант)? Тогда зачем он…
Показывать страничку со словами «Контент с данной страницы был удален, но присутствует здесь: %URL%. Перейти?».
Тем что я знаю куда перейду если ссылка жива, и в случае определенных умений и паранойи могу вытащить страничку из кэша гугла в случае пропажи?
Bit twiddling hacks точно никуда не денется. Ожидал по ссылке [3] именно его, и очень удивился, обнаружив, что ссылка ведет на viva64.com.
К чему приведёт?
((int)1) << 100

Кроме 0 не может быть никаких других значений

А в коде
(UInt32)b << (8 * i)

Вы уверены, что там именно 64 битное надо получить на выходе, а не 32? Может по алгоритму именно 32 надо.
Хотя, похоже на то, что таки 64 надо.
К чему приведёт? ((int)1) << 100

Теоретически это приведёт к неопределенному поведению программы. Практически это приведёт к нулевому значению. Поэтому и шатко правило, выявляющее подобные участки кода. Решил написать статью. Быть может, кто-то остановит меня и аргументированно отговорит делать соответствующее диагностическое правило.
Результат зависит от платформы и опций компилятора. Можно и 0 получить и ненулевое значение.
А на практике это кто-то может показать? Конкретный компилятор, конкретную платформу.
#include <stdio.h>

int main()
{
  volatile int i = 1;
  printf("%d\n", (i << 100));
}

msvc из SDK 7.1, 64 bit. Компилировать с -O2 и без оптимизации.
Нет под рукой. Прошу озвучить результат.
без оптимизации: 16, с оптимизацией — 0;
Отлично. Спасибо.

Теперь бы ещё пример разного поведения для (-1)<<1 найти.
А почему оно разное? Где в Стандарте это написано?
Нельзя сдвигать влево на величины, большие или равные количеству бит, а на один бит — пожалуйста.
В статье приведён соответствующий фрагмент из стандарта:… Otherwise, if E1 has a signed type and non-negative value, and E1*2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
Можно попробовать сдвинуть до нуля, и ловить компилятор на оптимизациях, когда он считает значение всегда меньшим нуля. Ну и собственно, различные оптимизации и будут теми неприятными ситуациями, когда такие вещи выстрелят так, что расхлебывать будешь очень долго, имхо.
нужна просто платформа, где отрицательные числа хранятся не в обратно-дополнительном коде. именно там -1 !== ~0
Поправка: не в дополнительном коде.

Уже обратный код дает -1 << 1 равным -3.
Прямой код даст вообще 2 или -2 в зависимости от тонкостей реализации.
Кроме 0 не может быть никаких других значений


Наивный компилятор, действуя по стандарту вполне мог бы сделать так:
    mov cl, 100
    mov eax, 1
    shl eax, cl

что было бы эквивалентно сдвигу влево на (100 % 32) = 4 с результатом 16.
Что-то мне ход мысли непонятен. Почему на 100%32?
А зачем в процессоре делать поддержку сдвига на «любое число»? Обычно железяка анализирует только последние 7 бит, игнорируя остальные. Это эквивалентно сдвигу на (N%32).
Вы не имете в виду циклический побитовый сдвиг влево, а просто побитовый влево?
sar eax,100
действительно даст 16
Хотел написать sal eax, 100
Смотрим Intel Manual:
The destination operand can be a register or a memory location. The count operand
can be an immediate value or the CL register. The count is masked to 5 bits (or 6 bits
if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if
64-bit mode and REX.W is used). A special opcode encoding is provided for a count
of 1.
То есть если вы напишете 100, это будет 0x64, после взятия младших 5 бит получится 4. 1<<4==16.
И даже более того, на 8086 получится таки наверно 0 (цитата из той же книжки):
The 8086 does not mask the shift count. However, all other IA-32 processors
(starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in
a maximum count of 31. This masking is done in all operating modes (including the
virtual-8086 mode) to reduce the maximum execution time of the instructions.
Хе. Добавил себе ещё одно правило держать в голове)
Хоть и ни разу больше, чем на 28 свдигать не приходилось
Предлагаю приобрести PVS-Studio. :)
Он будет помогать избегать таких пакостей и многих других, о которых можно даже не догадываться. Причём он будет делать это без устали и регулярно. Да ещё поможет новичкам, или тем, кто такие статьи не читает. Одно дело следить только за собой, а другое дело за всеми. :)
Вот сегодня, например, пакость.
Имеется shared_ptr с контенером this. После выхода за область видимости он удаляется, при чём два раза, что вызывает, конечно segfault. Целый день долбался с этой проблемой.
А как так могло получиться?
int A = -1 << 5; // undefined behaviour
int B = -1 >> 5; // unspecified behavior
>> Вывод один — так писать нельзя.
Глупость какая.
Если человек пишет такое, значит он рассчитывает на определённое поведение на целевых машинах.
Это может быть формирование масок или расширение знака в регистр для какого-нибудь abs(int):
sign= (x>>(sizeof(int)*8-1));
abs = (x^sign) — sign;

Все вменяемые современные CPU работают одинаково.
1) Тогда прошу ответить на вопрос. Как следует относиться к следующему коду, рассчитанный на определённое поведение на целевых машинах? :-)
int A = 1;
int B = A++ + ++A;

2) Не понял про CPU. Речь о том, что компилятор для неопределенных и неуточненных случаев может построить код, вычисляющий мусорный результат.
>>int B = A++ + ++A;
Это говно, а не код. Умышленно запутанная конструкция.

int B = A >> 5; работает понятно и определённо.
Рассчитывать на странное поведение >> это всё равно что рассчитывать на 5-битные байты

>> Речь о том, что компилятор для неопределенных и неуточненных случаев может построить код, вычисляющий мусорный результат.

Назовите какой компилятор и какой CPU так делает. Или это теоретические рассуждения?

Логический сдвиг влево работает одинаково для всех значений и знаковых и беззнаковых.
Арифметический сдвиг вправо реплицирует старший бит.
Это работает одинаково на всех мне известных системах везде от 8биток до супекомпьютеров.

Виталий, я рассчитываю, что через 2 дня будет готов новый рендер травы.
Логический сдвиг влево работает одинаково для всех значений и знаковых и беззнаковых.


Только что выше человек привёл пример, где (i << 100) даёт разный результат. :-)

Аккуратнее, аккуратнее :-)
Я вот очень аккуратен в исследованиях, вопросах и заметках. Я уже давно понял, что вообще не понимаю как писать на Си++ и как работают программы. :-)
Если не доходить до безумия вроде (i << 100), где i это int32, то писать просто.
Почему так получается? Потому что не думаю, что кому-то понравилась бы лишняя инструкция проверки длины операнда со стороны компилятора =)

MSVC, допустим, без оптимизации, если всё выражение в конечном итоге константа, то он сразу режет эту ситуацию в ноль.
Не могу понять, как (a >> 5) превратилось в (i << 100)
т.е. заведомо некорректный пример выхода за пределы разрядной сетки

Также и второй вопрос остался без ответа.
Так какой же компилятор и в каком режиме преобразует a>>5 в некорректный код.
А Вам не приходит в голову, что стандарт пишется не роботами, а людьми? и раз там есть упоминание такой ситуации и это названо неопределенным поведением, значит это поведение действительно отличается где-то. Или нет никакой уверенности, что поведение не изменится. Стандарт составляется коммитетом, чей суммарный запас знаний о процессорах явно превышает Ваш.
>>значит это поведение действительно отличается где-то
на ламповом чуде 50-лохматого года работающем прямом коде?

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

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

А вы пишите и запускаете тесты на целевых машинах, чтобы узнать являются ли они целевыми?
целевые машины это те, на которых должен выйти продукт
Вот именно. Вы всегда знаете, что это за машины?
Аргументированно.
С чем конкретно вы не согласны? И какие рекомендуете принять меры.
Как вы именно вы напишете хотя бы тот же abs(int) без бранчей и без сдвигов
Я его не буду писать. Т.к. на тех платформах, где такой abs нужен, он уже написан и в виде intrinsic в компилятор встроен.
Платформозависимый интринсик в VC?
Вы слышали о GCC?
1. intrinsic — всегда платформно-зависим.
2. да.
Мало того что платформо-, но и компиляторо-зависим.
Какой интринсик мне написать, если код рассчитан под 4 различных аппаратных платформы?
abs() это простейший пример.
Вы на каждый случай будете требовать специальный интринсик от всех в мире разработчиков компиляторов, на которых вдруг запустят ваш код?

Мне кажется, у вас какие-то превратные представления о том, как пишется код «рассчитанный на 4 различные архитектуры».

Кроме того, ваша реализация abs содержит ошибку — это лучшая демонстрация того, как делать не надо.
Т.е. если я хочу иметь портабельную эффективно работающую функцию это значит у меня какие-то превратные представления? =)
Использование интринсика для VC это, несомненно правильный путь.

>>Кроме того, ваша реализация abs содержит ошибку — это лучшая демонстрация того, как делать не надо.

За деревьями не видно леса?
abs это пример полезного использования сдвига

Какая ошибка? Переполнение?
Ну так просвятите, какой должен быть результат -0x80000000
в 32 битах
cc -O0 21.c -o 22_0
21.c: In function ‘main’:
21.c:6:5: warning: left shift count >= width of type [enabled by default]
holmes@darkstar:/home/projects/mc.old$ ./22_0
16

cc -O1 21.c -o 22_1
21.c: In function ‘main’:
21.c:6:5: warning: left shift count >= width of type [enabled by default]
holmes@darkstar:/home/projects/mc.old$ ./22_1
0


1 #include <stdio.h>
2
3 int main()
4 {
5 int i = 1;
6 printf("%d\n", (i << 100));
7 }
Уважаемый, вы на что отвечаете?
Мы обсуждали платформонезависимый abs, в частности реализованный через арифметический сдвиг руками _вправо_, ровно как и в «православном, платформозависимом интринсике VC»

что должен значить в данном контексте (i<<100)?
что оно собрано под gcc
Все, да не все…

Исторически сложилось два понимания арифметического (т.е. знакового) сдвига влево. Первое поведение полностью повторяет логический сдвиг (иногда это даже одна и та же инструкция), и именно такое поведение зачастую подразумевается программистами как очевидное.

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

Иными словами, если сдвигать влево на 1 бит число
1010 1010 1010 1010b

то может получиться как
0101 0101 0101 0100b

так и
1101 0101 0101 0100b.

Язык C является многоплатформенным, и потому его создателям пришлось выбирать между неопределенным поведением и сложной реализацией операции знакового сдвига влево. Насколько я понимаю ситуацию, плюсы и минусы обоих решений выглядят так:

Неопределенное поведение:
+: операция << всегда компилируется в одну инструкцию,
-: непереносимый код.

Сложная реализация:
+: переносимый код,
-: операция << для знаковых чисел компилируется в несколько инструкций.

При этом минус сложной реализации проявляется всегда, а минус неопределенного поведения — как правило, только если есть переполнение (иными словами, -1 << 1 всегда будет 2, за исключением совсем уж эзотерических процессоров). Именно потому неопределенное поведение и прописано в стандарте.
UPD: совсем забыл (хорошо хоть romy4 напомнил) что -1 << 1 равно -2 только для платформ, использующих дополнительный код
>> Все, да не все…
Обратите внимание на то что я написал
«Все вменяемые современные CPU работают одинаково. „

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

А уж вопрос о том, пойдёт ли PVS-Studio на такой машине, я думаю и не стоит
> Non-discrepant; no change will be implemented.
To clarify: the right shift operator SHOULD be logical, but in mspgcc an
arithmetic shift is used.

Т.е. ваш баг как раз подтверждает моё утверждение
в т.ч. арифметический сдвиг вправо работает одинаково
MSP430 это к тому же не процессор общего назначения, а 16битный DSP

MSP430 это general-purpose 16тибитный контролер с малым энергопотреблением, а никак не DSP. То, что в наборе инструкций присутствует MAC-операции его не делает DSP.
Хорошо.
Но это опять же не отменяет того факта, что «хороший и правильный» логический сдвиг вправо на нём _не работает_, а «нехороший и нестандартный» сдвиг знакового числа _работает_.

Правду минусами не победишь =)
То, что баг-репортер ожидал такого поведения не значит, что это баг.

В стандарте, процитированном выше, сказано, что если сдвиг вправо применен к отрицательному числу, то результат зависит от имплементации.

Стоит также помнить, что у разных процессоров разный набор инструкций. И довольно часто присутствуют и логический, и арифметический сдвиги вправо (старший бит расширяется вправо). Иногда ещё присутствуют до двух видов кольцевых сдвигов (в одном направлении). И в языке C используется только один вариант сдвига, остальные — недоступны.
Я, похоже, не совсем догоняю смысл этого багрепорта.
К какому типу был применён арифметический сдвиг?
Если к знаковому, то RRA работает точно так же как и на любых других современных процессорах.
Я уж не знаю что хотел сказать gribozavr этой ссылкой.

PS: смотрите профайл

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

Вот я и спрашиваю, в КАКОЙ имплементации он работает НЕ ТАК как у всех?
За вопросы тут минусуют на них не отвечая, а я это очень хочу, наконец, узнать.

>>И в языке C используется только один вариант сдвига, остальные — недоступны.
Недоступны кому?
-1 >> 5
1 >> 5

ГДЕ они оба недоступны? Назовите компилятор, пожалуйста
Вам привели пример — mspgcc.
Недоступны разработчику. Поясню, что имею ввиду. На многих контроллерах есть следующие инструкции сдвига вправо: логический, арифметический, циклический. Плюс эти же с участием бита в флаге переноса.

Из C с помощью операции >> доступен только один. В библиотеках компиляторов для таких архитектур, как правило, есть макросы, содержащие ассемблерные вставки для доступа.

И это нормально. Это — непереносимая часть, поэтому она не входит в состав С, разработанного, как низкоуровневый переносимый язык.
rla dst; arithmetic shift left emulated
rra src; arithmetic shift right

Arithmetic shift inserts zeroes for left shifts but the most significant bit, which
carries the sign, is replicated for right shifts.
sign= (x>>(sizeof(int)*8-1));
abs = (x^sign) — sign;


Что выдаст ваш код, если x равняется INT_MIN?
Некорректный результат.
Вас не смущает что INT_MIN*2 приводит к переполнению?
То есть самописная функция abs, которая возвращает отрицательные значения вам кажется нормальной?

Ну, ок.
Компьютерная целочисленная арифметика работает с конечной разрядной сеткой.
В этом нет ничего удивительного.
Не хватает 32 бит, используйте 64.
Любая функция в т.ч. не самописная не сможет в 32битах представить положительный результат от предельного отридцательного числа.
И какое это имеет отношение к обсуждаемому сдвигу?
Да как-то некрасиво вы со ссылками поступаете. Уж на википедию можно прямую ссылку давать неглядя да и вообще, ощущение что проблема высосана из пальца и «да мы же о вас заботимся!» смотрится как оправдание.
Прочитайте статью «Д'Артаньян и интернет, или работа над проблемой битых ссылок».
На Wikipedia ссылки плавают. Что-то удаляют, что-то объединяют, что-то переименовывают.
А не мог бы автор объяснить это утверждение:
int A = -1 << 5; // undefined behaviour
UFO just landed and posted this here
Не, мне цитату из Стандарта надо.
Чуть выше подсказали уже, спасибо.
С точки зрения стандарта его теперь следует переписать так:
static const int extend_offset[16] =
{ 0,
((~0u)<<1) | 1, ((~0u)<<2) | 1, ((~0u)<<3) | 1,
((~0u)<<4) | 1, ((~0u)<<5) | 1, ((~0u)<<6) | 1,
((~0u)<<7) | 1, ((~0u)<<8) | 1, ((~0u)<<9) | 1,
((~0u)<<10) | 1, ((~0u)<<11) | 1, ((~0u)<<12) | 1,
((~0u)<<13) | 1, ((~0u)<<14) | 1, ((~0u)<<15) | 1
};


А разве int всегда 32-битный? Опыть стандарты поменяли пока я спал? Или JPEG под микроконтроллеры уже не можно компилировать? :(
А причем тут размер int? Здесь нигде 32 бита не вспоминается.
P.S. Мой код тоже не идеал, но по крайней мере нет сдвига отрицательных чисел. Сейчас думаю, что ещё бы неплохо было сам массив extend_offset сделать unsigned.
Литеральный конструктор «0u»? Что обозначает буковка «u»?
Да. Соответственно, при этом и размер int. На мой профессиональный взгляд, приведенный код будет компилироваться некорректно, если размер «int» отличается от 32 бит — embedded всякий и прочая бесовщина, для которой JPEG регулярно компилируется. Вот я интересуюсь — это код неправильный или я что-то проспал?
int в С/С++ — от 16 битов и выше.
Соответственно, при этом и размер int

Загадочное предложение. Что при этом размер int?

unsigned может быть добавлен к любому целочисленному типу, это не повлияет на размер типа. C99 об этом говорит следующее (6.2.5:6):

For each of the signed integer types, there is a corresponding (but different) unsigned
integer type (designated with the keyword unsigned) that uses the same amount of
storage (including sign information) and has the same alignment requirements.

На мой взгляд приведённый код будет отлично компилироваться, с теми же результатами, что и оригинальный код.
Компилируем под эмбеддед, int 16 бит. Конструкция "~0u" превращается в двоичное «1111111111111111b», правильно? А в условиях задачи нужно получить «11111111111111111111111111111111b». То что код компилируется — это, конечно, хорошо — но, ИМХО, неплохо было бы чтобы он еще и работал :).
А, вон вы о чём. Да хз откуда эти условия в таком виде у автора. А вот код скомпилируется одинаково.
Нет, получить в случае 16-битного int нужно будет как раз 1111111111111111b, что собственно и произойдет. Я написал 11111111111111111111111111111111b для примера, имея в виду 32-битный int. Если бы я написал, скажем, 12 единичек (бывают 12-битные int), вопросов было гораздо больше. :)
бывают 12-битные int

Это где так?
http://en.wikipedia.org/wiki/12-bit:
Possibly the best-known 12-bit CPU is the PDP-8 and its relatives, produced in various incarnations from August 1963 to mid-1990. Many ADCs (analog to digital converters) have a 12-bit resolution. Some PIC microcontrollers use a 12-bit word size.

То есть вы утверждаете, что:
1. на нем есть ANSI C,
2. SHRT_MAX на этой платформе меньше 32767.

Так?
Я к тому, что Стандарт ANSI C _требует_ чтобы тип int содержал как минимум 16 бит. Поэтому очень странно выглядит утверждение о 12-битном int'е.

Возможно, вы его с «машинным словом» путаете.
Да, действительно. Как то не подумал. Возможно там нет Си.
Вы не могли бы указать, какой пункт стандарта это требует, а то я не смог с ходу найти?
Во-первых, я не туда ответил, а во вторых, я сам нашёл: С99 пункт 5.2.4.2.1 Sizes of integer types
Это хорошо, значит у меня еще не съехала крыша :).
Как же я хочу пожать вам руку за этот текст!
Я уже устал объяснять «закаленным в тяжелых боях» сишникам, что их код, наполненный сдвигами, чарпойнтерами и прочим гавном мамонта не дает никакой реальной выгоды, по сравнению с любым современным высокоуровневым API (говорю сейчас, в частности, про мобильные ОС). Эти люди, на пафосе, рассуждают о быстродействии, но код, в результате, невозможно прочитать и невозможно поддерживать.

Если говорить о современных мобильных ОС, то везде есть классы, для работы с битами. И на эти классы, по крайней мере, можно положиться в плане надёжности. В плане быстродействия, наверное, можно написать магический код, который будет работать чуть-чуть быстрее. Но понять этот код будет практически невозможно никому, кроме его автора. Да и сам автор через две недели будет несколько часов задумчиво чесать затылок, глядя на такой шедевр…

Подтверждаю. Читать код со сдвигами и побитовыми операциями очень трудно. Вдобавок там часто используются макросы. Это тяжёлое наследие. Но печальнее, что и сейчас продолжают так писать.
Вы не берёте во внимание хотя бы работу с железом в драйвере, где нет «то везде есть классы, для работы с битами» и «любым современным высокоуровневым API», в том числе и в ваших любимых мобильных ОС. Потому ваш взгляд довольно однобок и не затрагивает всех аспектов. Далеко не все железячники соглашаются не экономить биты в адресном пространстве своей железки — у них оно не резиновое и свопов нет, а ещё и может быть ограничено пропускной способностью шины или другими железками в цепочке.

Вообще наблюдаю картину, что многие программисты уже забывают, что есть и нижний уровень программ, где многие обплёвываемые ими, якобы устаревшие методы, вполне в ходу, т.к. огромные неповоротливые «высокоуровневые API» и «классы для работы с битами» либо просто не существуют (и не будут существовать), либо не могут обеспечить достаточной скорости. Зато когда доходит до дела и надо железку заставить работать, то вдруг выясняется, что и методы не такие уж и плохие и высокоуровневые плюшки не так уж хороши.
Автор как раз и говорит, что использовать эти инструменты без необходимости, в современных условиях, не просто плохой тон, а, практически, преступление.

Там, где это необходимо — вопросов быть не может. Любой инструмент надо уметь применять. Но когда человек начитался махровых книг 90х годов по С/C++, в которых было написано, что с помощью сдвига можно в 100 ускорить умножение на степень двойки и теперь лепит такой код на каждом шагу, осложняя жизнь окружающим людям… Это уже клиника. И лечится очень тяжело.
Плюс есть большое количество разнообразных процессоров, микроконтроллеров и DSP, оптимизация в компиляторах которых хромает (и сдвиговые операции часто быстрее и удобнее, чем умножение/деление). И, естественно, для них нет высокоуровневых API, виртуальных машин.
Сдвиги всегда быстрее чем деление. Умножение может быть однотактовым, но обычно занимает 2 и более тактов.
И то, и то может исполняться за 1 такт. Это уже не быстрее.

На суперскалярных архитектурах они вообще не сравнимы: что быстрее зависит от занятости модулей АЛУ и умножителей, зависимостей кода и других факторов. Дисциплин планирования исполнения микрокода, например.
Ерунда. У меня не было таких проблем, хотя я написал довольно много кода для работы с битовыми потоками. Ни с написанием, ни с последующим чтением кода.
А эти ваши замечательные классы (которые зачастую даже нумерацию бит используют не в соответствии с весами разрядов 2^n, а с начиная дибильной единицы), как правило говно такое, которое на чистом си написать невозможно никакими способами, хотя говнокода на си довольно много, как верно вами замечено.
Зачастую программистам приходится идти на компромиссы и пользоваться недокументированными возможностями, мир не идеален. И это касается не только ужасного говна мамонта, которым «современные» програмисты любят называть код, содержащий менее 9000 уровней абстракции, не говоря уж о (фу какая гаадость) чистом си, в котором даже сложениие выполняется просто оператором a+b, а не какимнить AdditionAbstractFactory. Пусть -1 << n это недокументированная возможность, но код более чем предсказуемо выливается в обычную команду sar, чем во многих случаях можно и стоит воспользоваться.
-1 >> 1 в команду sar, а -1 << n в команду sal (которая эквивалентна shl), если быть точнее -_-
Sign up to leave a comment.