Concepto detrás de estas cuatro líneas de código C complicado


¿Por qué este código da la salida C++Sucks? ¿Cuál es el concepto detrás de ella?

#include <stdio.h>

double m[] = {7709179928849219.0, 771};

int main() {
    m[1]--?m[0]*=2,main():printf((char*)m);    
}

Pruébalo aquí.

Author: Adam Stelmaszczyk, 2013-08-01

9 answers

El número 7709179928849219.0 tiene la siguiente representación binaria como un 64-bit double:

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

+ muestra la posición del signo; ^ del exponente, y - de la mantisa (es decir, el valor sin el exponente).

Dado que la representación utiliza exponente binario y mantisa, duplicar el número incrementa el exponente en uno. Su programa lo hace exactamente 771 veces, por lo que el exponente que comenzó en 1075 (representación decimal de 10000110011) se convierte en 1075 + 771 = 1846 en la representación binaria de 1846 es 11100110110. El patrón resultante se ve así:

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

Este patrón corresponde a la cadena que ves impresa, solo al revés. Al mismo tiempo, el segundo elemento de la matriz se convierte en cero, proporcionando un terminador nulo, haciendo que la cadena sea adecuada para pasar a printf().

 488
Author: dasblinkenlight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-23 17:42:56

Versión Más legible:

double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;    

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

Llama recursivamente main() 771 veces.

En el principio, m[0] = 7709179928849219.0 que stands para C++Suc;C. En cada llamada, m[0] se duplica, para "reparar" las dos últimas letras. En la última llamada, m[0] contiene la representación de caracteres ASCII de C++Sucks y m[1]contiene solo ceros, por lo que tiene un terminador nulo para C++Sucks cadena. Todo bajo el supuesto de que m[0] se almacena en 8 bytes, por lo que cada carácter toma 1 byte.

Sin recursión e ilegal main() llamándolo se verá así:

double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
    m[0] *= 2;
}
printf((char*) m);
 214
Author: Adam Stelmaszczyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-18 17:38:02

Descargo de responsabilidad: Esta respuesta se publicó en la forma original de la pregunta, que solo mencionaba C++ e incluía un encabezado C++. La conversión de la pregunta a C pura fue hecha por la comunidad, sin la aportación del preguntador original.


Formalmente hablando, es imposible razonar sobre este programa porque está mal formado (es decir, no es C++legal). Viola C++11 [basic.empezar.principal] p3:

La función main no se utilizará dentro de un programa.

Esto aparte, se basa en el hecho de que en un ordenador de consumo típico, un double es de 8 bytes de largo, y utiliza una cierta representación interna bien conocida. Los valores iniciales de la matriz se calculan de modo que cuando se realiza el "algoritmo", el valor final del primer double será tal que la representación interna (8 bytes) serán los códigos ASCII de los 8 caracteres C++Sucks. El segundo elemento en la matriz es entonces 0.0, cuyo primer byte es 0 en el representación, haciendo de esto una cadena de estilo C válida. Esto se envía a la salida usando printf().

Ejecutar esto en HW donde algunas de las anteriores no se mantienen resultaría en texto basura (o tal vez incluso un acceso fuera de los límites) en su lugar.

 103
Author: Angew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-16 09:17:57

Quizás la forma más fácil de entender el código es trabajar las cosas a la inversa. Comenzaremos con una cadena para imprimir for para el equilibrio, usaremos "C++Rocks". Punto crucial: al igual que el original, tiene exactamente ocho caracteres de largo. Ya que vamos a hacer (aproximadamente) como el original, e imprimirlo en orden inverso, comenzaremos poniéndolo en orden inverso. Para nuestro primer paso, solo veremos ese patrón de bits como un double, e imprimiremos el resultado:

#include <stdio.h>

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

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

Esto produce 3823728713643449.5. Por lo tanto, queremos manipular de alguna manera que no es obvio, pero es fácil de revertir. Voy a elegir semi-arbitrariamente la multiplicación por 256, lo que nos da 978874550692723072. Ahora, solo necesitamos escribir un código ofuscado para dividir por 256, luego imprimir los bytes individuales de eso en orden inverso:

#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);
}

Ahora tenemos un montón de fundición, pasando argumentos a (recursivo) main que son completamente ignorados (pero la evaluación para obtener el incremento y decremento son absolutamente cruciales), y por supuesto, ese número de aspecto completamente arbitrario para encubrir el hecho de que lo que estamos haciendo es realmente bastante sencillo.

Por supuesto, ya que el punto es la ofuscación, si nos apetece podemos tomar más pasos también. Solo por ejemplo, podemos tomar ventaja de la evaluación de cortocircuito, para convertir nuestra declaración if en una sola expresión, por lo que el cuerpo de main se ve así:

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

Para cualquiera que no esté acostumbrado a código ofuscado (y / o golf de código) esto comienza a parecer bastante extraño de hecho computing computando y descartando el and lógico de algún número de coma flotante sin sentido y el valor devuelto desde main, que ni siquiera devuelve un valor. Peor aún, sin darse cuenta (y pensar en) cómo funciona la evaluación de cortocircuitos, puede que ni siquiera sea inmediatamente obvio cómo evita la recursión infinita.

Nuestro siguiente paso probablemente sería separar la impresión de cada carácter de la búsqueda de ese carácter. Podemos hacerlo muy fácilmente generando el carácter correcto como el valor devuelto desde main, e imprimiendo lo que main devuelve:

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

Al menos para mí, eso parece bastante ofuscado, así que lo dejaré así.

 55
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-03 17:54:51

Solo está construyendo una matriz doble (16 bytes) que - si se interpreta como una matriz char - construye los códigos ASCII para la cadena "C++Sucks"

Sin embargo, el código no funciona en cada sistema, se basa en algunos de los siguientes hechos indefinidos:

 22
Author: D.R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-17 10:40:39

El siguiente código imprime C++Suc;C, por lo que toda la multiplicación es solo para las dos últimas letras

double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
 10
Author: Serve Laurijssen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-01 12:05:54

Los otros han explicado la pregunta bastante a fondo, me gustaría añadir una nota que este es comportamiento indefinido de acuerdo con el estándar.

C++11 3.6.1/3 función Principal

La función main no se utilizará dentro de un programa. El vínculo (3.5) de main está definido por la implementación. Un programa que define main como deleted o que declara main como inline, static o constexpr está mal formado. El nombre main no está reservado de otro modo. [ Ejemplo: las funciones miembro, clases y enumeraciones se pueden llamar main, al igual que las entidades en otros espacios de nombres. - ejemplo final]

 9
Author: Yu Hao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-01 14:06:25

El código podría reescribirse así:

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

Lo que está haciendo es producir un conjunto de bytes en la matriz double m que corresponden a los caracteres 'C++Sucks' seguidos de un terminador nulo. Han ofuscado el código eligiendo un valor doble que cuando se duplica 771 veces produce, en la representación estándar, ese conjunto de bytes con el terminador nulo proporcionado por el segundo miembro de la matriz.

Tenga en cuenta que este código no funcionaría bajo una endian representación. Además, llamar main() no está estrictamente permitido.

 8
Author: Jack Aidley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-02 16:12:46

Básicamente es solo una forma inteligente de ocultar la cadena "C++Sucks" (nota los 8 bytes) dentro del primer valor doble, que se multiplica recursivamente por dos hasta que los valores dobles segundos alcanzan cero (771 veces).

Multiplicando los valores dobles 7709179928849219.0 * 2 * 711 resulta en "C++Sucks" si interpretas el valor de byte de la cadena double as, que printf () hace con el cast. Y printf() no falla, porque el segundo valor doble es " 0 "e interpretado como" \0 " por printf().

 -2
Author: Sir Pancake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-03 17:53:06