x86-Maschinencodefunktion mit 32 Bit (i386), 13 Byte
Aufrufkonvention: i386 System V (Stapelargumente), mit einem NULL-Zeiger als Sentinel / Terminator für das Ende der Argumentliste . (Clobbers EDI, entspricht sonst SysV).
C (und asm) übergeben keine Typinformationen an verschiedene Funktionen. Daher kann die OP-Beschreibung der Übergabe von Ganzzahlen oder Arrays ohne explizite Typinformationen nur in einer Konvention implementiert werden, die eine Art Struktur- / Klassenobjekt (oder Zeiger auf ein solches Objekt) übergibt ), keine ganzen Zahlen auf dem Stapel. Daher habe ich angenommen, dass alle Argumente Nicht-NULL-Zeiger sind und der Aufrufer ein NULL-Abschlusszeichen übergibt.
Eine NULL-terminierte Zeigerliste von Argumenten wird in C tatsächlich für Funktionen wie POSIX verwendetexecl(3)
: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
C erlaubt keine int foo(...);
Prototypen ohne festes Argument, int foo();
bedeutet aber dasselbe: Argumente, die nicht spezifiziert sind. (Anders als in C ++, wo es bedeutet int foo(void)
). In jedem Fall ist dies eine asm Antwort. Einen C-Compiler zu überreden, diese Funktion direkt aufzurufen, ist interessant, aber nicht erforderlich.
nasm -felf32 -l/dev/stdout arg-count.asm
Einige Kommentarzeilen wurden entfernt.
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
Die Frage zeigt, dass die Funktion in der Lage sein muss, 0 zurückzugeben, und ich entschied mich, dieser Anforderung zu folgen, indem ich den abschließenden NULL-Zeiger nicht in die Anzahl der Argumente einbezog. Dies kostet jedoch 1 Byte. (Entfernen Sie für die 12-Byte-Version die LEA und kommentieren Sie die scasd
äußere Schleife und die xchg
, aber nicht die dec edx
. Ich habe die LEA verwendet, weil sie die gleichen Kosten wie die anderen drei Anweisungen zusammen verursacht, aber effizienter ist, sodass die Funktion geringer ist.) uops.)
C-Aufrufer zum Testen :
Gebaut mit:
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
ist sogar bei -O0 erforderlich, um gcc anzuweisen, davon auszugehen, dass Funktionen nicht gespeichert edi
/ wiederhergestellt werden, da ich so viele Aufrufe in einer C-Anweisung (dem printf
Aufruf) verwendet habe, die sogar -O0
EDI verwendet hat. Es scheint sicher zu sein main
, dass GCCs EDI von ihrem eigenen Aufrufer (im CRT-Code) unter Linux mit glibc blockieren, aber ansonsten ist es völlig falsch, mit verschiedenen kompilierten Codes zu mischen / abzugleichen -fcall-used-reg
. Es gibt keine __attribute__
Version, mit der wir die asm-Funktionen mit anderen als den üblichen benutzerdefinierten Aufrufkonventionen deklarieren könnten.
#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);
}
Zwei andere Versionen kamen ebenfalls mit 13 Bytes an: Diese basiert auf loopne
einem Wert, der um 1 zu hoch ist.
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
Diese Version verwendet rep scasd anstelle einer Schleife, verwendet aber das Argument count modulo 256. (Oder begrenzt auf 256, wenn die oberen Bytes von ecx
0 bei der Eingabe sind!)
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
Komisch, noch eine weitere Version basierend auf inc eax
/ pop edx
/ test edx,edx
/ jnz
kamen 13 Bytes in an. Es ist eine Callee-Pops-Konvention, die von C-Implementierungen niemals für verschiedene Funktionen verwendet wird. (Ich habe die ret-Adresse in ecx und jmp ecx anstelle von ret eingefügt. (Oder push / ret, um den Prädiktorstapel für die Rücksprungadresse nicht zu unterbrechen.)