当使用 GNU C 内联 asm 时,使用约束来告诉编译器你想要的东西,而不是使用 asm 模板中的指令“手动”执行它。
对于writechar
and readchar
,我们只需要 a"syscall"
作为模板,根据x86-64 Linux 系统调用约定(char
非常接近匹配 System V ABI 的函数调用约定)。 i386 和 x86-64 上的 UNIX 和 Linux 系统调用的调用约定是什么。write(2)
这也可以轻松避免破坏编译器可能保留值的红色区域(低于 RSP 128 字节)。你不能从内联 asm 中破坏它(所以push
/pop
除非你sub rsp, 128
首先使用:请参阅Using base pointer register in C++ inline asm以及许多关于 GNU C inline asm 的有用链接),并且没有办法告诉编译器你破坏它。您可以使用 构建-mno-redzone
,但在这种情况下,输入/输出操作数要好得多。
我很犹豫叫这些putchar
和getchar
。如果您正在实现自己的不支持缓冲的 stdio,则可以这样做,但某些功能需要输入缓冲才能正确实现。例如,scanf
必须检查字符以查看它们是否与格式字符串匹配,如果不匹配则将它们设为“未读”。不过,输出缓冲是可选的;你可以我认为完全实现 stdio 与创建私有缓冲区和write()
它的函数,或直接write()
他们的输入指针。
writechar()
:
int writechar(char my_char)
{
int retval; // sys_write uses ssize_t, but we only pass len=1
// so the return value is either 1 on success or -1..-4095 for error
// and thus fits in int
asm volatile("syscall #dummy arg picked %[dummy]\n"
: "=a" (retval) /* output in EAX */
/* inputs: ssize_t read(int fd, const void *buf, size_t count); */
: "D"(1), // RDI = fd=stdout
"S"(&my_char), // RSI = buf
"d"(1) // RDX = length
, [dummy]"m" (my_char) // dummy memory input, otherwise compiler doesn't store the arg
/* clobbered regs */
: "rcx", "r11" // clobbered by syscall
);
// It doesn't matter what addressing mode "m"(my_char) picks,
// as long as it refers to the same memory as &my_char so the compiler actually does a store
return retval;
}
这在 Godbolt 编译器资源管理器上使用 gcc -O3非常有效地编译。
writechar:
movb %dil, -4(%rsp) # store my_char into the red-zone
movl $1, %edi
leaq -4(%rsp), %rsi
movl %edi, %edx # optimize because fd = len
syscall # dummy arg picked -4(%rsp)
ret
@nrz 的测试比该答案中的不安全(红色区域破坏)版本更main
有效地内联它,利用了大多数寄存器未修改的事实,因此它可以只设置一次。syscall
main:
movl $97, %r8d # my_char = 'a'
leaq -1(%rsp), %rsi # rsi = &my_char
movl $1, %edx # len
.L6: # do {
movb %r8b, -1(%rsp) # store the char into the buffer
movl %edx, %edi # silly compiler doesn't hoist this out of the loop
syscall #dummy arg picked -1(%rsp)
addl $1, %r8d
cmpb $123, %r8b
jne .L6 # } while(++my_char < 'z'+1)
movb $10, -1(%rsp)
syscall #dummy arg picked -1(%rsp)
xorl %eax, %eax # return 0
ret
readchar(),以同样的方式完成:
int readchar(void)
{
int retval;
unsigned char my_char;
asm volatile("syscall #dummy arg picked %[dummy]\n"
/* outputs */
: "=a" (retval)
,[dummy]"=m" (my_char) // tell the compiler the asm dereferences &my_char
/* inputs: ssize_t read(int fd, void *buf, size_t count); */
: "D"(0), // RDI = fd=stdin
"S" (&my_char), // RDI = buf
"d"(1) // RDX = length
: "rcx", "r11" // clobbered by syscall
);
if (retval < 0) // -1 .. -4095 are -errno values
return retval;
return my_char; // else a 0..255 char / byte
}
调用者可以通过检查来检查错误c < 0
。