Por que esse código fornece a saída C++Sucks
? Qual é o conceito por trás disso?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Teste aqui .
c
deobfuscation
codeslayer1
fonte
fonte
skcuS++C
.Respostas:
O número
7709179928849219.0
tem a seguinte representação binária como um de 64 bitsdouble
:+
mostra a posição do sinal;^
do expoente e-
da mantissa (ou seja, o valor sem o expoente).Como a representação usa expoente binário e mantissa, dobrar o número incrementa o expoente em um. Seu programa faz isso precisamente 771 vezes; portanto, o expoente iniciado em 1075 (representação decimal de
10000110011
) se torna 1075 + 771 = 1846 no final; representação binária de 1846 é11100110110
. O padrão resultante é assim:Esse padrão corresponde à sequência que você vê impressa, apenas ao contrário. Ao mesmo tempo, o segundo elemento da matriz torna-se zero, fornecendo terminador nulo, tornando a cadeia adequada para a passagem
printf()
.fonte
7709179928849219
valor e recuperei a representação binária.Versão mais legível:
Recursivamente chama
main()
771 vezes.No início,
m[0] = 7709179928849219.0
que está paraC++Suc;C
. Em cada chamada,m[0]
é duplicado, para "reparar" as duas últimas letras. Na última chamada,m[0]
contém representação de caracteres ASCII deC++Sucks
em[1]
contém apenas zeros, portanto, possui um terminador nulo para aC++Sucks
sequência. Tudo sob a suposição de quem[0]
é armazenado em 8 bytes, então cada caractere leva 1 byte.Sem recursão e
main()
chamada ilegal , ficará assim:fonte
Isenção de responsabilidade: Esta resposta foi postada na forma original da pergunta, que mencionava apenas C ++ e incluía um cabeçalho C ++. A conversão da pergunta para C puro foi feita pela comunidade, sem a contribuição do autor original.
Formalmente, é impossível argumentar sobre esse programa porque ele é mal formado (ou seja, não é C ++ legal). Ele viola o C ++ 11 [basic.start.main] p3:
Além disso, ele se baseia no fato de que, em um computador de consumo típico, a
double
possui 8 bytes de comprimento e usa uma certa representação interna conhecida. Os valores iniciais da matriz são calculados para que, quando o "algoritmo" for executado, o valor final do primeirodouble
seja tal que a representação interna (8 bytes) seja o código ASCII dos 8 caracteresC++Sucks
. O segundo elemento da matriz é então0.0
, cujo primeiro byte está0
na representação interna, tornando-a uma seqüência de caracteres válida no estilo C. Este é então enviado para a saída usandoprintf()
.A execução disso no HW, onde algumas das opções acima não são válidas, resultaria em texto inválido (ou talvez até mesmo um acesso fora dos limites).
fonte
basic.start.main
3.6.1 / 3 com a mesma redação.main()
ou substituí-la por uma chamada de API para formatar o disco rígido ou o que for.Talvez a maneira mais fácil de entender o código seja trabalhar de maneira inversa. Começaremos com uma string para imprimir - para o equilíbrio, usaremos "C ++ Rocks". Ponto crucial: assim como o original, tem exatamente oito caracteres. Como vamos fazer (aproximadamente) o original e imprimi-lo na ordem inversa, começaremos colocando-o na ordem inversa. Para nossa primeira etapa, veremos esse padrão de bits como um
double
e imprimiremos o resultado:Isso produz
3823728713643449.5
. Então, queremos manipular isso de alguma maneira que não seja óbvio, mas que seja fácil de reverter. Escolho semi-arbitrariamente a multiplicação por 256, o que nos dá978874550692723072
. Agora, basta escrever um código oculto para dividir por 256 e imprimir os bytes individuais em ordem inversa:Agora, temos muitos lançamentos, passando argumentos para (recursivos)
main
que são completamente ignorados (mas a avaliação para obter o incremento e o decréscimo é absolutamente crucial) e, é claro, esse número de aparência completamente arbitrário para encobrir o fato de que estamos fazendo é realmente bem direto.É claro que, como todo o ponto é ofuscação, se quisermos, também podemos dar mais passos. Apenas por exemplo, podemos tirar proveito da avaliação de curto-circuito, para transformar nossa
if
declaração em uma única expressão, para que o corpo de main seja assim:Para qualquer pessoa que não esteja acostumada com código ofuscado (e / ou código de golfe), isso começa a parecer bastante estranho - computar e descartar a lógica
and
de algum número de ponto flutuante sem sentido e o valor de retorno demain
, que nem sequer está retornando um valor. Pior, sem perceber (e pensar) como funciona a avaliação em curto-circuito, pode até não ser imediatamente óbvio como evita a recursão infinita.Nosso próximo passo provavelmente seria separar a impressão de cada caractere da localização desse caractere. Podemos fazer isso com bastante facilidade, gerando o caractere certo como valor de retorno
main
e imprimindo o quemain
retorna:Pelo menos para mim, isso parece ofuscado o suficiente, então vou deixar por isso mesmo.
fonte
É apenas a construção de uma matriz dupla (16 bytes) que - se interpretada como uma matriz de caracteres - cria os códigos ASCII para a string "C ++ Sucks"
No entanto, o código não está funcionando em cada sistema, ele conta com alguns dos seguintes fatos indefinidos:
fonte
O código a seguir é impresso
C++Suc;C
, portanto, toda a multiplicação é apenas para as duas últimas letrasfonte
Os outros explicaram a questão minuciosamente, gostaria de acrescentar uma observação de que esse é um comportamento indefinido de acordo com o padrão.
C ++ 11 3.6.1 / 3 Função principal
fonte
O código pode ser reescrito assim:
O que está fazendo é produzir um conjunto de bytes na
double
matrizm
que correspondam aos caracteres 'C ++ Sucks' seguidos por um terminador nulo. Eles ofuscaram o código escolhendo um valor duplo que, quando duplicado, 771 vezes produz, na representação padrão, esse conjunto de bytes com o terminador nulo fornecido pelo segundo membro da matriz.Observe que esse código não funcionaria sob uma representação endian diferente. Além disso, a chamada
main()
não é estritamente permitida.fonte
f
retorno éint
?int
retorno na pergunta. Deixe-me consertar isso.Primeiro, devemos lembrar que números duplos de precisão são armazenados na memória em formato binário da seguinte maneira:
i) 1 bit para o sinal
(ii) 11 bits para o expoente
iii) 52 bits para a magnitude
A ordem dos bits diminui de (i) para (iii).
Primeiro, o número fracionário decimal é convertido em número binário fracionário equivalente e, em seguida, é expresso como forma de ordem de magnitude em binário.
Assim, o número 7709179928849219.0 se torna
Agora, considerando os bits de magnitude 1., é negligenciado, pois todo o método da ordem de magnitude deve começar com 1.
Então a parte da magnitude se torna:
Agora, a potência de 2 é 52 , precisamos adicionar um número de polarização como 2 ^ (bits para o expoente -1) -1 ou seja, 2 ^ (11 -1) -1 = 1023 , para que nosso expoente se torne 52 + 1023 = 1075
Agora, nosso código multiplica o número com 2 , 771 vezes, o que faz com que o expoente aumente 771
Portanto, nosso expoente é (1075 + 771) = 1846, cujo equivalente binário é (11100110110)
Agora nosso número é positivo, então nosso bit de sinal é 0 .
Portanto, nosso número modificado se torna:
bit de sinal + expoente + magnitude (concatenação simples dos bits)
como m é convertido em ponteiro de char, dividiremos o padrão de bits em pedaços de 8 do LSD
(cujo equivalente hexadecimal é :)
Qual do mapa de caracteres, como mostrado, é:
Agora que isso foi feito, m [1] é 0, o que significa um caractere NULL
Agora, supondo que você execute este programa em uma máquina little-endian (o bit de ordem inferior é armazenado no endereço inferior), então o ponteiro m aponta para o bit de endereço mais baixo e, em seguida, prossegue pegando bits em pedaços de 8 (como o tipo convertido para char * ) e o printf () para quando contado 00000000 no último chunck ...
Este código, no entanto, não é portátil.
fonte