Pourquoi ce code donne-t-il la sortie `` ? Quel est le concept derrière elle ?
Testez-le ici.
Pourquoi ce code donne-t-il la sortie `` ? Quel est le concept derrière elle ?
Testez-le ici.
Le nombre 7709179928849219.0
a la suite de représentation binaire en 64 bits, double
:
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
indique la position du signe; ^
de l'exposant, et -
de la mantisse (c'est à dire la valeur sans l'exposant).
Depuis la représentation utilise le binaire de l'exposant et la mantisse, en doublant le nombre augmente à l'exposant par un. Votre programme est-il précisément 771 fois, de sorte que l'exposant qui a commencé à 1075 (représentation décimale d' 10000110011
) devient 1075 + 771 = 1846 à la fin; représentation binaire de 1846 est - 11100110110
. Le schéma ressemble à ceci:
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
Ce modèle correspond à la chaîne de caractères que vous voyez imprimé, uniquement vers l'arrière. Dans le même temps, le deuxième élément de la matrice est à zéro, en fournissant terminateur null, faisant de la chaîne adapté pour le passage à l' printf
.
Version plus lisible:
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);
}
}
Il appelle de manière récursive main()
771 fois.
Au début, m[0] = 7709179928849219.0
, qui se dresse pour C++Suc;C
. Lors de chaque appel, m[0]
obtient doublé, pour "réparer" les deux dernières lettres. Dans le dernier appel, m[0]
contient des caractères ASCII dans le fichier de la représentation de l' C++Sucks
et m[1]
ne contient que des zéros, donc il a un terminateur null pour C++Sucks
chaîne de caractères. Le tout sous l'hypothèse que l' m[0]
est stocké sur 8 octets, de sorte que chaque char prend 1 octet.
Sans récursivité et illégale main()
appel, il ressemblera à ceci:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
Formellement parlant, il est impossible de raisonner sur ce programme parce qu'il est mal formé (c'est à dire qu'il n'est pas légal C++). Il viole C++11[de base.commencer.principal]p3:
La fonction principale ne doit pas être utilisé à l'intérieur d'un programme.
Cela mis à part, il repose sur le fait que sur un ordinateur du consommateur, double
est de 8 octets de long, et utilise un certain bien-connue représentation interne. Les valeurs du tableau sont calculées de sorte que lorsque l ' "algorithme" est effectuée, la valeur finale de la première double
sera tel que la représentation interne (8 octets) seront les codes ASCII de 8 caractères C++Sucks
. Le deuxième élément du tableau est alors 0.0
, dont le premier octet est - 0
dans la représentation interne, ce qui en fait un valide C chaîne de style. C'est ensuite envoyé vers la sortie à l'aide de printf()
.
L'exécution de ce sur HW où une partie de la ci-dessus ne tiennent pas la entraînerait des ordures texte (ou peut-être même un accès en dehors des limites) à la place.
Peut-être la façon la plus simple de comprendre le code est de travailler à travers les choses dans le sens inverse. Nous allons commencer avec une chaîne à imprimer -- pour l'équilibre, nous allons utiliser "C++Rocks". Point Crucial: tout comme l'original, c'est exactement huit caractères. Puisque nous allons faire (à peu près) à l'instar de l'original, et l'imprimer dans l'ordre inverse, nous allons commencer par mettre les choses dans l'ordre inverse. Pour notre première étape, nous allons simplement voir que peu de modèle en tant que double
, et d'imprimer le résultat:
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
Ce produit 3823728713643449.5
. Donc, nous voulons à manipuler que d'une certaine façon qui n'est pas évident, mais il est facile à inverser. Je vais semi-choisir arbitrairement une multiplication par 256, ce qui nous donne 978874550692723072
. Maintenant, nous avons juste besoin d'écrire un code masqué à la division par 256, puis imprimez les différents octets de ce dans l'ordre inverse:
#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);
}
Maintenant nous avons beaucoup de casting, en passant des arguments (récursif) main
qui sont complètement ignorés (mais l'évaluation pour obtenir l'incrémentation et de décrémentation sont tout à fait crucial), et bien sûr c'est totalement arbitraire, à la recherche du numéro de dissimuler le fait que ce que nous faisons est vraiment assez simple.
Bien sûr, puisque le but est de dissimulation, si nous en avons envie, nous pouvons prendre des mesures plus ainsi. Juste pour exemple, nous pouvons tirer avantage de l'évaluation de court-circuit, if
déclaration dans une expression unique, de sorte que le corps de la principale ressemble à ceci:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
Pour quelqu'un qui n'est pas habitué à d'obfuscation de code (et/ou du code de golf) cela commence à sembler assez étrange en effet -- informatique et le rejet de la logique and
de certains sens nombre à virgule flottante et la valeur de retour de main
, ce qui n'est même pas de retourner une valeur. Pire, sans s'en rendre compte (et à penser) la façon dont évaluation de court-circuit fonctionne, il peut même ne pas être immédiatement évident comment il évite une récursion infinie.
Notre prochaine étape sera probablement de séparer l'impression de chaque caractère de trouver ce personnage. On peut le faire assez facilement en générant le droit de caractères comme valeur de retour d' main
, et l'impression qu' main
retourne:
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
Au moins pour moi, qui me semble assez obscure, donc je vais en rester là.
Il s’accumule juste un tableau double (16 octets) qui - s’il est interprété comme un tableau de char - accumuler les codes ASCII pour la chaîne « C ++ Sucks »
Toutefois, le code ne fonctionne pas sur chacun des systèmes, elle s’appuie sur certains des faits non définis suivants :
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.