ループや条件付きステートメントなしで1から1000まで印刷するCコードはどのように機能しますか?


148

ループや条件なしで1から1000までC出力するコードを見つけました。しかし、それがどのように機能するのかわかりません。誰かがコードを調べて各行を説明できますか?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

1
CまたはC ++としてコンパイルしていますか?どのようなエラーが表示されますか?mainC ++では呼び出すことができません。
ninjalj

@ninjalj C ++プロジェクトを作成し、コードをコピー/貼り付け、エラーは次のとおりです:不正、左のオペランドの型は 'void(
__cdecl

1
@ninjaljこれらのコードはideone.orgで機能しますが、ビジュアルスタジオでは機能しませんideone.com/MtJ1M
ob_dev

@oussama似ていますが、少しより理解しにくい:ideone.com/2ItXm ますね歓迎。:)
マーク

2
これらの行からすべての「&」文字を削除しました(&main +(&exit-&main)*(j / 1000))(j + 1); そして、このコードはまだ機能します。
ob_dev '30 / 10/30

回答:


264

そのようなコードを書かないでください。


の場合j<1000j/1000ゼロです(整数除算)。そう:

(&main + (&exit - &main)*(j/1000))(j+1);

以下と同等です。

(&main + (&exit - &main)*0)(j+1);

これは:

(&main)(j+1);

どの呼び出すmainj+1

の場合j == 1000、同じ行が次のように出力されます。

(&main + (&exit - &main)*1)(j+1);

どちらに要約する

(&exit)(j+1);

それはexit(j+1)プログラムであり、プログラムから離れます。


(&exit)(j+1)exit(j+1)本質的に同じです-C99§6.3.2.1/ 4を引用:

関数指定子は、関数型を持つ式です。sizeof演算子または単項&演算子のオペランドである場合を除いて、型が「関数の戻り型」の関数指定子は、型が「関数の戻り型へのポインタ」の式に変換されます。

exit関数指定子です。単項&アドレス演算子がない場合でも、関数へのポインタとして扱われます。(&それを明示的にするだけです。)

また、関数呼び出しについては、§6.5.2.2/ 1以降で説明しています。

呼び出された関数を表す式には、voidを返すか、配列型以外のオブジェクト型を返す関数へのポインターが必要です。

したがってexit(j+1)、関数型から関数へのポインター型への自動変換により機能し、関数へのポインター型へ(&exit)(j+1)の明示的な変換でも機能します。

とはいえ、上記のコードは準拠しておらず(main2つの引数を取るか、まったく引数を取らない)、&exit - &main§6.5.6/ 9に従って未定義であると私は考えています。

2つのポインターが差し引かれると、両方とも同じ配列オブジェクトの要素を指すか、配列オブジェクトの最後の要素の1 つ後を指します。...

追加(&main + ...)はそれ自体有効であり、§6.5.6/ 7で次のように追加された数量がゼロの場合に使用できます。

これらの演算子の目的では、配列の要素ではないオブジェクトへのポインターは、オブジェクトの型を要素型として、長さが1の配列の最初の要素へのポインターと同じように動作します。

したがって、ゼロを追加しても&main問題ありません(ただし、あまり使用しません)。


4
foo(arg)そして、(&foo)(arg)同等であり、彼らは、引数argでFOOを呼び出します。newty.de/fpt/fpt.htmlは、関数ポインターに関する興味深いページです。
マット

1
@Krishnabhadra:最初のケースでfooは、はポインターで&fooあり、そのポインターのアドレスです。2番目の場合、fooは配列で、&foofooと同等です。
マット

8
少なくともC99のため、必要以上に複雑:((void(*[])()){main, exit})[j / 1000](j + 1);
パーヨハンソン

1
&foofoo配列に関しては同じではありません。&fooは配列fooへのポインタ、は最初の要素へのポインタです。彼らは同じ価値を持っています。関数の場合fun&fun両方とも関数へのポインターです。
ヨハンソンによれば

1
あなたが見ればFYI、他の質問は、上記の引用に関連する答え、あなたは事実comformant C99にある変化があるかわかります。怖いですが、本当です。
Daniel Pryden、2011年

41

再帰、ポインター演算を使用し、整数除算の丸め動作を利用します。

j/1000用語はすべてのために0に切り捨てj < 1000。一度jに達する1000年、それは1と評価されます。

ここで、がある場合a + (b - a) * nnは0または1のどちらかで、if とaif n == 0になりbますn == 1&main(のアドレスmain())と&exitfor aおよびを使用するとb、が1000未満の場合に用語(&main + (&exit - &main) * (j/1000))が返されます。次に、結果の関数ポインタに引数が渡されます。&mainj&exitj+1

このコンストラクト全体は、再帰的な動作になりjます。1000未満の場合、mainそれ自体を再帰的に呼び出します。j1000 に達すると、exit代わりに呼び出し、プログラムを終了コード1001で終了させます(これはダーティですが、機能します)。


1
良い答えですが、疑問が1つあります。終了コード1001でのメインの終了方法 メインは何も返しません。デフォルトの戻り値はありますか?
Krishnabhadra、2011年

2
jが1000に達すると、mainはそれ自体に再帰しなくなります。代わりに、libc関数を呼び出します。この関数exitは、終了コードを引数として取り、現在のプロセスを終了します。その時点で、jは1000なので、j + 1は1001に等しく、これが終了コードになります。
tdammers '29年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.