web-dev-qa-db-ja.com

ループまたは条件ステートメントなしで1から1000までを出力するCコードはどのように機能しますか?

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

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

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

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


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

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

以下と同等です:

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

どちらですか:

_(&main)(j+1);
_

_j+1_でmainを呼び出します。

_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演算子または単項演算子のオペランドである場合を除き、タイプ "function Return type"の関数指定子型「型を返す関数へのポインタ」を持つ式に変換されます。

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

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

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

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

そうは言っても、上記のコードは準拠していません(mainは2つの引数を取るか、まったく引数を取りません)、_&exit - &main_は§6.5.6/ 9に従って未定義です:

2つのポインターが減算されると、-両方が同じ配列オブジェクトの要素を指す、または配列オブジェクトの最後の要素の1つ後; ...

_(&main + ...)_の追加はそれ自体で有効であり、使用することができます。if§6.5.6/ 7以降、追加された数量はゼロでした言う:

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

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

264
Mat

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

j/1000項は、すべてのj < 1000について0に切り捨てられます。 jが1000に達すると、1と評価されます。

a + (b - a) * nがあり、nが0または1の場合、n == 0の場合はan == 1の場合はbになります。 aおよびbに対して&mainmain()のアドレス)および&exitを使用すると、jが1000未満の場合は(&main + (&exit - &main) * (j/1000))という用語は&mainを返し、そうでない場合は&exitを返します。結果の関数ポインタには、引数j+1が渡されます。

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

41
tdammers