80386マシンコード、105バイト
コードのHexdump:
60 8b fa 81 ec 00 41 00 00 33 c0 8b f4 33 d2 42
89 14 06 42 33 ed 8b d8 03 2c 1e 2a fa 73 f9 83
c6 04 89 2c 06 42 3b d1 76 ea fe c4 3a e1 76 db
33 d2 0f c7 f0 f7 f5 86 e9 85 d2 74 1b 33 c0 8d
34 0c 39 14 86 77 03 40 eb f8 2b 54 86 fc 40 89
07 83 c7 04 2a e8 77 e1 42 89 17 83 c7 04 fe cd
7f f7 4a b6 41 03 e2 61 c3
C関数として:void random_partition(int n, int result[]);
。指定されたバッファ内の数値のリストとして結果を返します。リストの終わりを示すものではありませんが、ユーザーは数字を累積することで終わりを見つけることができます-合計がn
ます。
使用方法(Visual Studioで):
#include <stdio.h>
__declspec(naked) void __fastcall random_partiton(int n, int result[])
{
#define a(byte) __asm _emit 0x ## byte
a(60) a(8b) a(fa) a(81) a(ec) a(00) a(41) a(00) a(00) a(33) a(c0) a(8b) a(f4) a(33) a(d2) a(42)
a(89) a(14) a(06) a(42) a(33) a(ed) a(8b) a(d8) a(03) a(2c) a(1e) a(2a) a(fa) a(73) a(f9) a(83)
a(c6) a(04) a(89) a(2c) a(06) a(42) a(3b) a(d1) a(76) a(ea) a(fe) a(c4) a(3a) a(e1) a(76) a(db)
a(33) a(d2) a(0f) a(c7) a(f0) a(f7) a(f5) a(86) a(e9) a(85) a(d2) a(74) a(1b) a(33) a(c0) a(8d)
a(34) a(0c) a(39) a(14) a(86) a(77) a(03) a(40) a(eb) a(f8) a(2b) a(54) a(86) a(fc) a(40) a(89)
a(07) a(83) a(c7) a(04) a(2a) a(e8) a(77) a(e1) a(42) a(89) a(17) a(83) a(c7) a(04) a(fe) a(cd)
a(7f) a(f7) a(4a) a(b6) a(41) a(03) a(e2) a(61) a(c3)
}
void make_stack() // see explanations about stack below
{
volatile int temp[65 * 64];
temp[0] = 999;
}
int main()
{
int result[100], j = 0, n = 64, counter = n;
make_stack(); // see explanations about stack below
random_partiton(n, result);
while (counter > 0)
{
printf("%d ", result[j]);
counter -= result[j];
++j;
}
putchar('\n');
}
出力例(n = 64の場合):
21 7 4 4 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1
これには多くの説明が必要です...
もちろん、私は他の誰もが使用したアルゴリズムも使用しました。複雑さに関する要件には選択肢がありませんでした。したがって、アルゴリズムについてあまり説明する必要はありません。とにかく:
以下の部分を使用してf(n, m)
、n
要素の分割数で示しm
ます。それらを2次元配列(Cで宣言f[65][64]
)に格納します。最初のインデックスはn
で、2番目のインデックスはm-1
です。私はサポートすることを決めましたn=65
があまりにも面倒ので、それを放棄しました...
このテーブルを計算するCコードは次のとおりです。
#define MAX_M 64
int f[(MAX_M + 1) * MAX_M];
int* f2;
int c; // accumulates the numbers needed to calculate f(n, m)
int m;
int k; // f(k, m), for various values of k, are accumulated
int n1;
for (n1 = 0; n1 <= n; ++n1)
{
f2 = f;
f2[n1 * MAX_M] = 1;
for (m = 2; m <= n; ++m)
{
c = 0;
k = n1;
while (k >= 0)
{
c += f2[k * MAX_M];
k -= m;
}
++f2;
f2[n1 * MAX_M] = c;
}
}
このコードには難読化されたスタイルがあるため、アセンブリ言語に簡単に変換できます。要素f(n, n)
の分割数であるまで要素を計算しn
ます。このコードが終了すると、一時変数c
に必要な番号が含まれます。この番号を使用して、ランダムパーティションを選択できます。
int index = rand() % c;
後で、これindex
は生成されたテーブルを使用して必要な形式(数字のリスト)に変換されます。
do {
if (index == 0)
break;
m = 0;
f2 = &f[n * MAX_M];
while (f2[m] <= index)
{
++m;
}
index -= f2[m-1];
++m;
*result++ = m;
n -= m;
} while (n > 0);
do {
*result++ = 1;
--n;
} while (n > 0);
このコードは、アセンブリ言語への変換用にも最適化されています。小さな「バグ」があります:パーティショニング1
の最後に数字が含まれていない場合、最後のループはに遭遇しn = 0
、不要な1
要素を出力します。ただし、印刷コードは数値の合計を追跡し、この余分な数値を印刷しないため、問題はありません。
インラインアセンブリに変換すると、このコードは次のようになります。
__declspec(naked) void _fastcall random_partition_asm(int n, int result[])
{
_asm {
pushad;
// ecx = n
// edx = m
// bh = k; ebx = k * MAX_M * sizeof(int)
// ah = n1; eax = n1 * MAX_M * sizeof(int)
// esp = f
// ebp = c
// esi = f2
// edi = result
mov edi, edx;
sub esp, (MAX_M + 1) * MAX_M * 4; // allocate space for table
xor eax, eax;
row_loop:
mov esi, esp;
xor edx, edx;
inc edx;
mov dword ptr [esi + eax], edx;
inc edx;
col_loop:
xor ebp, ebp;
mov ebx, eax;
sum_loop:
add ebp, [esi + ebx];
sub bh, dl;
jae sum_loop;
add esi, 4;
mov [esi + eax], ebp;
inc edx;
cmp edx, ecx;
jbe col_loop;
inc ah;
cmp ah, cl;
jbe row_loop;
// Done calculating the table
// ch = n; ecx = n * MAX_M * sizeof(int)
// eax = m
// ebx =
// edx = index
// esp = f
// esi = f2
// ebp = c
// edi = result
xor edx, edx;
rdrand eax; // generate a random number
div ebp; // generate a random index in the needed range
xchg ch, cl; // multiply by 256
n_loop:
test edx, edx;
jz out_trailing;
xor eax, eax;
lea esi, [esp + ecx];
m_loop:
cmp [esi + eax * 4], edx;
ja m_loop_done;
inc eax;
jmp m_loop;
m_loop_done:
sub edx, [esi + eax * 4 - 4];
inc eax;
mov [edi], eax;
add edi, 4;
sub ch, al;
ja n_loop;
out_trailing:
inc edx;
out_trailing_loop:
mov dword ptr [edi], edx;
add edi, 4;
dec ch;
jg out_trailing_loop;
dec edx;
mov dh, (MAX_M + 1) * MAX_M * 4 / 256;
add esp, edx;
popad;
ret;
}
}
注意すべきいくつかの楽しいこと:
- 乱数の生成には、わずか3バイトのマシンコード(
rdrand
命令)が必要です
- 偶然にも、テーブルのサイズは64であるため、1行のサイズは256バイトです。これを使用して、行インデックスをのような「高バイト」レジスタに保持します。これ
ah
により、256倍の自動乗算が可能になります。これを利用するために、n = 65
ます。。私はこの罪を許されることを願っています...
スタック上のスペースの割り当ては、スタックポインタレジスタから0x4100を引くことによって実行されesp
ます。これは6バイトの命令です!この番号を追加して戻すと、5バイトで管理できました。
dec edx; // here edx = 1 from earlier calculations
mov dh, (MAX_M + 1) * MAX_M * 4 / 256; // now edx = 0x4100
add esp, edx; // this deallocates space on stack
MS Visual Studioでこの関数をデバッグすると、スタックに割り当てたスペースにデータを書き込むとクラッシュすることがわかりました。いくつか掘り下げた後、ある種のスタックオーバーラン保護を発見しました。OSは、スタックに非常に限られた範囲の仮想アドレスのみを割り当てているようです。関数が遠く離れたアドレスにアクセスすると、OSはそれがオーバーランであると見なし、プログラムを強制終了します。ただし、関数に多くのローカル変数がある場合、OSはそれを機能させるために追加の「魔法」を実行します。そのため、スタックに割り当てられた大きな配列を持つ空の関数を呼び出す必要があります。この関数が戻ると、追加のスタックVMページが割り当てられ、使用できるようになります。
void make_stack()
{
volatile int temp[65 * 64];
temp[0] = 999; // have to "use" the array to prevent optimizing it out
}