LINUX.ORG.RU

Арифметический или логический сдвиг?


0

0

Здравствуйте!

GCC сдвиг выполняет не арифметический, не логический, а какой-то волшебный:

$ cat left64.c
#include <stdint.h>
#include <stdio.h>

#define SHIFT(N, COUNT) ({                                      \
        int a=COUNT;                                            \
        printf("a=%d\n", a);                                    \
        printf("%llu<<a = %llx\n", N, N << a);                   \
        printf("%llu<<%d = %llx\n\n", N, COUNT, N << COUNT);    \
})

int main()
{
        SHIFT(3ULL, 63);
        SHIFT(3ULL, 64);
        SHIFT(3ULL, 65);
        SHIFT(1ULL, 63);
        SHIFT(1ULL, 64);
        SHIFT(1ULL, 65);

        return 0;
}
$ gcc left64.c
$ ./a.out 
a=63
3<<a = 8000000000000000
3<<63 = 8000000000000000

a=64
3<<a = 3
3<<64 = 0

a=65
3<<a = 6
3<<65 = 0

a=63
1<<a = 8000000000000000
1<<63 = 8000000000000000

a=64
1<<a = 1
1<<64 = 0

a=65
1<<a = 2
1<<65 = 0

Как это понимать вообще?
Как сделать чтоб 1<<{переменная} и 1<<{число} выдавали одинаковый результат?
★★★★★

Знакомься - это undefined behaviour. В частности: "if the value of the right operand is negative or is greater or equal to the width of the promoted left operand, the behaviour is undefined".

В переводе на русский - ССЗБ ;)

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

> Вариант:

>(a>=64) ? 0 : 1ULL<<a

>не интересен -- ибо "грязный хак" :-)

Ну и молодежь пошла. Стандартов не читает, корректную технику программирования называет "грязным хаком". Куда мир катится? :/

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

Неопределённое поведение это, конечно, здорово, но, неужели, они хотя бы для себя внутри GCC не могли этот behaviour определить, чтобы хотя бы и 1<<64 возвращал 1?
Ладно, в любом случае спасибо, буду пользовать "корректную технику программирования" ;-)

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

Кстати, использование в роли правого операнда сдвига переменной как-то... "неконвенционально" :) А если левый операнд у тебя всегда один и тот же, сдвиг лучше сделать через таблично.

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

> А если левый операнд у тебя всегда один и тот же, сдвиг лучше сделать через таблично.

Утверждается, что это давно "не лучше". Смысл в том, что процессору проще посчитать что просили, чем обращаться к памяти.

Casus ★★★★★
()

> Как сделать чтоб 1<<{переменная} и 1<<{число} выдавали одинаковый результат?

В случаях, когда выражение имеет вид 1<<{число}, его вычисляет, очевидно, компилятор на этапе комбинации. В случае 1<<{переменная} оно вычисляется рантаймом. Думаю, разница в твоём случае от этого. Понятно, что 64 -- некорректное значение, но в целом я согласен, что несколько неконсистентно. Надо багрепорт гцц-шникам отправить :)

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

я думаю они в курсе и ничего менять не будут:

$ gcc -Wall -O0 left64.c -o left64
left64.c: In function &#8216;main&#8217;:
left64.c:14: warning: left shift count >= width of type
left64.c:15: warning: left shift count >= width of type
left64.c:17: warning: left shift count >= width of type
left64.c:18: warning: left shift count >= width of type

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

>> А если левый операнд у тебя всегда один и тот же, сдвиг лучше сделать через таблично.

>Утверждается, что это давно "не лучше". Смысл в том, что процессору проще посчитать что просили, чем обращаться к памяти.

Судя по результатам, там для сдвига с не-литералом в роли правого операнда вызывается функция. ИМХО, это займет больше времени (кроме 1-го раза, может быть).

Хотя, может, ты и прав. Надо мерять :)

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

> Судя по результатам, там для сдвига с не-литералом в роли правого операнда вызывается функция. ИМХО, это займет больше времени (кроме 1-го раза, может быть).

1. Какая такая функция? "<<"? Это оператор, и только. Одна команда ассемблера, при том правый операнд с большой вероятностью будет предсказан и заранее положен в кеш процессора. Или там сначала загрузка в регистр? В любом случае, предсказатель чего грузить в кеш тут не ошибётся, и код загрузки значения правого операнда будет распараллелен.

2. Мысль о порочности табличного метода не моя, вычитал из LKML, там это считается за истину :)

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

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

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

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.