80386 머신 코드, 105 바이트
코드의 16 진 덤프 :
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 3 3 3 3 2 2 2 2 1 1 1 1 1 1
이것은 많은 설명이 필요합니다 ...
물론 다른 사람들도 사용했던 알고리즘을 사용했습니다. 복잡성에 대한 요구 사항은 선택하지 않았습니다. 따라서 알고리즘을 너무 많이 설명 할 필요가 없습니다. 어쨌든:
최대 이하의 부분을 사용 f(n, m)
하는 n
요소 의 분할 수로 표시합니다 m
. 두 번째 배열을 C로 선언 된 2D 배열에 저장합니다 f[65][64]
. 첫 번째 색인은 n
이고 두 번째 색인은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이므로 한 행의 크기는 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
}