Сдвиг вправо = странный результат? - PullRequest
4 голосов
/ 21 сентября 2010

Я помогал кому-то с домашней работой и столкнулся с этой странной проблемой. Проблема в том, чтобы написать функцию, которая меняет порядок байтов целого числа со знаком (так или иначе была указана функция), и вот решение, которое я придумал:

int reverse(int x)
{
    int reversed = 0;

    reversed = (x & (0xFF << 24)) >> 24;
    reversed |= (x & (0xFF << 16)) >> 8;
    reversed |= (x & (0xFF << 8)) << 8;
    reversed |= (x & 0xFF) << 24;

    return reversed;
}

Если вы передадите 0xFF000000 этой функции, первое присваивание приведет к 0xFFFFFFFF. Я не очень понимаю, что происходит, но я знаю, что это как-то связано с преобразованиями назад и вперед между подписанным и неподписанным или что-то в этом роде.

Если я добавлю ul к 0xFF, он будет работать нормально, что, я полагаю, происходит из-за того, что он вынужден подписать без знака, а затем преобразовать в подпись или что-то в этом направлении. Результирующий код также изменяется; без спецификатора ul он использует sar (арифметическое смещение вправо), но в качестве беззнакового он использует shr по назначению.

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

Заранее спасибо!

Ответы [ 6 ]

12 голосов
/ 21 сентября 2010

Поскольку x - это число со знаком , результатом (x & (0xFF << 24)) будет 0xFF000000, который также подписан, и, таким образом, отрицательное число, поскольку установлен старший (знаковый) бит,Оператор >> в int (значение со знаком) выполняет расширение знака (Редактировать: хотя это поведение не определено и зависит от реализации) и распространяет значение знакового бита 1, когда значение сдвигается вправо.

Вы должны переписать функцию следующим образом, чтобы работать исключительно с беззнаковыми значениями:

unsigned reverse(unsigned x)
{
    unsigned int reversed = 0;

    reversed = (x & (0xFF << 24)) >> 24;
    reversed |= (x & (0xFF << 16)) >> 8;
    reversed |= (x & (0xFF << 8)) << 8;
    reversed |= (x & 0xFF) << 24;

    return reversed;
}
7 голосов
/ 21 сентября 2010

Из ваших результатов мы можем сделать вывод, что вы работаете на 32-битной машине.

(x & (0xFF << 24)) >> 24

В этом выражении 0xFF является int, поэтому 0xFF << 24 также является int, как и x.

Когда вы выполняете битовое & между двумя int, результатом также является int, и в этом случае значение равно 0xFF000000, что на 32-битном компьютере означает, что бит знака установлен так что у вас есть отрицательное число.

Результат выполнения правого сдвига для объекта со знаком со отрицательным значением определяется реализацией. В вашем случае в качестве знакопеременного арифметического сдвига вправо выполняется.

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

3 голосов
/ 21 сентября 2010

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

Только не делай этого, это не переносимо.

1 голос
/ 07 октября 2010

Если вы хотите, чтобы он работал одинаково на всех платформах с целыми числами со знаком и без знака, измените

(x & (0xFF << 24)) >> 24

на

(x >> 24) & 0xFF
1 голос
/ 21 сентября 2010

x подписано, поэтому для знака используется старший бит. 0xFF000000 означает «отрицательный 0x7F000000». Когда вы делаете сдвиг, результатом является «расширенный знак»: двоичная цифра, которая добавляется слева для замены прежнего MSB, который был сдвинут вправо, всегда совпадает со знаком значения. Так

0xFF000000 >> 1 == 0xFF800000
0xFF000000 >> 2 == 0xFFC00000
0xFF000000 >> 3 == 0xFFE00000
0xFF000000 >> 4 == 0xFFF00000

Если сдвигаемое значение является беззнаковым или если сдвиг направлен влево, новый бит будет равен 0. Только в сдвигах вправо значений со знаками расширение знака входит в игру.

0 голосов
/ 21 сентября 2010

Если это Java-код, вы должны использовать '>>>', что означает беззнаковое смещение вправо, в противном случае оно будет подписывать расширение значения

...