resueltos - Concepto detrás de estas cuatro líneas de código C complicado
lenguaje c ejemplos (9)
Básicamente, es solo una forma inteligente de ocultar la cadena "C ++ chupa" (note los 8 bytes) dentro del primer valor doble, que se multiplica de forma recursiva con dos hasta que los segundos valores dobles llegan a cero (771 veces).
Al multiplicar los valores dobles 7709179928849219.0 * 2 * 711, se obtiene el resultado de "C ++ apesta" si interpreta el valor en bytes del doble como una cadena, que printf () hace con la conversión. Y printf () no falla, porque el segundo valor doble es "0" e interpretado como "/ 0" por printf ().
¿Por qué este código da la salida C++Sucks
? ¿Cuál es el concepto detrás de esto?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Pruébalo here .
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 double
matriz m
que corresponden a los caracteres ''C ++ Sucks'' seguidos por un terminador nulo. Han ofuscado el código al elegir un valor doble que cuando se duplicó 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 representación endiana diferente. Además, llamar a main()
no está estrictamente permitido.
El número 7709179928849219.0
tiene la siguiente representación binaria como double
64 bits:
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).
Como la representación usa exponente binario y mantisa, duplicar el número incrementa el exponente en uno. Su programa lo hace precisamente 771 veces, por lo que el exponente que comenzó a 1075 (representación decimal de 10000110011
) se convierte en 1075 + 771 = 1846 al final; 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 ve impresa, solo hacia atrás. Al mismo tiempo, el segundo elemento de la matriz se convierte en cero, lo que proporciona un terminador nulo, lo que hace que la cadena sea adecuada para pasar a printf()
.
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);
Los otros han explicado la pregunta bastante a fondo, me gustaría agregar una nota de que este es un comportamiento indefinido de acuerdo con el estándar.
C ++ 11 3.6.1 / 3 Función principal
La función main no debe ser utilizada dentro de un programa. El enlace (3.5) de main está definido por la implementación. Un programa que define main como eliminado o que declara que main está en línea, estático o constexpr está mal formado. El nombre principal no está reservado de otro modo. [Ejemplo: las funciones miembro, las clases y las enumeraciones se pueden llamar main, al igual que las entidades en otros espacios de nombres. —En ejemplo]
Quizás la forma más fácil de entender el código es trabajar a la inversa. Comenzaremos con una cadena para imprimir: para mantener el equilibrio, usaremos "C ++ Rocks". Punto crucial: al igual que el original, tiene exactamente ocho caracteres. Ya que vamos a hacer (aproximadamente) el original e imprimirlo en orden inverso, comenzaremos por ponerlo 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 eso de alguna manera que no sea obvio, pero que sea fácil de revertir. 978874550692723072
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 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 muchos argumentos de lanzamiento, pasando a main
(recursivo) que son completamente ignorados (pero la evaluación para obtener el incremento y decremento es absolutamente crucial), y por supuesto ese número de aspecto completamente arbitrario para encubrir el hecho de que estamos Hacer es realmente bastante sencillo.
Por supuesto, dado que todo el asunto es la ofuscación, si sentimos que podemos también podemos tomar más medidas. Solo por ejemplo, podemos aprovechar la evaluación de cortocircuito, para convertir nuestra sentencia if
en una sola expresión, de modo que el cuerpo de main se vea así:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
Para cualquiera que no esté acostumbrado a un código ofuscado (y / o un código de golf), esto comienza a parecer bastante extraño: calcular y descartar el número lógico and
de algún punto flotante sin sentido y el valor de retorno de main
, que ni siquiera es devolviendo un valor. Peor aún, sin darse cuenta (y pensar en) cómo funciona la evaluación de cortocircuito, 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 personaje de encontrar ese personaje. Podemos hacerlo con bastante facilidad generando el carácter correcto como el valor de retorno de main
, e imprimiendo qué retornos main
:
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
Al menos para mí, eso parece bastante confuso, así que lo dejo así.
Simplemente está creando una matriz doble (16 bytes) que, si se interpreta como una matriz de caracteres, 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 no definidos:
- doble tiene exactamente 8 bytes
- endianness
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);
}
}
Recursivamente se llama main()
771 veces.
Al principio, m[0] = 7709179928849219.0
, que stands 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]
solo contiene ceros, por lo que tiene un terminador nulo para la cadena de C++Sucks
. Todo bajo el supuesto de que m[0]
se almacena en 8 bytes, por lo que cada char toma 1 byte.
Sin recursión y llamadas main()
ilegales se verá así:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
Descargo de responsabilidad: esta respuesta se publicó en la forma original de la pregunta, que solo mencionaba C ++ e incluía un encabezado de C ++. La conversión de la pregunta a C pura fue realizada por la comunidad, sin aportación del autor de la pregunta original.
Hablando formalmente, es imposible razonar acerca de este programa porque está mal formado (es decir, no es legal C ++). Viola C ++ 11 [basic.start.main] p3:
La función main no debe ser utilizada dentro de un programa.
Aparte de esto, se basa en el hecho de que en una computadora de consumo típica, un double
tiene 8 bytes de longitud y utiliza una cierta representación interna conocida. Los valores iniciales de la matriz se calculan de manera 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 de la matriz es entonces 0.0
, cuyo primer byte es 0
en la representación interna, lo que hace que esta sea una cadena de estilo C válida. Esto se envía a la salida utilizando printf()
.
Ejecutar esto en HW, donde algunos de los puntos anteriores no son válidos, daría lugar a texto basura (o incluso a un acceso fuera de límites).