mã máy x86_64, 4 byte
Hướng dẫn BSF (bit quét chuyển tiếp) thực hiện chính xác điều này !
0x0f 0xbc 0xc7 0xc3
Trong lắp ráp kiểu gcc, đây là:
.globl f
f:
bsfl %edi, %eax
ret
Đầu vào được đưa ra trong thanh ghi EDI và được trả về trong thanh ghi EAX theo các quy ước gọi c 64 bit tiêu chuẩn .
Do mã hóa nhị phân bổ sung của hai, điều này hoạt động cho các số -ve cũng như + ve.
Ngoài ra, mặc dù tài liệu có nội dung "Nếu nội dung của toán hạng nguồn là 0, nội dung của toán hạng đích không được xác định." , Tôi thấy trên máy ảo Ubuntu của mình có đầu ra f(0)
là 0.
Hướng dẫn:
- Lưu ở trên
evenness.s
và lắp ráp vớigcc -c evenness.s -o evenness.o
- Lưu trình điều khiển kiểm tra sau đây
evenness-main.c
và biên dịch với gcc -c evenness-main.c -o evenness-main.o
:
#include <stdio.h>
extern int f(int n);
int main (int argc, char **argv) {
int i;
int testcases[] = { 14, 20, 94208, 7, 0, -4 };
for (i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
printf("%d, %d\n", testcases[i], f(testcases[i]));
}
return 0;
}
Sau đó:
- Liên kết:
gcc evenness-main.o evenness.o -o evenness
- Chạy:
./evenness
@FarazMasroor yêu cầu thêm chi tiết về cách trả lời này.
Tôi quen thuộc với c hơn là sự phức tạp của lắp ráp x86, vì vậy thông thường tôi sử dụng trình biên dịch để tạo mã lắp ráp cho tôi. Tôi biết từ kinh nghiệm rằng các phần mở rộng gcc như __builtin_ffs()
, __builtin_ctz()
và__builtin_popcount()
thường biên dịch và lắp ráp thành 1 hoặc 2 hướng dẫn trên x86. Vì vậy, tôi bắt đầu với một chức năng c như:
int f(int n) {
return __builtin_ctz(n);
}
Thay vì sử dụng trình biên dịch gcc thông thường tất cả các cách để mã đối tượng, bạn có thể sử dụng -S
tùy chọn để biên dịch chỉ để lắp ráp - gcc -S -c evenness.c
. Điều này cung cấp cho một tập tin lắp ráp evenness.s
như thế này:
.file "evenness.c"
.text
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
rep bsfl %eax, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
Rất nhiều trong số này có thể được chơi golf. Đặc biệt chúng ta biết rằng quy ước gọi c cho các hàm có chữ ký là tốt và đơn giản - tham số đầu vào được truyền vào thanh ghi và giá trị trả về được trả về trong thanh ghi. Vì vậy, chúng tôi có thể loại bỏ hầu hết các hướng dẫn - rất nhiều trong số chúng liên quan đến việc lưu các thanh ghi và thiết lập một khung ngăn xếp mới. Chúng tôi không sử dụng ngăn xếp ở đây và chỉ sử dụng thanh ghi, vì vậy không cần phải lo lắng về các thanh ghi khác. Điều này để lại mã lắp ráp "đánh gôn":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Lưu ý như @zwol chỉ ra, bạn cũng có thể sử dụng trình biên dịch được tối ưu hóa để đạt được kết quả tương tự. Cụ thể -Os
tạo ra chính xác các hướng dẫn ở trên (với một vài chỉ thị trình biên dịch bổ sung không tạo ra bất kỳ mã đối tượng bổ sung nào.)
Điều này hiện được lắp ráp với gcc -c evenness.s -o evenness.o
, sau đó có thể được liên kết thành một chương trình trình điều khiển thử nghiệm như được mô tả ở trên.
Có một số cách để xác định mã máy tương ứng với lắp ráp này. Yêu thích của tôi là sử dụng disass
lệnh tháo gỡ gdb :
$ gdb ./evenness
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ./evenness...(no debugging symbols found)...done.
(gdb) disass /r f
Dump of assembler code for function f:
0x00000000004005ae <+0>: 0f bc c7 bsf %edi,%eax
0x00000000004005b1 <+3>: c3 retq
0x00000000004005b2 <+4>: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1)
0x00000000004005bc <+14>: 0f 1f 40 00 nopl 0x0(%rax)
End of assembler dump.
(gdb)
Vì vậy, chúng ta có thể thấy rằng mã máy cho bsf
hướng dẫn là 0f bc c7
và cho ret
là c3
.