C - Побитовая манипуляция

Я очень новичок в 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) и не понимаю, что он делает. Может ли кто-нибудь подробно объяснить вторую строку кода?

Это немного сдвиг в направлении стрелок. Но, боже мой, этот код либо слишком умен для меня, либо он действительно очень непонятен, неудивительно, что вы боретесь. Попробуйте записать данные в двоичном формате и выполнить его вручную. например 2 << 1 равно 4. Попробуйте выполнять одну операцию за раз с printf между ними, чтобы лучше ее обработать.

Pam 27.08.2018 11:40

Я предполагаю, что он просто будет работать в том порядке, в котором он появился - проблема в том, что это два битовых сдвига один за другим. Математически это делает (x * (2 ^ x)) * 2 ^ x. Неужели есть способ написать это попроще?

Pam 27.08.2018 11:46

Чтобы было понятно: Приведенный выше код написан крайне плохо. Вы не увидите в реальной жизни кодовых баз, если они написаны настоящими профессионалами. Хорошие выражения легко понять. Я не понимаю, почему некоторые учебники / книги / учителя бросают эту чушь в новичков.

user694733 27.08.2018 11:49

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

Martin James 27.08.2018 11:55

@MartinJames: «Вам не нужно изучать, как это работает». Я думаю, что это достаточно хороший повод, чтобы чему-то не учиться.

phoxis 27.08.2018 12:02

Это запутанный код и не подходит для учить о битовых операциях.

too honest for this site 27.08.2018 14:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
283
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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 27.08.2018 11:50

@melpomene Спасибо за это дополнение!

Ilya 27.08.2018 11:52

Я думаю, также стоит отметить, что, поскольку << остается ассоциативным, x << x << x оценивается как (x << x) << x

dmuir 27.08.2018 12:02

@dmuir Спасибо! Я расширил ответ этими пояснениями.

Ilya 27.08.2018 12:28

На самом деле нет гарантии, что! 0 («не 0») будет оценено как 1. Это может быть любое ненулевое значение.

NoamD 27.08.2018 13:10

@NoamD есть - !0 == 1

Sander De Dycker 27.08.2018 13:28

@NoamD Конечно, такая гарантия существует, C17 6.5.3.3 §5 «Результатом оператора логического отрицания! Является 0, если значение его операнда не равно 0, 1, если значение его операнда сравнивается с 0. " Стандарт не может быть более ясным, чем это.

Lundin 27.08.2018 13:34
Ответ принят как подходящий

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

Глядя на подвыражение 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

Другие вопросы по теме