Почему этот код дает вывод C++Sucks
? Какая концепция стоит за этим?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Проверьте это здесь .
c
deobfuscation
codeslayer1
источник
источник
skcuS++C
.Ответы:
Число
7709179928849219.0
имеет следующее двоичное представление как 64-битноеdouble
:+
показывает положение знака;^
показателя степени и-
мантиссы (то есть значения без показателя степени).Поскольку представление использует двоичную экспоненту и мантиссу, удвоение числа увеличивает экспоненту на единицу. Ваша программа делает это точно 771 раз, поэтому показатель степени, который начинается с 1075 (десятичное представление
10000110011
), в конце становится 1075 + 771 = 1846; двоичное представление 184611100110110
. Результирующий шаблон выглядит так:Этот шаблон соответствует строке, которую вы видите напечатанной, только назад. В то же время второй элемент массива становится нулевым, обеспечивая нулевой терминатор, что делает строку пригодной для передачи в
printf()
.источник
7709179928849219
значение и вернул двоичное представление.Более читаемая версия:
Он рекурсивно звонит
main()
771 раз.В самом начале
m[0] = 7709179928849219.0
, который стоит заC++Suc;C
. При каждом вызовеm[0]
удваивается, чтобы «починить» последние две буквы. В последнем вызовеm[0]
содержит символьное представление ASCIIC++Sucks
иm[1]
содержит только нули, поэтому у него есть нулевой терминатор дляC++Sucks
строки. Все по предположению, чтоm[0]
хранится на 8 байтах, поэтому каждый символ занимает 1 байт.Без рекурсии и незаконного
main()
вызова это будет выглядеть так:источник
Отказ от ответственности: Этот ответ был опубликован в оригинальной форме вопроса, в которой упоминается только C ++ и заголовок C ++. Преобразование вопроса в чистый C было сделано сообществом, без участия первоначального автора.
Формально говоря, невозможно рассуждать об этой программе, потому что она плохо сформирована (то есть, это не законный C ++). Это нарушает C ++ 11 [basic.start.main] p3:
Помимо этого, он опирается на тот факт, что на типичном потребительском компьютере
double
длина a составляет 8 байтов и использует определенное хорошо известное внутреннее представление. Начальные значения массива вычисляются таким образом, чтобы при выполнении «алгоритма» конечное значение первогоdouble
было таким, чтобы внутренним представлением (8 байтов) были коды ASCII из 8 символовC++Sucks
. Тогда второй элемент в массиве0.0
, первый байт которого находится0
во внутреннем представлении, делает эту строку допустимой в стиле C. Затем он отправляется на вывод с помощьюprintf()
.Выполнение этого в HW, где некоторые из вышеперечисленных не выполняются, приведет к появлению мусорного текста (или, возможно, даже доступа за пределы).
источник
basic.start.main
3.6.1 / 3 с той же формулировкой.main()
или заменить его вызовом API для форматирования жесткого диска или чего-либо еще.Возможно, самый простой способ понять код - это работать в обратном порядке. Мы начнем со строки для печати - для баланса мы будем использовать «C ++ Rocks». Важный момент: точно так же, как и оригинал, он ровно восемь символов. Поскольку мы собираемся сделать (примерно) как оригинал и распечатать его в обратном порядке, мы начнем с того, что поместим его в обратном порядке. Для нашего первого шага мы просто рассмотрим этот битовый шаблон как
double
и распечатаем результат:Это производит
3823728713643449.5
. Итак, мы хотим манипулировать этим каким-то образом, что не очевидно, но легко изменить. Я буду полу-произвольно выбирать умножение на 256, что дает нам978874550692723072
. Теперь нам просто нужно написать некоторый запутанный код, чтобы разделить его на 256, а затем распечатать отдельные байты в обратном порядке:Теперь у нас есть много приведений, передачи (рекурсивных) аргументов
main
, которые полностью игнорируются (но оценка для получения приращения и уменьшения крайне важна), и, конечно, это совершенно произвольно выглядящее число, чтобы скрыть тот факт, что мы делаем это действительно довольно просто.Конечно, поскольку все дело в запутывании, если мы хотим этого, мы можем сделать больше шагов. Например, мы можем воспользоваться оценкой короткого замыкания, чтобы превратить наш
if
оператор в одно выражение, поэтому тело main выглядит следующим образом:Для тех, кто не привык к запутанному коду (и / или коду для гольфа), это начинает выглядеть довольно странно - вычисление и отбрасывание логики
and
некоторого бессмысленного числа с плавающей запятой и возвращаемого значенияmain
, которое даже не возвращает ценность. Хуже того, не осознавая (и не задумываясь) о том, как работает оценка короткого замыкания, может быть даже не сразу очевидно, как избежать бесконечной рекурсии.Наш следующий шаг, вероятно, будет отделять печать каждого символа от поиска этого символа. Мы можем сделать это довольно легко, сгенерировав правильный символ в качестве возвращаемого значения
main
и распечатав, чтоmain
возвращает:По крайней мере, мне это кажется достаточно запутанным, поэтому я оставлю это на этом.
источник
Это просто создание двойного массива (16 байтов), который - если интерпретировать его как массив символов - создает коды ASCII для строки "C ++ Sucks"
Тем не менее, код работает не на каждой системе, он опирается на некоторые из следующих неопределенных фактов:
источник
Следующий код печатает
C++Suc;C
, поэтому все умножение только для последних двух буквисточник
Другие довольно подробно объяснили вопрос, я хотел бы добавить, что это неопределенное поведение в соответствии со стандартом.
C ++ 11 3.6.1 / 3 Основная функция
источник
Код можно переписать так:
Он производит набор байтов в
double
массиве,m
которые соответствуют символам «C ++ Sucks», за которыми следует нулевой терминатор. Они запутали код, выбрав удвоенное значение, которое при удвоении 771 раз в стандартном представлении выдает тот набор байтов с нулевым терминатором, который предоставляется вторым членом массива.Обратите внимание, что этот код не будет работать в другом представлении с прямым порядком байтов. Кроме того, звонить
main()
строго запрещено.источник
f
возвращаетеint
?int
ответ в вопросе. Позвольте мне это исправить.Сначала мы должны вспомнить, что числа двойной точности хранятся в памяти в двоичном формате следующим образом:
(i) 1 бит для знака
(ii) 11 битов для показателя степени
(iii) 52 бита для величины
Порядок битов уменьшается с (i) до (iii).
Сначала десятичное дробное число преобразуется в эквивалентное дробное двоичное число, а затем оно выражается в виде порядка величины в двоичном виде.
Таким образом, номер 7709179928849219.0 становится
Теперь при рассмотрении величины битов 1 пренебрегают, так как все методы порядка величин должны начинаться с 1.
Таким образом, часть величины становится:
Теперь степень 2 равна 52 , нам нужно добавить к ней число смещения как 2 ^ (биты для показателя -1) -1, т.е. 2 ^ (11 -1) -1 = 1023 , поэтому наш показатель степени становится 52 + 1023 = 1075
Теперь наш код mutiplies номер с 2 , 771 раз , что делает показатель увеличится на 771
Таким образом, наш показатель равен (1075 + 771) = 1846 , бинарный эквивалент которого (11100110110)
Теперь наше число положительное, поэтому наш бит знака равен 0 .
Таким образом, наш модифицированный номер становится:
знаковый бит + экспонента + величина (простая конкатенация битов)
поскольку m преобразуется в указатель на символ, мы разделим битовую комбинацию на 8 частей от LSD
(чей эквивалент Hex :)
Который из карты персонажей, как показано:
Теперь, когда это сделано, m [1] равно 0, что означает символ NULL
Теперь предположим, что вы запускаете эту программу на машине с прямым порядком байтов (бит младшего разряда хранится в младшем адресе), так что указатель m указывает на бит младшего адреса, а затем продолжает работу, занимая биты в порциях 8 (как тип, приведенный к char * ) и printf () останавливается, если в последнем чанке установлено значение 00000000 ...
Этот код, однако, не является переносимым.
источник