18

次の質問は、大学のプログラミングコンテストで出されました。出力を推測したり、その動作を説明したりするように求められました。言うまでもなく、私たちの誰も成功しませんでした。

main(_){write(read(0,&_,1)&&main());}

いくつかの短いグーグルは私をこの正確な質問に導きました:で尋ねられましたcodegolf.stackexchange.com

https://codegolf.stackexchange.com/a/1336/4085

そこで、それが何をするのかを説明しました:Reverse stdin and place on stdout、しかしどのようにではありませ

私はまた、この質問でいくつかの助けを見つけました:メインへの3つの議論、および他の難解なトリック ですが、それでもどのようmain(_)に、&_そしてどのように&&main()機能するかを説明していません。

私の質問は、これらの構文はどのように機能するのかということです。それらは、私が知っておくべきことですか、それでも関連性がありますか?

完全な答えではないにしても、(リソースリンクなどへの)ポインタに感謝します。

4

2 に答える 2

26

このプログラムは何をしますか?

main(_){write(read(0,&_,1)&&main());}

それを分析する前に、それをきれいにしましょう:

main(_) {
    write ( read(0, &_, 1) && main() );
}

まず、_醜い名前ではありますが、それが有効な変数名であることを知っておく必要があります。変更しましょう:

main(argc) {
    write( read(0, &argc, 1) && main() );
}

次に、関数の戻り型とパラメーターの型がCではオプションであることに注意してください(C ++ではオプションではありません)。

int main(int argc) {
    write( read(0, &argc, 1) && main() );
}

次に、戻り値がどのように機能するかを理解します。特定のCPUタイプの場合、戻り値は常に同じレジスタに格納されます(たとえば、x86のEAX)。したがって、returnステートメントを省略すると、戻り値は最新の関数が返したものになる可能性があります。

int main(int argc) {
    int result = write( read(0, &argc, 1) && main() );
    return result;
}

toの呼び出しreadは、多かれ少なかれ明白です。標準のin(ファイル記述子0)から、にあるメモリにバイト単位で読み込まれ&argcます11読み取りが成功した場合は戻り、それ以外の場合は0を返します。

&&論理「and」演算子です。左側が「true」(技術的にはゼロ以外の値)である場合にのみ、右側を評価します。&&式の結果は、int常に1(「true」の場合)または0(falseの場合)になります。

この場合、右側mainは引数なしで呼び出します。main1つの引数で宣言した後、引数なしで呼び出すことは未定義の動作です。それでも、パラメータの初期値を気にしない限り、多くの場合機能しargcます。

次に、の結果が&&に渡されwrite()ます。したがって、コードは次のようになります。

int main(int argc) {
    int read_result = read(0, &argc, 1) && main();
    int result = write(read_result);
    return result;
}

うーん。writeマニュアルページをざっと見ると、 1つではなく3つの引数を取ることがわかります。未定義動作の別のケース。main引数が少なすぎる場合と同じようwriteに、2番目と3番目の引数で何が返されるかを予測することはできません。一般的なコンピューターでは、何かを取得しますが、何が発生するかはわかりません。write(非定型のコンピューターでは、奇妙なことが起こる可能性があります。)作成者は、以前にメモリスタックに格納されていたものをすべて受信することに依存しています。そして、彼はそれが2番目と3番目の引数であることに依存しています。

int main(int argc) {
    int read_result = read(0, &argc, 1) && main();
    int result = write(read_result, &argc, 1);
    return result;
}

への無効な呼び出しを修正し、mainヘッダーを追加して、次のように展開し&&ます。

#include <unistd.h>
int main(int argc, int argv) {
    int result;
    result = read(0, &argc, 1);
    if(result) result = main(argc, argv);
    result = write(result, &argc, 1);
    return result;
}


結論

このプログラムは、多くのコンピューターで期待どおりに機能しません。元の作成者と同じコンピューターを使用している場合でも、別のオペレーティングシステムでは動作しない可能性があります。同じコンピューターと同じオペレーティングシステムを使用している場合でも、多くのコンパイラーでは機能しません。同じコンピュータコンパイラとオペレーティングシステムを使用している場合でも、コンパイラのコマンドラインフラグを変更すると機能しない場合があります。

コメントで言ったように、質問には有効な答えがありません。コンテストの主催者または審査員が別の言い方をしているのを見つけた場合は、次のコンテストに招待しないでください。

于 2012-04-25T18:24:23.747 に答える
9

わかりました。_これは、初期のK&R C構文で宣言された変数であり、デフォルトの型はintです。一時的なストレージとして機能します。

プログラムは、標準入力から1バイトを読み取ろうとします。入力がある場合は、mainを再帰的に呼び出し、1バイトを読み取り続けます。

入力の最後に、read(2)は0を返し、式は0を返し、write(2)システムコールは実行され、コールチェーンはおそらく巻き戻されます。

この時点から結果は実装に大きく依存するため、ここでは「おそらく」と言います。他のパラメータwrite(2)が欠落していますが、何かがレジスタとスタックにあるため、何かがカーネルに渡されます。同じ未定義の動作が、のさまざまな再帰的アクティブ化からの戻り値に適用されますmain

私のx86_64Macでは、プログラムはEOFまで標準入力を読み取り、その後終了して、何も書き込みません。

于 2012-04-25T18:13:49.173 に答える