Я очень новичок в C, и я пытаюсь понять побитовые операторы в C Я нашел этот код перед собой (чтобы преобразовать 2 в 37)
int main(void)
{
int x = 2;
x = (x<<x<<x) | (x<<x<<x) | (x << !!x) | !!x ;
printf("%d\n" , x ); // prints 37
}
Сейчас я впервые вижу что-то вроде этого (x<<x<<x) и не понимаю, что он делает.
Может ли кто-нибудь подробно объяснить вторую строку кода?
Я предполагаю, что он просто будет работать в том порядке, в котором он появился - проблема в том, что это два битовых сдвига один за другим. Математически это делает (x * (2 ^ x)) * 2 ^ x. Неужели есть способ написать это попроще?
Чтобы было понятно: Приведенный выше код написан крайне плохо. Вы не увидите в реальной жизни кодовых баз, если они написаны настоящими профессионалами. Хорошие выражения легко понять. Я не понимаю, почему некоторые учебники / книги / учителя бросают эту чушь в новичков.
Да, это надуманный код домашнего задания. Не беспокойтесь об этом - никто, кроме академических кругов, никогда не пишет такой код. Вам не нужно изучать, как это работает.
@MartinJames: «Вам не нужно изучать, как это работает». Я думаю, что это достаточно хороший повод, чтобы чему-то не учиться.
Это запутанный код и не подходит для учить о битовых операциях.





Я бы порекомендовал вам разделить эту длинную строку на несколько небольших частей (иногда более эффективно попробовать такие вещи, т.е. чтения документации недостаточно):
int x = 2;
printf("%d\n", x); // prints 2
printf("%d\n", x << x); // prints 8
printf("%d\n", x << x << x); // prints 32
printf("%d\n", !!x); // prints 1
printf("%d\n", x << !!x); // prints 4
printf("%d\n", x); // prints 2 (just to become sure that x was not changed)
Итак, вы знаете, что начальная длинная строка равна x = (32 | 32 | 1 | 4). Но это 32 + 4 + 1 = 37.
Посмотрим подробнее:
Что такое << и >>?
Операторы сдвига поразрядно сдвигают значение слева на количество битов справа:
<< сдвигается влево и добавляет нули в правый конец.>> сдвигается вправо и добавляет либо 0, если значение является беззнаковым типом, либо расширяет верхний бит (для сохранения знака), если это тип со знаком.Также стандарт C говорит о E1 >> E2: "Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией." Арифметический сдвиг не гарантируется.
Поскольку << является левоассоциативным, x<<x<<x оценивается как (x<<x)<<x.
См. Также: Что такое операторы побитового сдвига (побитового сдвига) и как они работают?
Что такое !!x?
Это унарное НЕ и унарное НЕ.
!!x можно использовать как сокращение для (x != 0 ? 1 : 0).
Стандарт C говорит о E1 >> E2: "Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией." Арифметический сдвиг не гарантируется.
@melpomene Спасибо за это дополнение!
Я думаю, также стоит отметить, что, поскольку << остается ассоциативным, x << x << x оценивается как (x << x) << x
@dmuir Спасибо! Я расширил ответ этими пояснениями.
На самом деле нет гарантии, что! 0 («не 0») будет оценено как 1. Это может быть любое ненулевое значение.
@NoamD Конечно, такая гарантия существует, C17 6.5.3.3 §5 «Результатом оператора логического отрицания! Является 0, если значение его операнда не равно 0, 1, если значение его операнда сравнивается с 0. " Стандарт не может быть более ясным, чем это.
Это известно как «обфускация»: написание излишне сложного кода для того, чтобы что-то казалось более продвинутым, чем оно есть на самом деле.
Глядя на подвыражение x<<x<<x, это простой логический сдвиг влево. ассоциативность операторов операторов сдвига находится слева направо, поэтому выражение равно (x<<x)<<x.
Мы вышли из смены
2 на 2 и получим 8. 8 << 2, сдвинем влево 8 на 2 и получим 32:
x = 32 | 32 | (x << !!x) | !!x ;
Тогда для любого выражения с поразрядным ИЛИ 32 | 32, где операнды 32 идентичны, это то же самое, что просто записать 32 без ИЛИ. Таким образом, это эквивалентно:
x = 32 | (x << !!x) | !!x ;
!! - это довольно распространенный, хотя и непонятный трюк в C для преобразования любого целого числа в логическое значение 0 или 1. !! - это не один оператор, а двойной оператор логического «не» !. Сначала у нас есть !2, то есть 0. Затем !0, который дает 1. Остается вот что:
x = 32 | (2 << 1) | 1;
2 << 1 равен 4, поэтому:
x = 32 | 4 | 1;
Запишите его в двоичном формате:
0010 0000
OR 0000 0100
OR 0000 0001
------------
0010 0101 = 0x25 hex = 37 dec
Это немного сдвиг в направлении стрелок. Но, боже мой, этот код либо слишком умен для меня, либо он действительно очень непонятен, неудивительно, что вы боретесь. Попробуйте записать данные в двоичном формате и выполнить его вручную. например 2 << 1 равно 4. Попробуйте выполнять одну операцию за раз с printf между ними, чтобы лучше ее обработать.