Почему (% 256) отличается от (a & 0xFF)?

Я всегда предполагал, что при выполнении (a % 256) оптимизатор, естественно, будет использовать эффективную побитовую операцию, как если бы я написал (a & 0xFF).

При тестировании в компиляторе gcc-6.2 (-O3):

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

И при попытке другого кода:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

Похоже, я полностью что-то пропустил. Любые идеи?

Ответ 1

Это не то же самое. Попробуйте num = -79, и вы получите разные результаты от обеих операций. (-79) % 256 = -79, а (-79) & 0xff - некоторое положительное число.

Используя unsigned int, операции будут одинаковыми, и код, вероятно, будет таким же.

PS - Кто-то прокомментировал

Они не должны быть одинаковыми, a % b определяется как a - b * floor (a / b).

Это не так, как это определено в C, С++, Objective-C (т.е. все языки, на которых компилируется код в вопросе).

Ответ 2

Короткий ответ

-1 % 256 дает -1, а не 255, который равен -1 & 0xFF. Поэтому оптимизация была бы неверной.

Длинный ответ

С++ имеет соглашение, что (a/b)*b + a%b == a, что кажется вполне естественным. a/b всегда возвращает результат арифметики без дробной части (усечение в сторону 0). Как следствие, a%b имеет тот же знак, что и a или равен 0.

Деление -1/256 дает 0 и, следовательно, -1%256 должно быть -1, чтобы удовлетворить вышеуказанному условию ((-1%256)*256 + -1%256 == -1). Это, очевидно, отличается от -1&0xFF, который равен 0xFF. Поэтому компилятор не может оптимизировать то, как вы хотите.

Соответствующий раздел в С++ standard [expr.mul §4] от N4606 гласит:

Для интегральных операндов оператор / дает алгебраическое отношение с любой дробной частью, отброшенной; если частное выражение a/b представимо в типе результата, (a/b)*b + a%b равно a [...].

Включение оптимизации

Однако с использованием типов unsigned оптимизация будет полностью корректной, удовлетворяющей приведенному выше соглашению:

unsigned(-1)%256 == 0xFF

См. также this.

Другие языки

Это очень сильно отличается от разных языков программирования, так как вы можете найти Wikipedia.

Ответ 3

Так как С++ 11, num % 256 должен быть неположительным, если num отрицательный.

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

Было бы иначе, если num в вашем случае был unsigned: в наши дни я почти ожидал, что компилятор сделает оптимизацию, которую вы цитируете.

Ответ 4

У меня нет телепатического представления о рассуждениях компилятора, но в случае % существует необходимость иметь дело с отрицательными значениями (и округлением до нуля), тогда как при & результат всегда равен более низкие 8 бит.

Инструкция sar звучит для меня как "арифметическое право сдвига", заполняя освобожденные биты знаковым значением знака.

Ответ 5

Математически, по модулю определяется следующее:

a% b = a - b * floor (a/b)

Это право здесь должно очистить его для вас. Мы можем исключить слово для целых чисел, потому что целочисленное деление эквивалентно полу (a/b). Однако, если компилятор должен использовать общий трюк, как вы предлагаете, он должен работать для всех a и всех b. К сожалению, это не так. Математически говоря, ваш трюк на 100% правильный для целых чисел без знака (я вижу, что число слов, объявленных целыми целыми разрядами ответа, прерывается, но я могу подтвердить или опровергнуть это, поскольку -a% b должен быть положительным). Однако, можете ли вы сделать этот трюк для всех b? Возможно нет. Вот почему компилятор этого не делает. В конце концов, если modulo были легко записаны как одна побитовая операция, мы бы просто добавили модульную схему, такую ​​как добавление и другие операции.