#include <stdio.h>
int square (int n) {
__asm__("mov %eax, n"
"mul %eax");
}
int main(void) {
printf("\nSquare of 4 is %i", square(4));
/* Calling square gives Segmentation fault: 11 error */
return 0;
}
当我在装有Mac OS X 10.7和gcc-5.0.0的iMac(Core 2 Duo)上编译此代码时:gcc -o assem -DDEBUG=9 -ansi -pedantic -Wall -g assem.c
编译时出现警告:
assem.c: In function ‘square’:
assem.c:6:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
assem.c:4:Can't relocate expression. Absolute 0 assumed.
Compilation finished at Mon Jul 25 18:23:47
当我运行它时,它给出Segmentation fault: 11
如何修复?
- 注意 *:我浏览了大约10个关于
Segmentation fault: 11
、assembly
和inline-assembly
的问题,没有一个有帮助。
更新
当我将内嵌组件变更为:asm ("imul %0, %0" : "+r"(n)); return n;
编译器给出以下错误:
assem.c: In function ‘square’:
assem.c:4:1: warning: implicit declaration of function ‘asm’ [-Wimplicit-function-declaration]
asm ("imul %0, %0" : "+r"(n));
^
assem.c:4:20: error: expected ‘)’ before ‘:’ token
asm ("imul %0, %0" : "+r"(n));
^
assem.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
Compilation exited abnormally with code 1 at Mon Jul 25 18:46:49
当我将程序集更改为asm ("imul %0, %0" : "+r"(n));
时,编译器给出了与上面类似的错误。
更新2(2022年7月25日)
为了解决这个问题而不彻底改变square
函数,我从Peter Cordes的注解中复制了一部分代码,并将其作为clang
版本:
#include <stdio.h>
int quadrat (int n) {
asm { mov eax, n / imul eax,eax / mov n, eax / };
}
int main(void) {
printf("\nSquare of 4 is %i\n", 4*4);
return 0;
}
我的Mac OS X上确实有clang-3.7.1
:
>clang -v
clang version 3.7.1 (tags/RELEASE_371/final)
Target: x86_64-apple-darwin11.4.2
Thread model: posix
我尝试使用以下代码编译它:
clang -fasm-blocks ass-clang.c
- 注意 *:我通常不会使用
clang
代码未编译:
ass-clang.c:4:20: error: unexpected token in argument list
asm { mov eax, n / imul eax,eax / mov n, eax / };
更新#3(特定于奖励问题)
如何修复此代码
int square (int n) {
__asm__("mov %eax, n"
"mul %eax");
}
而不改变它的基本结构?也就是说,n
将被移动到eax
(或任何其他寄存器,如果需要),则该寄存器的值将与其自身相乘,最好使用mul
命令,最后返回结果,最好不使用return
命令。换句话说,我需要修复代码,不是重写。例如,我认为这是重写:
asm ("imul %0, %0" : "+r"(n)); return n;
此外,这种重写并不直观。:
是什么?"+r"
在那里做什么,它是分配Unix读取权限吗:)
1条答案
按热度按时间hc8w905p1#
最好不使用返回命令。
如果你想让你的C代码返回一个值,它将需要使用
return
。这就是C的工作原理。这就是为什么Peter建议这样做,使用clang -fasm-blocks
来允许MSVC的内联asm风格。它使用with clang on Godbolt进行编译,将n
存储到堆栈的低效asm,以便它可以成为asm块中的内存操作数(因为内联asm requires that inefficiency的这种样式)。如果你是用纯asm语言写这段代码的话,“eax”寄存器是用来保存函数的返回值的。所以你可以“欺骗”你的方式不使用C
return
语句,但前提是你告诉编译器你将自己处理从函数返回的行为。这就是Peter建议使用__attribute__((naked))
时所说的。此属性通知编译器,它应该假定所有内容都由函数中的汇编程序代码100%处理(包括返回值和实际返回)。这就是他所说的,当他说你可以省略Cx 1 m5n1x语句,而在你填充了eax之后只使用x86
ret
指令。如果你把一个函数标记为naked,您 * 不能 * 使用return语句。函数必须只包含asm,而不是C代码(参见docs)。编译器完全不干涉,所以当asm语句或块获得控制权时,可以确保RSP指向返回地址。但这也意味着不能在asm中使用命名的局部变量或参数。因此您必须自己实现调用约定。本质上,
naked
意味着你在汇编中写一个函数,但是使用C编译器给予它一个名字和原型,并使它成为你C源文件的一部分。我不推荐这种方法。如果你想写一个汇编函数,在汇编中写一个汇编函数,并将它链接到你的C代码。试图将两者硬塞在一起通常只会导致混乱。但是
naked
允许你省略return
语句,使用ret
指令,如果出于某种原因这是必要的。什么是
:
?如果你读过docs(你读过docs,对吗?),你会看到什么:
事实上,@fuz只使用了一个冒号,这意味着他没有只输入的约束,也没有使用乱码。
那个“+r”是怎么回事
看一下docs,
"r"
表示“在调用asm指令之前将值移入寄存器”。+
表示正在更新值(与=
相反,=
表示“写入但不读取”)。嵌套将被移动到eax(或任何其他寄存器,如果需要话)
通过写
"+r"(n)
,我们已经将值从n
移到了寄存器中,不再需要在模板中包含mov
指令。由于n
可能已经在前面的代码中的寄存器中,这可能节省了额外的mov
指令。它将选择哪个寄存器?因为我们没有指定,编译器将选择最有效的寄存器。因为我们不知道将选择哪个寄存器(并且它可能在不同的编译中改变),我们使用
%0
来引用第一个约束,%1
引用第二个约束(如果我们有一个的话),等等。最好使用穆尔命令
这样做有几个问题。mul指令要求使用eax寄存器。如果编译器将eax用于其他用途,则强制编译器显式使用eax可能会导致代码效率降低。
但更重要的是,
mul
使用2个寄存器。当您将两个寄存器相乘时,最大可能的结果是宽度的两倍。mul
是一个扩展乘法,它将输出放入edx:eax
。你的原始代码没有为这么大的值做任何准备。但是你不能忽略
edx
寄存器正在被改变的事实。如果你不告诉编译器你正在改变那个寄存器的内容,它是不会知道的。它怎么会不知道呢?毕竟,'穆尔'指令就在那里,对吗?来自文档:GCC本身并不解析汇编指令,也不知道它们的含义,甚至不知道它们是否是有效的汇编输入。
也就是说,如果在不通知编译器的情况下更改寄存器(通过输出约束或乱码),如果编译器将其用于其他用途,则可能会出现大混乱。相比之下,具有多个操作数的
imul
只输出单个寄存器,这与原始代码更加一致。(不需要非加宽mul
,因为无论输入被视为有符号还是无符号,完全乘法的低半部分都是相同的。)例如我认为这是重写:
我不确定我同意。
如果你想把值移到寄存器中,
"+r"
会帮你完成。如果你坚持的话,你可以使用mul
和imul
,但是你会迫使编译器在eax
之外为你释放edx
寄存器。并且运行一条较慢的指令(更多的微操作用于写第二个寄存器)。为什么要降低代码的效率呢?选择是“重写”
edx
寄存器还是使用imul
。哪一个重写更小?此外,这种改写是不直观的。
好吧,现在你抓到我了。
编写内联asm是 * 困难 * 的(这就是为什么我建议您使用don't do it)。
但这种“扩展的asm”方法是gcc、clang、intel等公司都采用的方法。(毫不奇怪)走了一条不同的路。但他们的解决方案结果是如此困难(或者在他们的编译器中无法维护地实现),他们决定完全不支持64位代码的内联asm。(将汇编语言与C结合使用的最复杂的方法),您需要了解它是如何工作的。
Fuz的方法生成的代码效率最高。它还允许代码被内联,这是这里描述的大多数其他方法所不能做到的。它使用的寄存器数量最少,使这些宝贵的资源可供编译器用于其他目的。
总之,我不知道如何找到一个比这更干净、更高效的内联asm解决方案:
比较一下它的编译方式和MSVC风格的内联asm版本的编译方式,在Godbolt上使用clang 14:
第一个
顺便说一句,MSVC文档支持在
asm{}
块的末尾在EAX中保留一个值,然后在没有Cx 1 m36n1x语句的情况下从函数末尾退出。(即使内联一个包含asm{}
块的函数时,这在MSVC中也是有效的。(可能有足够多的程序员滥用了“碰巧有效”,MS将其正式化)。这降低了从asm{}块中 out 获取结果的效率,但对存储/重新加载获取值没有帮助。但是在
clang -fasm-blocks
中,它只是碰巧工作,在内联时中断。