なぜこのコードは出力を与えるのC++Sucks
ですか?その背後にあるコンセプトは何ですか?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
skcuS++C
ます。
なぜこのコードは出力を与えるのC++Sucks
ですか?その背後にあるコンセプトは何ですか?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
skcuS++C
ます。
回答:
数値7709179928849219.0
は、64ビットとして次のバイナリ表現を持っていますdouble
。
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
標識の位置を示します。^
指数と-
仮数(つまり、指数なしの値)の。
表現は2進数の指数と仮数を使用するため、数値を2倍にすると、指数が1増加します。プログラムはそれを正確に771回実行するため、1075で始まった指数(10進表記10000110011
)は最後に1075 + 771 = 1846になります。1846のバイナリ表現はです11100110110
。結果のパターンは次のようになります。
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
このパターンは、印刷された文字列に対応し、逆方向のみです。同時に、配列の2番目の要素がゼロになり、nullターミネーターが提供されるため、文字列はに渡すのに適していprintf()
ます。
7709179928849219
。値を貼り付け、バイナリ表現を取得しました。
より読みやすいバージョン:
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);
}
}
main()
771回再帰的に呼び出します。
初めはm[0] = 7709179928849219.0
、はの略ですC++Suc;C
。すべての呼び出しで、m[0]
最後の2文字を「修復」するために2倍になります。最後の呼び出しでは、m[0]
のASCII文字の表現が含まれていますC++Sucks
し、m[1]
それが持っているので、ゼロのみが含まれているヌル・ターミネータのためのC++Sucks
文字列を。すべてm[0]
が8バイトに格納されていると仮定して、各文字は1バイトを使用します。
再帰と違法なmain()
呼び出しがないと、次のようになります。
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
免責事項:この回答は、C ++のみに言及し、C ++ヘッダーを含む質問の元の形式に投稿されました。質問の純粋なCへの変換は、元の質問者からの入力なしで、コミュニティによって行われました。
正式には、このプログラムは形式が正しくない(つまり、正当なC ++ではない)ため、このプログラムについて推論することは不可能です。C ++ 11 [basic.start.main] p3に違反しています:
関数mainはプログラム内で使用しないでください。
これはさておき、一般的なコンシューマコンピュータでdouble
は、a は8バイトの長さであり、よく知られた特定の内部表現を使用するという事実に依存しています。配列の初期値は「アルゴリズム」が実行されたときに最初の最終値がdouble
内部表現(8バイト)が8文字のASCIIコードになるような値になるように計算されますC++Sucks
。配列の2番目の要素はであり0.0
、その最初のバイトは0
内部表現にあり、これを有効なCスタイルの文字列にします。次に、これを使用して出力に送信されますprintf()
。
上記のいくつかが成り立たないHWでこれを実行すると、代わりにガベージテキスト(またはおそらく範囲外のアクセス)が発生します。
basic.start.main
おきます-C ++ 03にも同じ表現の3.6.1 / 3がありました。
main()
、またはハードドライブ、または任意の書式を設定するためのAPI呼び出しに置き換えます。
おそらく、コードを理解する最も簡単な方法は、逆の処理を行うことです。印刷する文字列から始めます。バランスのために、「C ++ Rocks」を使用します。重要な点:オリジナルと同様に、長さは正確に8文字です。オリジナルとほぼ同じように印刷し、逆の順序で印刷するので、逆の順序で配置することから始めます。最初のステップとして、そのビットパターンをとして表示double
し、結果を出力します。
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
これは生成し3823728713643449.5
ます。それで、私たちはそれを明白ではないが、簡単に元に戻すことができる方法で操作したいと思います。私は、任意に256倍の乗算を選択します978874550692723072
。ここで、256で除算する難読化されたコードを記述し、その個々のバイトを逆の順序で出力する必要があります。
#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);
}
これmain
で、完全に無視される(再帰的な)引数を(再帰的)に渡してキャストします(ただし、増分と減分を取得するための評価は非常に重要です)。本当に簡単です。
もちろん、要点はすべて難読化されているので、必要に応じて、さらに多くの手順を実行できます。たとえば、短絡評価を利用してif
ステートメントを単一の式に変えることができるため、mainの本体は次のようになります。
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
難読化コード(および/またはコードゴルフ)に慣れていない誰にもこの開始は確かにかなり奇妙に見えるために-論理的なコンピューティングおよび廃棄and
いくつかの無意味なの浮動小数点数とからの戻り値main
でもAが返されていません、値。さらに悪いことに、短絡評価がどのように機能するかを認識(および考え)なければ、無限再帰を回避する方法がすぐに明らかになることすらありません。
次のステップは、各文字の印刷とその文字の検出を分離することでしょう。からの戻り値として適切な文字を生成し、main
何をmain
返すかを出力することで、これをかなり簡単に行うことができます。
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
少なくとも私には、それは十分に難読化されているように見えるので、それはそのままにしておきます。
他はかなり完全に質問を説明しました、私はこれが標準に従って未定義の動作であるというメモを追加したいと思います。
C ++ 11 3.6.1 / 3 メイン関数
関数mainはプログラム内で使用しないでください。mainのリンケージ(3.5)は実装定義です。mainを削除済みとして定義するプログラム、またはmainをインライン、静的、またはconstexprとして宣言するプログラムは、形式が正しくありません。mainという名前は、他に予約されていません。[例:メンバー関数、クラス、および列挙は、他の名前空間のエンティティと同様に、メインと呼ぶことができます。—例を終了]
コードは次のように書き直すことができます。
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
それがしていることは、double
配列にバイトのセットを生成することですm
「C ++ Sucks」という文字に対応し、その後にnullターミネータが続きます。彼らは、771回2倍したときに、配列の2番目のメンバーによって提供されるnullターミネーターを持つバイトのセットを標準表現で生成するdouble値を選択することにより、コードを難読化しました。
このコードは別のエンディアン表現では機能しないことに注意してください。また、呼び出しmain()
は厳しく禁止されています。
f
戻ってくるのint
ですか?
int
問題のリターンをコピーするのを無知だった。修正させてください。
最初に、倍精度の数値は次のようにバイナリ形式でメモリに格納されることを思い出してください。
(i)符号用の1ビット
(ii)指数用の11ビット
(iii)マグニチュード用の52ビット
ビットの順序は(i)から(iii)に減少します。
最初に、10進数の小数が同等の小数の2進数に変換され、次に2進数の桁数の形式で表されます。
したがって、7709179928849219.0という数値は
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
マグニチュードビットを考慮しながら、1。は無視されます。
したがって、等級部分は次のようになります。
1011011000110111010101010011001010110010101101000011
これで2のべき乗は52になります。バイアス値を2 ^(指数-1のビット)-1として追加する必要があります。 つまり2 ^(11 -1)-1 = 1023なので、指数は52 + 1023 = 1075になります。
これで、コードに2と771倍の数値が乗算され、指数が771増加します。
したがって、指数は(1075 + 771)= 1846で、2進数の等価物は(11100110110)です。
これで数値は正なので、符号ビットは0です。
したがって、変更後の番号は次のようになります。
符号ビット+指数+大きさ(ビットの単純な連結)
0111001101101011011000110111010101010011001010110010101101000011
mはcharポインタに変換されるため、LSDからビットパターンを8のチャンクに分割します。
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(16進数の同等物は:)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
示されているように文字マップからどれが:
s k c u S + + C
これが行われると、m [1]は0になり、これはNULL文字を意味します
ここで、このプログラムをリトルエンディアンマシン(下位ビットは下位アドレスに格納されています)で実行すると仮定すると、ポインターmは最下位アドレスビットを指し、ビットを8のチャックに取り込みます(char *にキャストされる型として)。 )そして最後のチャンクで00000000に遭遇するとprintf()は停止します...
ただし、このコードは移植できません。