Концепция этих четырех строк хитрого C-кода

Eclipse в Windows: для проекта: Свойства -> C / C ++ Build -> Настройка, чем вкладка «Настройка инструмента». выберите «CGG C ++ Compiler», чем в правой части, вы увидите команду: g ++ измените ее на команду: g ++ CFLAGS, если вы хотите, чтобы поддержка C ++ 11 изменялась как Command: g ++ --std = c ++ 11

PS: Эта модификация будет действительна только для текущего проекта и только для текущей конфигурации. Если вы хотите, чтобы он для всех конфигураций изменял каждую конфигурацию (Run, Debug) аналогично.

378
задан Adam Stelmaszczyk 17 July 2014 в 15:51
поделиться

7 ответов

Число 7709179928849219.0 имеет следующее двоичное представление в виде 64-битного double:

01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------

+ показывает положение знака; ^ экспоненты и - мантиссы (то есть значение без экспоненты).

Так как представление использует двоичную экспоненту и мантиссу, удвоение числа увеличивает экспоненту на единицу. Ваша программа делает это ровно 771 раз, поэтому показатель степени, который начинается в 1075 (десятичное представление 10000110011), в конце становится 1075 + 771 = 1846; бинарное представление 1846 года 11100110110. Результирующий шаблон выглядит следующим образом:

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'

Этот шаблон соответствует строке, которую вы видите напечатанной, только назад. В то же время второй элемент массива становится нулевым, обеспечивая нулевой терминатор, что делает строку пригодной для передачи в printf().

492
ответ дан gsamaras 17 July 2014 в 15:51
поделиться

Это просто создание двойного массива (16 байтов), который - если интерпретировать его как массив символов - создает коды ASCII для строки «C ++ Sucks»

Однако код не работая с каждой системой, она опирается на некоторые из следующих неопределенных фактов:

23
ответ дан Nilay Vishwakarma 17 July 2014 в 15:51
поделиться

Возможно, самый простой способ понять код - это работать в обратном порядке. Мы начнем со строки для печати - для баланса мы будем использовать «C ++ Rocks». Важный момент: точно так же, как и оригинал, он ровно восемь символов. Поскольку мы собираемся сделать (примерно) как оригинал и распечатать его в обратном порядке, мы начнем с того, что поместим его в обратном порядке. Для нашего первого шага мы просто рассмотрим эту битовую комбинацию как double и распечатаем результат:

#include <stdio.h>

char string[] = "skcoR++C";

int main(){
    printf("%f\n", *(double*)string);
}

Это приводит к 3823728713643449.5. Итак, мы хотим манипулировать этим каким-то образом, что не очевидно, но легко изменить. Я буду полу-произвольно выбирать умножение на 256, что дает нам 978874550692723072. Теперь нам просто нужно написать некоторый запутанный код для деления на 256, а затем распечатать отдельные байты этого в обратном порядке:

#include <stdio.h>

double x [] = { 978874550692723072, 8 };
char *y = (char *)x;

int main(int argc, char **argv){
    if (x[1]) {
        x[0] /= 2;  
        main(--x[1], (char **)++y);
    }
    putchar(*--y);
}

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

Конечно, так как все дело в запутывании, если мы чувствуем, что мы можем сделать больше шагов. Например, мы можем воспользоваться оценкой короткого замыкания, чтобы превратить наш оператор if в одно выражение, поэтому тело main выглядит следующим образом:

x[1] && (x[0] /= 2,  main(--x[1], (char **)++y));
putchar(*--y);

Для всех, кто не Привычный к запутанному коду (и / или коду для гольфа) это начинает выглядеть довольно странно - вычисление и отбрасывание логического and некоторого бессмысленного числа с плавающей запятой и возвращаемого значения из main, которое даже не возвращает значение. Хуже того, не осознавая (и не задумываясь) о том, как работает оценка короткого замыкания, может быть даже не сразу очевидно, как избежать бесконечной рекурсии.

Наш следующий шаг, вероятно, будет отделять печать каждого символа от поиска этого символа. Мы можем сделать это довольно легко, сгенерировав правильный символ в качестве возвращаемого значения из main, и распечатав то, что возвращает main:

x[1] && (x[0] /= 2,  putchar(main(--x[1], (char **)++y)));
return *--y;

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

56
ответ дан Peter Mortensen 17 July 2014 в 15:51
поделиться

Отказ от ответственности: Этот ответ был опубликован в оригинальной форме вопроса, в которой упоминается только C ++ и заголовок C ++. Преобразование вопроса в чистый C было сделано сообществом, без участия первоначального автора.


Формально говоря, невозможно рассуждать об этой программе, потому что она плохо сформирована (то есть это не является легальным C ++). Это нарушает C ++ 11 [basic.start.main] p3:

Функция main не должна использоваться в программе.

Помимо этого, он опирается на тот факт, что на типичном потребительском компьютере double имеет длину 8 байт и использует определенное хорошо известное внутреннее представление. Начальные значения массива вычисляются таким образом, чтобы при выполнении «алгоритма» конечное значение первого double было таким, чтобы внутренним представлением (8 байтов) были коды ASCII из 8 символов C++Sucks , Тогда вторым элементом в массиве является 0.0, чей первый байт - 0 во внутреннем представлении, что делает эту строку допустимой в стиле C. Затем он отправляется на вывод с помощью printf().

Запуск этого в HW, где некоторые из вышеперечисленных не выполняются, приведет к тексту мусора (или, возможно, даже к выходу за пределы).

104
ответ дан Angew 17 July 2014 в 15:51
поделиться

Следующий код печатает C++Suc;C, поэтому все умножение выполняется только для последних двух букв

double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
11
ответ дан Cody Gray 17 July 2014 в 15:51
поделиться

Другие довольно подробно объяснили вопрос, я хотел бы добавить примечание, что это неопределенное поведение в соответствии со стандартом.

C ++ 11 3.6.1 / 3 Основная функция

Функция main не должна использоваться в программе. Связь (3.5) с main определяется реализацией. Программа, которая определяет main как удаленный или объявляет main как встроенный, статический или constexpr, неверна. Имя main не является зарезервированным. [Пример: функции-члены, классы и перечисления могут называться main, как и сущности в других пространствах имен. - конец примера]

10
ответ дан Yu Hao 17 July 2014 в 15:51
поделиться

Код можно переписать следующим образом:

void f()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        f();
    } else {
          printf((char*)m);
    }
}

Он создает набор байтов в массиве double m, который соответствует символам C + + Отстой, за которым следует нулевой терминатор. Они запутали код, выбрав удвоенное значение, которое при удвоении 771 раз в стандартном представлении выдает тот набор байтов с нулевым терминатором, который предоставляется вторым членом массива.

Обратите внимание, что этот код не будет работать с другим порядком байтов. Кроме того, вызов main() строго запрещен.

9
ответ дан Jack Aidley 17 July 2014 в 15:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: