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-ediist 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 printfAufruf) verwendet habe, die sogar -O0EDI 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 loopneeinem 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 ecx0 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/ jnzkamen 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.)