Chức năng mã máy x86 32 bit (i386), 13 byte
Quy ước gọi: i386 System V (stack args), với con trỏ NULL là sentinel / terminator cho danh sách end-of-arg-list . (Clobbers EDI, nếu không thì tuân thủ SysV).
C (và asm) không chuyển thông tin loại cho các hàm matrixdic, do đó, mô tả của OP về việc truyền các số nguyên hoặc mảng không có thông tin loại rõ ràng chỉ có thể được thực hiện trong một quy ước truyền một số loại đối tượng struct / class (hoặc con trỏ tới ), không phải số nguyên trần trên ngăn xếp. Vì vậy, tôi quyết định giả định rằng tất cả các đối số đều là con trỏ không phải NULL và người gọi vượt qua một đầu cuối NULL.
Một danh sách con trỏ kết thúc bằng NULL thực sự được sử dụng trong C cho các hàm như POSIXexecl(3)
: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
C không cho phép các int foo(...);
nguyên mẫu không có đối số cố định, nhưng int foo();
có nghĩa tương tự: args không xác định. (Không giống như trong C ++ có nghĩa là gì int foo(void)
). Trong mọi trường hợp, đây là một câu trả lời asm. Việc dỗ một trình biên dịch C để gọi hàm này trực tiếp là thú vị nhưng không bắt buộc.
nasm -felf32 -l/dev/stdout arg-count.asm
với một số dòng bình luận loại bỏ.
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
Câu hỏi cho thấy hàm phải có thể trả về 0 và tôi quyết định làm theo yêu cầu đó bằng cách không bao gồm con trỏ NULL kết thúc trong số đếm arg. Điều này không tốn 1 byte, mặc dù. (Đối với phiên bản 12 byte, hãy loại bỏ LEA và bỏ ghi chú scasd
bên ngoài vòng lặp và xchg
, nhưng không phảidec edx
. Tôi đã sử dụng LEA vì chi phí giống như ba hướng dẫn khác được đặt cùng nhau, nhưng hiệu quả hơn, do đó chức năng sẽ ít hơn ôi.)
Người gọi C để thử nghiệm :
Được xây dựng với:
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
được yêu cầu ngay cả ở -O0 để nói với gcc giả sử rằng các hàm edi
bị khóa mà không lưu / khôi phục nó, bởi vì tôi đã sử dụng rất nhiều cuộc gọi trong một câu lệnh C ( printf
cuộc gọi) thậm chí -O0
đang sử dụng EDI. Dường như an toàn cho gcc main
để chặn EDI từ người gọi của chính nó (bằng mã CRT), trên Linux với glibc, nhưng nếu không thì nó hoàn toàn không có thật để trộn / kết hợp mã được biên dịch với khác nhau -fcall-used-reg
. Không có __attribute__
phiên bản nào của nó để cho phép chúng ta khai báo các hàm asm với các quy ước gọi tùy chỉnh khác với thông thường.
#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);
}
Hai phiên bản khác cũng có 13 byte: phiên bản này dựa trên loopne
trả về giá trị quá cao bằng 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
Phiên bản này sử dụng rep scasd thay vì vòng lặp, nhưng lấy số đếm modulo 256. (Hoặc giới hạn ở mức 256 nếu các byte trên ecx
là 0 khi nhập!)
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
Thật thú vị, nhưng một phiên bản khác dựa trên inc eax
/ pop edx
/ test edx,edx
/ jnz
xuất hiện ở mức 13 byte. Đó là một quy ước callee-pop, không bao giờ được sử dụng bởi các triển khai C cho các hàm biến đổi. (Tôi đã đưa addr retr vào ecx và jmp ecx thay vì ret. (Hoặc đẩy / ret để không phá vỡ ngăn xếp dự báo địa chỉ trả về).