Доброй вам пятницы, хабралюди. У меня с друзьями есть такая традиция — мы собираемся и вместе программируем. Раньше у нас такая работа была, а сейчас уже просто традиция — кодобредогенерация. Мы выбрали несложную задачку и нарисовали решений кто во что горазд. Код будет ближе к концу статьи, но не спешите листать вниз, надо же сначала объяснить, что это за код и как мы пришли к нему.

Сама задача — получить число 0x17 самым внезапным образом.


Внимание! Данный пост оскорбляет чувство прекрасного и практики программирования на C. Читайте, воспринимайте и комментируйте на свой страх и риск.


Вы любите магические числа в коде? Вот бывает смотришь какой-то код и видишь a = b ^ 7. Почему именно 7? Что это за число, откуда оно взялось, что означает? Столько много вопросов и так мало ответов, если кто-то не утрудился комментарием. Есть несколько способов разрешить неопределенность такого рода:

  1. Оставить комментарий. Но комменты для слабаков — не наш путь.
  2. Сделать переменную или макрос с говорящим именем. a = b ^ HEARTBEAT_MASK_BYTE. Уже неплохо, но можно же и лучше, не так ли?
  3. Показать вычислениями путь, который привел к данному числу. Вот! Это отличный способ. Но не всегда применимый, как, например, в рассматриваемом коде. В нем 7 — это законченный и самостоятельный элемент, который не является результатом каких-то операций.

Зачем рассматривать еще какие-то способы? Берем последний и в путь! Но, как я сказал, есть проблема — не всегда есть адекватный способ вычислениями показать путь к числу. Но задача стояла избавиться от магического числа, а не сделать это логично. Более того, доведем задачу до абсурда — будем получать числа максимально непонятным образом.

Программно-аппаратный комплекс в виде вызова функции rand() постановил, что избавляться мы будем от магического числа 0x17.

Коллектив погроммистов в составе меня, Viscount и [УДАЛЕНО] приступил к творчеству.
0х17 способов получить 0x17:

	int Ox01 = ~-~-~-~-~-~-~-~-~-' ';
	int Ox02 = ((!true)["true"]-(false)["FALSE"])>>true;
	int Ox03 = 'X'/2/2^!*"";
	int Ox04 = ('0'>>!*"")-!*"";
	int Ox05 = (~'!'-~'~')>>!*"">>(2==1==0);
	int Ox06 = ('|'||'|'|'|')["||||||||"]%*"error";
	int Ox07 = '.'>>!false;
	int Ox08 = '\\'>>('!'>>(1<<2));
	int Ox09 = '/'-'/'/'/'>>'/'/'/';
	int Ox0a = (*"")["yes"]^(*"")["no"];
	int Ox0b = *"yes"^*"no";
	int Ox0c = '0'/2-!*"";
	int Ox0d = ((!'!'+'+')>>true)+(true<<true);
	int Ox0e = (-~true^!false)*(true<<(-~true|!false))-!false;
	int Ox0f = '!'-'^'%*"*";
	int Ox10 = -~*" L"^-~*"5Z";
	int Ox11 = *"Totally" -* "not" +* "0x17" + true;
	int Ox12 = -~!*""*-~!*""*-~!*""*-~!*""*-~!*""+~!*""*-~!*""*-~!*""-true;
	int Ox13 = -compl(-compl true xor true)<<-compl true|'8'>>('1'^'2');
	int Ox14 = '^'>>('<'^'>');
	int Ox15 = *"'"-(' '>>!0);
	int Ox16 = '_'>>-~1;
	int Ox17 = 010-001+010+010;

Для сомневающихся в том, что указанная задача выполнена верно — ссылка на ideone.

Упражнения, направленные на разбор тех или иных действий, я оставляю в качестве собственной деградации читателю. Но при появлении вопросов готов ответить на них в комментариях. Там же призываю всех желающих демонстрировать свои способы выстрелить себе в ногу сделать рокетжамп.

Кстати, мы ищем Питон-разработчика, который умеет программировать понятнее чем я. Присоединяйтесь!

Комментарии (35)


  1. Sinatr
    20.04.2018 12:08

    доведем задачу до абсурда
    По-моему у вас получилось. Почему 0x17? Из-за 0x0b?


    1. xi-tauw Автор
      20.04.2018 12:14
      +1

      Ничем специальным число не обусловлено. Просто генератор выдал именно 0x17 и дальше пошло-поехало.


  1. sim-dev
    20.04.2018 12:13
    +1

    Блин, уже на втором примере сломал мозг…


  1. VioletGiraffe
    20.04.2018 12:34

    Хочу поставить 0х17 лайков. Но с пояснениями статья была бы намного интереснее и полезнее.

    Второй пример сильно попахивает неопределённым поведением и индексацией случайных адресов памяти. Или я неправ?


    1. xi-tauw Автор
      20.04.2018 12:49

      Нет, UB нет ни в одном из примеров.
      Я обдумывал идею добавить разборы, но они сделали бы статью существенно скучнее. В итоге решил оставить без пояснений, благо комментарии позволяют обсудить конкретные места.


    1. mayorovp
      20.04.2018 12:49
      +1

      Нет. (!true)["true"] — это то же самое что "true"[0], т.е. символ 't'. Со второй частью аналогично.


    1. SidMeier
      20.04.2018 12:52
      +1

      Вроде как ptr[n] == n[ptr], то есть это выражение эквивалентно ('t' -'F') >> 1 => 0x2e >> 1 => 0x17


    1. spinach
      20.04.2018 16:02
      +1

      Здесь всё в порядке: x[y] раскрывается в *(x + y).


      1. VioletGiraffe
        20.04.2018 16:21

        Да, дошло через 5 минут модификаций кода в ideone, чтоб понять, что же он делает. А комментарий ни добавить, ни отредактировать уже не мог. В общем, надо сначала подумать, а потом уже в комментариях спрашивать :)

        Только насчёт x + y не согласен, потому что здесь это явно раскрылось в y+x. Арифметика указателей разве коммутативна?


        1. xi-tauw Автор
          20.04.2018 16:41

          Коммутативна для байт, на этом и построен трюк 3[«test»] == «test»[3]


          1. TheCalligrapher
            21.04.2018 21:32

            Что такое "коммутативна для байт"??? О_о


        1. TheCalligrapher
          21.04.2018 21:31

          Коммутативность не имеет никакого отношения к "арифметике указателей". Коммутативен встроенный бинарный оператор + и коммутативен абсолютно всегда, безусловно и во всех контекстах.


          1. VioletGiraffe
            21.04.2018 21:47

            Я о том, что совершенно не очевидно, что int + char* — такая же валидная операция, как char* + int. Более того: возможно, даже, имело бы смысл сделать первый вариант синтаксической ошибкой.


            1. TheCalligrapher
              21.04.2018 22:46

              Я не вижу никакой опасности в такой коммутативности. А запрещать чисто ради запрещения в С не принято.


  1. KvanTTT
    20.04.2018 13:06

    Простите, но там в начале разве должна быть O (буква) или все же 0 (цифра)?


    int Ox01 = ~-~-~-~-~-~-~-~-~-' ';


    1. GeorgWarden
      20.04.2018 13:11

      Названия переменных же нельзя начинать с цифры. Вот и выкрутились


      1. KvanTTT
        20.04.2018 15:11

        Точно, не заметил что это название переменной.


  1. Mikihiso
    20.04.2018 14:08
    +1

    Есть еще сильно подгонный вариант (по сути будет работать только в вашем примере на Ideone, и может еще на других 64 битных машинах с linux, gcc 6.3 и использующих ту же версию libc) — в глобальных переменных обьявить extern «C» void _start(); (в случае чистого C — просто extern) а непосредственно в main int Ox18 = ~((char*)_start)[142];


    1. xi-tauw Автор
      20.04.2018 14:09

      В некоторых промежуточных версиях у нас были подобные варианты, например, через отрицательные индексы массивов, но в итоге, от них решили отказаться, поскольку они очень ненадежные.


  1. Mikihiso
    20.04.2018 14:13

    Вообще говоря общая идея — это использовать оп код вызова call x86 ассемблера, я пока думаю как это можно сделать 100% предсказуемым образом.
    P.S сори, промахнулся веткой :(


  1. iCpu
    20.04.2018 19:09

    Нумерация OxZZ не с нуля — вся статья на выброс.


    1. xi-tauw Автор
      20.04.2018 20:10
      +1

      Ну хоть кто-то заметил, а я уже начал терять веру в людей.


      1. olleggerr
        20.04.2018 22:38

        Разве это не было сделано, что бы последняя переменная было с номером 0x17?


        1. xi-tauw Автор
          20.04.2018 22:39

          Примерно так. Но даже если бы отсчет шел с Ox00 по Ox16 общее количество в 0x17 все равно было бы очевидно.


  1. TheCalligrapher
    20.04.2018 22:40
    -1

    Во-первых, ваши решения написаны не на С, а на GCC, да и те платформеннозависимы. Такие задачи интереснее решать именно на стандартном С. Ваши решения в большинстве своем к С относятся мало или вообще никак.


    Во-вторых, ideone в профессиональных кругах не считается уважаемым или убедительным ресурсом, особенно в вопросах С, поэтому заявления вида "для сомневающихся — ссылка на ideone" никаких сомнений не развеивают, а могут вызвать лишь facepalm.


    1. xi-tauw Автор
      20.04.2018 22:45

      А можно аргументы?


      1. TheCalligrapher
        20.04.2018 23:55

        Запросто!


        На "во-первых" аргументы очевидны: большинство вариантов завязаны на свойство платфоременно-зависимого character set. Вот и все. К тому же фактически способов что-то сделать у вас от силы три. И один из них — применение * к строковому литералу — раздут в огромное число вариантов. Зачем было делать искусственное и скучное раздувание этих способов в такое количество уныло повторяющихся вариантов — не ясно. Это же сразу бросается в глаза.


        Найти остроумные решения, которые бы работали на настоящем стандартном С — вот это действительно интересная задача, потому что в ней заключается challenge. А ваши чисто косметические выверты на фактически готовеньком результате (т.е. на конкретных значениях character constants) — примитивная пустышка/профанация для студенток-первокурсниц.


        На "во-вторых" аргументы тоже несложны: хорошо известно, что ideone занимается наглой пост-фильтрацией диагностических сообщений компилятора. Не видя стандартных диагностических сообщений компилятора рядовой пользователь не сможет судить о корректности кода. Ваш код грубо ошибочен, но тем не менее проглатывается ideone — вескость этого аргумента переоценить невозможно.


        Например, для использования true и false обязательно требуется включение <stdbool.h>. Для использования compl и xor обязательно требуется включение <iso646.h>. У вас же ideone в режиме С (!) проглотил это все даже не поперхнувшись. Именно по таким причинам в темах по языку С не принято оскорблять присутствующих ссылками на потешные глюкала типа ideone. Возмите в привычку пользоваться общепризнанными стандартами типа coliru. Какой бы вы ресурс не использовали, контроль над командной строкой компилятора — обязателен.


        P.S. Ой, только что обратил внимание! Вы вообще в С++ это все компилировали! Так зачем же вы нам тогда баки забиваете какими то сказками про "практику программирования на C"?


        1. xi-tauw Автор
          21.04.2018 00:05

          А я все думал, когда же вы заметите про c++.
          Я себе ограничений в «чистый си, где даже даже в ASCII нельзя быть уверенным» не ставил.
          Произошло ровно то, что написано в статье — мой код оскорбил практики программирования на си.


          1. TheCalligrapher
            21.04.2018 00:13

            На С++ все даже хуже, ибо в С++ символьная константа имеет тип char, а не int. И это значит, что в С++ все ваши символьные константы будет подвергаться integral promotions, которые, в зависимости от платформы, могут превратить ее в int или в unsigned int. Вариант с unsigned int — целый ящик Пандоры самостоятельных проблем. (Хотя аналогичные проблемы с integral promotions присутствуют в этом коде и с точки зрения С).


        1. mayorovp
          21.04.2018 10:37
          +1

          Тем не менее, используемый character set не является чем-то уникальным для GCC, так что ваше утверждение «решения написаны на GCC» все еще странное.


          1. TheCalligrapher
            21.04.2018 19:07

            Это не принципиально. Завязка на character set в решении таких задач — это совершенно неинтересный читинг. А тут еще 90% приведенных "решений" — унылое повторение снова, снова и снова одного и того же приема с *"строка", к тому же завязанного на character set.


  1. 0x18h
    20.04.2018 22:59
    +1

    избавляться мы будем от магического числа 0x17.

    Вот это мне повезло!


    1. xi-tauw Автор
      20.04.2018 23:16

      Я был близок!


  1. xi-tauw Автор
    20.04.2018 23:15

    del


  1. iamoverit
    21.04.2018 00:06

    На картинке для кликбайта написано 0x17 а в решениях совсем другое
    int Ox07 = '.'>>!false;
    int Ox17 = 010-001+010+010;
    какая то даже не пятничная статья