我有很多麻烦使它工作:
我尝试了以下方法:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntaxn"
"xor eax, eax n"
"inc eax n"
"myloop: n"
"shr %0, 1 n"
"adc eax, eax n"
"jnc short myloop n"
"mov %1, %0 n"
: [i] "=r"(i), [n] "=r"(n));;
return n;
}
我将得到:
Line 11: Char 14: error: unknown token in expression
"shr %0, 1 n"
^
<inline asm>:5:5: note: instantiated into assembly here
shr %edx, 1
^
显然编译器将%0
替换为%register
,但仍然保留'%'
…
因此我决定用edx
代替%0
,用ecx
代替%1
:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntaxn"
"xor eax, eax n"
"inc eax n"
"myloop: n"
"shr edx, 1 n"
"adc eax, eax n"
"jnc short myloop n"
"mov ecx, edx n"
: [i] "=r"(i), [n] "=r"(n));;
return n;
}
并获取结果错误:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==31==ERROR: AddressSanitizer: SEGV on unknown address 0x0001405746c8 (pc 0x00000034214d bp 0x7fff1363ed90 sp 0x7fff1363ea20 T0)
==31==The signal is caused by a READ memory access.
#1 0x7f61ff3970b2 (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
AddressSanitizer can not provide additional info.
==31==ABORTING
我怀疑编译器优化了东西并内联了调用的函数(所以不是ret),但仍然不知道我该怎么做。
注意:我不能将编译器从clang更改为gcc,因为它不是我,而是使用clang 11的远程服务器。我也读过这个链接,但它很老了(2013年),如果从那时起事情没有改变,我会感到惊讶。
编辑:在Peter Cordes的精彩回答之后,我能够使它更好地工作:
uint32_t reverseBits(volatile uint32_t n) {
uint32_t i = n;
__asm__ (".intel_syntax noprefixn"
"xor rax,rax n"
"inc rax n"
"myloop: n"
"shr %V0, 1 n"
"adc eax, eax n"
"jnc short myloop n"
"mov %V0, rax n"
".att_syntax"
: [i] "=r"(i));;
return i;
}
然而有两件事:
1/我不得不将eax
改为rax
,因为%V0
需要64位(r13
),这很奇怪,因为i
应该只占32位(uint32_t)。
2/我没有得到想要的输出:
input is : 00000010100101000001111010011100
output is: 93330624 (00000101100100000001110011000000)
expected: 964176192 (00111001011110000010100101000000)
NB:我测试了"mov %V0, 1 n"
,正确地得到1
作为输出,这证明了替换在某种程度上是有效的。
我不知道有什么好的方法可以做到这一点,我建议在GNU C内联asm中使用AT&T语法(或方言替代add {%1,%0 | %0,%1}
,因此它可以在GCC中以两种方式工作)。像-masm=intel
这样的选项不会像GCC那样让clang代入裸寄存器名。
(更新:clang 14更改:如何设置gcc或clang永久使用内联asm()语句的英特尔语法?)
如何生成汇编代码与clang在英特尔语法?是关于-S
输出使用的语法,与GCC不同的是,它没有连接到编译器的内联asm输入的语法。--x86-asm-syntax=intel
的行为没有改变:它仍然以Intel语法输出,并且不能帮助您使用内联asm。
您可以滥用%V0
或%V[i]
(而不是%0
或%[i]
)来打印"naked"模板中的完整注册名https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers,但这很糟糕,因为它只打印完整的注册名。即使对于选择EAX的32位整型,它也会打印RAX而不是EAX.
(它也不能为"m"
内存操作数获得dword ptr [rsp + 16]
或任何编译器的寻址模式选择,但这总比没有好。虽然在我看来,这并不比仅仅使用AT&T语法更好。)
或者您可以选择硬寄存器,如"=a"(var)
,然后只是明确地使用EAX而不是%0
。但这更糟,并且抵消了约束系统的一些优化好处。
在模板中仍然需要".intel_syntax noprefixn"
,并且应该以".att_syntax"
结束模板,将汇编器切换回AT&T模式,以汇编以后编译器生成的asm。(如果你想让你的代码与GCC一起工作,这是必需的!)Clang的内置汇编器在汇编之前不会将您的内联asm文本合并到一个大的asm文本文件中,它直接将编译器生成的指令转换为机器码。
显然告诉编译器它可以选择"=r"
的任何寄存器,然后实际上使用您自己的硬编码选择,当编译器选择不同时,将创建未定义的行为。您会踩到编译器的脚趾,破坏它以后想要使用的值,并让它从错误的寄存器中获取垃圾作为输出。我不知道你为什么要在你的问题中提到这一点;对于AT&T语法,由于同样相当明显的原因,会以完全相同的方式中断。