このコードが出力を与えるのはなぜC++Sucks
ですか? その背後にあるコンセプトは何ですか?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
ここでテストします。
このコードが出力を与えるのはなぜC++Sucks
ですか? その背後にあるコンセプトは何ですか?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
ここでテストします。
数値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 番目の要素がゼロになり、ヌル ターミネータが提供され、文字列が に渡すのに適したものになりprintf()
ます。
より読みやすいバージョン:
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 char 表現が含まれてC++Sucks
おりm[1]
、ゼロのみが含まれているため、文字列のnull ターミネータがありC++Sucks
ます。すべてが 8 バイトで格納されると想定m[0]
されているため、各文字は 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 でこれを実行すると、代わりにガベージ テキスト (または、境界外のアクセスでさえ) が発生します。
おそらく、コードを理解する最も簡単な方法は、逆の順序で作業することです。出力する文字列から始めます -- バランスを取るために、"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
ステートメントを単一の式に変えることができるため、メインの本体は次のようになります。
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
難読化されたコード (および/またはコード ゴルフ) に慣れていない人にとっては、これはかなり奇妙に見え始めます。and
無意味な浮動小数点数の論理値と からの戻り値を計算して破棄しますmain
。価値。さらに悪いことに、短絡評価がどのように機能するかを認識 (および考え) しないと、無限再帰を回避する方法がすぐにはわからない可能性があります。
次のステップは、おそらく、各文字の印刷とその文字の検索を分離することです。main
からの戻り値として適切な文字を生成し、戻り値を出力することで、これを非常に簡単に行うことができmain
ます。
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
少なくとも私には、それは十分難読化されているように見えるので、そのままにしておきます。
次のコードは を出力C++Suc;C
するので、掛け算全体は最後の 2 文字だけです
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
コードは次のように書き直すことができます。
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
これが行っているのは、たまたま文字「C++Sucks」に対応し、その後に null ターミネータが続く一連のバイトをdouble
配列内に生成することです。m
彼らは double 値を選択することでコードを難読化し、771 回倍増すると、標準表現では、配列の 2 番目のメンバーによって提供される null ターミネータを持つバイトのセットが生成されます。
このコードは、別のエンディアン表現では機能しないことに注意してください。また、通話main()
は厳禁です。