32-бітна (i386) функція машинного коду x86, 13 байт
Конвенція виклику: Система i386 V (стеки арг), з покажчиком NULL як дозорний / термінатор для кінця аргументу списку . (Clobbers EDI, інакше відповідає SysV).
C (і ASM) не передають інформацію про тип різноманітним функціям, тому опис ОП передачі цілих чисел або масивів без явної інформації про тип може бути реалізований лише в конвенції, яка передає якийсь об'єкт структури / класу (або покажчики на такий ), а не оголені цілі числа на стеку. Тому я вирішив припустити, що всі аргументи були не-NULL-покажчиками, а абонент передає NULL-термінатор.
Список покажчиків аргументів, що закінчується NULL, фактично використовується в C для таких функцій, як POSIXexecl(3)
: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
C не дозволяє int foo(...);
прототипам без фіксованого аргументу, але int foo();
означає те саме: аргументи не вказані. (На відміну від C ++, де це означає int foo(void)
). У будь-якому випадку, це відповідь asm. Спільний компілятор С для виклику цієї функції безпосередньо цікавий, але не обов'язковий.
nasm -felf32 -l/dev/stdout arg-count.asm
видалено рядки з коментарями.
24 global argcount_pointer_loop
25 argcount_pointer_loop:
26 .entry:
28 00000000 31C0 xor eax, eax ; search pattern = NULL
29 00000002 99 cdq ; counter = 0
30 00000003 89E7 mov edi, esp
31 ; scasd ; edi+=4; skip retaddr
32 .scan_args:
33 00000005 42 inc edx
34 00000006 AF scasd ; cmp eax,[edi] / edi+=4
35 00000007 75FC jne .scan_args
36 ; dec edx ; correct for overshoot: don't count terminator
37 ; xchg eax,edx
38 00000009 8D42FE lea eax, [edx-2] ; terminator + ret addr
40 0000000C C3 ret
size = 0D db $ - .entry
Питання показує, що функція повинна вміти повертати 0, і я вирішив виконати цю вимогу, не включаючи закінчуючий вказівник NULL у число аргументів. Однак це коштує 1 байт. (Для 12-байтової версії вийміть LEA і розметайте scasd
зовнішній цикл та на xchg
, але не на dec edx
. Я використовував LEA, оскільки він коштує так само, як і ці три три інструкції, укомплектовані, але він більш ефективний, тому функцій менше упс.)
C абонент для тестування :
Побудовано з:
nasm -felf32 -l /dev/stdout arg-count.asm | cut -b -28,$((28+12))- &&
gcc -Wall -O3 -g -std=gnu11 -m32 -fcall-used-edi arg-count.c arg-count.o -o ac &&
./ac
-fcall-used-edi
потрібно навіть при -O0 сказати gcc припускати, що функція clobber edi
не зберігає / не відновлює її, оскільки я використав стільки викликів в одному операторі C ( printf
виклик), що навіть -O0
використовував EDI. Це, мабуть, є безпечним для того, main
щоб gcc's клобувати EDI від власного абонента (у CRT-коді), в Linux з glibc, але в іншому випадку змішувати / відповідати коду, компільованому з різними, абсолютно неправдиво -fcall-used-reg
. Немає його __attribute__
версії, яка б дозволила нам оголосити функції asm зі спеціальними умовами викликів, відмінними від звичайних.
#include <stdio.h>
int argcount_rep_scas(); // not (...): ISO C requires at least one fixed arg
int argcount_pointer_loop(); // if you declare args at all
int argcount_loopne();
#define TEST(...) printf("count=%d = %d = %d (scasd/jne) | (rep scas) | (scas/loopne)\n", \
argcount_pointer_loop(__VA_ARGS__), argcount_rep_scas(__VA_ARGS__), \
argcount_loopne(__VA_ARGS__))
int main(void) {
TEST("abc", 0);
TEST(1, 1, 1, 1, 1, 1, 1, 0);
TEST(0);
}
Дві інші версії також увійшли в 13 байт: ця заснована на loopne
поверненні значення, яке занадто високе на 1.
45 global argcount_loopne
46 argcount_loopne:
47 .entry:
49 00000010 31C0 xor eax, eax ; search pattern = NULL
50 00000012 31C9 xor ecx, ecx ; counter = 0
51 00000014 89E7 mov edi, esp
52 00000016 AF scasd ; edi+=4; skip retaddr
53 .scan_args:
54 00000017 AF scasd
55 00000018 E0FD loopne .scan_args
56 0000001A 29C8 sub eax, ecx
58 0000001C C3 ret
size = 0D = 13 bytes db $ - .entry
Ця версія використовує rep scasd замість циклу, але приймає аргумент підрахунку аргументу 256. (Або обмежено на 256, якщо верхні байти ecx
дорівнюють 0!)
63 ; return int8_t maybe?
64 global argcount_rep_scas
65 argcount_rep_scas:
66 .entry:
67 00000020 31C0 xor eax, eax
68 ; lea ecx, [eax-1]
69 00000022 B1FF mov cl, -1
70 00000024 89E7 mov edi, esp
71 ; scasd ; skip retaddr
72 00000026 F2AF repne scasd ; ecx = -len - 2 (including retaddr)
73 00000028 B0FD mov al, -3
74 0000002A 28C8 sub al, cl ; eax = -3 +len + 2
75 ; dec eax
76 ; dec eax
77 0000002C C3 ret
size = 0D = 13 bytes db $ - .entry
Цікаво, ще одна версія , заснована на inc eax
/ pop edx
/ test edx,edx
/ jnz
увійшов в 13 байт. Це умовний виклик callee-pops, який ніколи не використовується реалізаціями C для варіативних функцій. (Я перескакував ret addr в ecx, а jmp ecx замість ret. (Або натисніть / ret, щоб не порушити стек передбачувача зворотної адреси).