Что такое оператор >>> = в C?

300

Учитывая коллегу как загадку, я не могу понять, как эта программа на самом деле компилируется и запускается. Что это за оператор >>>= и странный 1P1 литерал? Я тестировал в Clang и GCC. Предупреждений нет, а выход "???"

#include <stdio.h>

int main()
{
    int a[2]={ 10, 1 };

    while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )
        printf("?");

    return 0;
}
  • 1
    Подсказка: это не единственный оператор. Это несколько операторов рядом друг с другом.
  • 35
    Некоторые из них являются орграфами .
Показать ещё 16 комментариев
Теги:
obfuscation
bit-shift
literals
digraphs

3 ответа

438
Лучший ответ

Строка:

while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )

содержит digraphs :> и <:, которые переводятся на ] и [ соответственно, поэтому эквивалентно:

while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] )

Литерал 0xFULL совпадает с 0xF (который является шестнадцатеричным для 15); ULL просто указывает, что это unsigned long long литерал. В любом случае, как логическое значение true, поэтому 0xFULL ? '\0' : -1 оценивается как '\0', который является символом , численное значение которого просто 0.

Между тем 0X.1P1 представляет собой шестнадцатеричный литерал с плавающей запятой, равный 2/16 = 0.125. В любом случае, будучи ненулевым, оно также истинно как логическое, поэтому его двойное отключение с !! вызывает 1. Таким образом, все это упрощается до:

while( a[0] >>= a[1] )

Оператор >>= представляет собой составное назначение, которое бит сдвигает свой левый операнд на правое число бит, заданное правым операндом, и возвращает результат. В этом случае правый операнд a[1] всегда имеет значение 1, поэтому он эквивалентен:

while( a[0] >>= 1 )

или, что эквивалентно:

while( a[0] /= 2 )

Начальное значение a[0] равно 10. После смещения вправо один раз он становится 5, затем (округляется) 2, затем 1 и, наконец, 0, после чего цикл заканчивается. Таким образом, тело цикла запускается три раза.

  • 18
    Не могли бы вы рассказать о P в 0X.1P1 .
  • 76
    @Kay: Это то же самое, что e в 10e5 , за исключением того, что вы должны использовать p для шестнадцатеричных литералов, потому что e - это шестнадцатеричная цифра.
Показать ещё 7 комментариев
70

Это довольно неясный код, включающий digraphs, а именно <: и :>, которые являются альтернативными токенами для [ и ] соответственно. Существует также некоторое использование условного оператора . Существует также оператор смещения битов, назначение правого сдвига >>=.

Это более читаемая версия:

while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] )

и еще более читаемую версию, заменив выражения в [] на значения, которые они разрешают:

while( a[0] >>= a[1] )

Замена a[0] и a[1] для их значений должна упростить вычисление того, что делает цикл, т.е. эквивалент:

int i = 10;
while( i >>= 1)

который просто выполняет (целое) деление на 2 на каждой итерации, производя последовательность 5, 2, 1.

  • 0
    Я не управлял этим - это не произведет ???? Впрочем, а не ??? как ОП достал? (Ха.) Codepad.org/nDkxGUNi действительно производит ??? ,
  • 7
    @ Jongware 10 были разделены на первой итерации. Значения, оцениваемые циклом, равны 5, 2, 1 и 0. Таким образом, он выводится только 3 раза.
43

Перейдем к выражению слева направо:

a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]

Первое, что я замечаю, это то, что мы используем тернарный оператор из использования ?. Итак, подвыражение:

0xFULL ? '\0' : -1

говорит: "Если 0xFULL отличен от нуля, верните '\0', в противном случае -1. 0xFULL - это шестнадцатеричный литерал с беззнаковым длинно-длинным суффиксом - это означает шестнадцатеричный литерал типа unsigned long long Это не имеет большого значения, потому что 0xF может вписываться в регулярное целое число.

Кроме того, тернарный оператор преобразует типы второго и третьего членов в их общий тип. '\0' затем преобразуется в int, который просто 0.

Значение 0xF больше, чем ноль, поэтому оно проходит. Теперь выражение будет выглядеть следующим образом:

a[ 0 :>>>=a<:!!0X.1P1 ]

Далее, :> является digraph. Это конструкция, которая расширяется до ]:

a[0 ]>>=a<:!!0X.1P1 ]

>>= является подписанным оператором сдвига справа, мы можем выделить это из a, чтобы сделать его более ясным.

Кроме того, <: является орграфом, который расширяется до [:

a[0] >>= a[!!0X.1P1 ]

0X.1P1 - это шестнадцатеричный литерал с показателем. Но независимо от значения, !! всего, что отличное от нуля, истинно. 0X.1P1 0.125, который отличен от нуля, поэтому он становится:

a[0] >>= a[true]
-> a[0] >>= a[1]

>>= - это подписанный оператор сдвига справа. Он изменяет значение его левого операнда, сдвигая его биты вперед на значение на правой стороне оператора. 10 в двоичном формате 1010. Итак, вот шаги:

01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000

>>= возвращает результат своей работы, поэтому, если сдвиг a[0] остается ненулевым для каждого момента, когда его биты сдвигаются вправо на один, цикл будет продолжаться. Четвертая попытка заключается в том, что a[0] становится 0, поэтому цикл никогда не вводится.

В результате ? печатается три раза.

  • 3
    :> это орграф , а не триграф. Он не обрабатывается препроцессором, он просто распознается как эквивалент токена ] .
  • 0
    @KeithThompson Спасибо
Показать ещё 6 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню