Що таке >>> = оператор у C?


294

Даний колегою як головоломка, я не можу зрозуміти, як насправді компілюється та запускається ця програма C. Що це за >>>=оператор і дивний 1P1буквал? Я пройшов тестування в Clang та GCC. Немає попереджень і вихід "???"

#include <stdio.h>

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

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

    return 0;
}

36
Деякі з них - це диграфи .
juanchopanza

12
@Kay, ні в цьому випадку::> =] тоді a [...] >> = a [...]
Adriano Repetti

6
@Marc Я не думаю, що це може бути ">>> =", оскільки це не буде компілюватись, однак наведений вище код насправді компілюється.
CustomCalc

21
0x.1P1Шістнадцяткова буквальний з показником. Це 0x.1числова частина, або 1/16 тут. Число після 'P' - потужність двох, число множиться на. Так 0x.1p1це дійсно 1/16 * 2, або 1/8. А якщо вам цікаво, 0xFULLщо це просто 0xF, і ULLє суфіксом дляunsigned long long
jackarms

71
Синтаксис C - нескінченний матеріал для любителів і дрібниць, але в кінцевому рахунку не все, що важливо.
Керрек СБ

Відповіді:


468

Лінія:

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

містить графіки :> і <:, які перекладаються на відповідно ]і [відповідно, тож еквівалентно:

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

Буквал 0xFULL- це те саме, що 0xF(що є шестигранним 15); то ULLпросто вказує , що Це unsigned long longбуквальне . У будь-якому випадку, як булеве, це правда, так 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.
Кей - SE є зло

77
@Kay: Це те саме, що і eв 10e5, за винятком того, що ви повинні використовувати pдля шістнадцяткових літералів, тому що eце шістнадцяткова цифра.
Дітріх Епп

9
@Kay: Шістнадцятковий поплавковий літерал є частиною C99, але GCC також приймає їх у код C ++ . Як зазначає Дітріх, pрозділяє мантісу і показник, як і eв звичайній науковій поплавковій нотації; одна відмінність полягає в тому, що при шістнадцяткових плавцях основа експоненціальної частини дорівнює 2 замість 10, тобто 0x0.1p1дорівнює 0x0,1 = 1/16 разів 2¹ = 2. (У будь-якому випадку тут нічого з цього не має значення; будь-який ненульовий значення буде працювати однаково добре.)
Ілмарі Каронен

6
@chux: Мабуть, це залежить від того, компільований код як C або (як він був позначений спочатку) C ++. Але я зафіксував текст, щоб сказати "літеральний символ" замість " charбуквальний", і додав посилання у Вікіпедії. Дякую!
Ільмарі Каронен

8
Приємне скорочення.
Корі

69

Це деякий досить туманний код з участю диграфів , а саме , <:і :>які є альтернативними маркерами для [і ]відповідно. Існує також деяке використання умовного оператора . Також є оператор злегка переміщення , призначення правильного зсуву >>=.

Це більш читаема версія:

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.


Я цього не проводив - хіба це не призведе до того ????, що отримає ???ОП? ( Гм .) Codepad.org/nDkxGUNi справді виробляє ???.
usr2564301

7
@Jongware 10 ділилися на першій ітерації. Таким чином, значення, що оцінюються циклом, становлять 5, 2, 1 і 0. Отже, він друкується лише 3 рази.
MysticXG

42

Перейдемо до виразу зліва направо:

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 ]

Далі :>- диграф . Це конструкція, яка розширюється на ]:

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
:>- це диграф , а не триграф. Це не обробляється препроцесором, він просто розпізнається як маркер, еквівалентний ].
Кіт Томпсон,

@KeithThompson Подяки
0x499602D2

1
Потрійний оператор ( ?:) має тип, який є загальним типом другого та третього членів. Перший термін завжди умовний і має тип bool. Оскільки і другий, і третій терміни мають тип, intрезультатом потрійної операції буде int, а не unsigned long long.
Корі

2
@KeithThompson з ним міг обробляти препроцесор. Препроцесор повинен знати про орграфа , тому що #і ##у орграфа форми; ніщо не зупиняє реалізацію від перекладу діаграм до недиграфів на етапах раннього перекладу
ММ

@MattMcNabb Вже давно мені довелося це знати, але IIRC, як наслідок інших вимог, диграфи повинні залишатися у своїй формі диграфа до моменту, коли pp-токени будуть перетворені в лексеми (прямо на початку фази перекладу 7).
zwol
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.