我正在尝试在Linux(x86)上学习GCC内联汇编,我的第一个实验是尝试实现乘法的整数溢出检测。这似乎很容易,但它有副作用,我不明白。
所以,这里我想将两个无符号8位整数相乘,看看结果是否溢出。基本上我只是将第一个操作数加载到AL寄存器中,将另一个操作数加载到BL寄存器中,然后使用mul
指令。结果存储为AX寄存器中的16位值。然后我将AX寄存器中的值复制到我的C变量b
,除非它溢出。如果它溢出,我将c
设置为1。
uint8_t a = 10;
uint8_t b = 25;
uint8_t c = 0; // carry flag
__asm__
(
"clc;" // Clear carry flag
"movb %3, %%al;" // Load b into %al
"movb %2, %%bl;" // Load a into %bl
"mul %%bl;" // Multiply a * b (result is stored in %ax)
"movw %%ax, %0;" // Load result into b
"jnc out;" // Jump to 'out' if the carry flag is not set
"movb $1, %1;" // Set 'c' to 1 to indicate an overflow
"out:"
:"=m"(b), "=m"(c) // Output list
:"ir"(a), "m"(b) // Input list
:"%al", "%bl" // Clobbered registers (not sure about this)
);
这似乎工作正常。如果我printf
'b'的值我得到250,这是正确的。此外,如果我将'b'的起始值更改为26,则在将乘法c
设置为1之后,当然指示溢出(10 * 26> ~uint8_t(0))。我看到的问题是C变量a
在乘法后设置为0(或溢出时为1)。我不明白为什么a
会被任何我改变在这里做它甚至不在输出变量列表中,为什么我的汇编例程会影响a
的值?
另外,我不确定被破坏的寄存器列表。该列表应该告知GCC有关在汇编例程中使用的任何寄存器,以便GCC不会尝试错误地使用它们。我想我需要告知GCC我使用了AL和BL寄存器,但AX寄存器怎么样?它隐式用于存储两个8位整数的乘积,所以我是否需要将它包含在被破坏的寄存器列表中?
答案 0 :(得分:7)
我遇到的问题是乘法后C变量
a
设置为0(或者 溢出。)我不明白为什么a
会被我在这里做的任何东西改变。不是 即使在输出变量列表中,为什么我的汇编例程会影响a
的值?
mul %%bl
将AL(8位)乘以BL(8位),将结果放在AX(16位)中。
请注意,AL和AX不是单独的寄存器:AL只是AX的底部8位。
movw %%ax, %0
将AX(16位)存储到b
的地址......这是uint8_t
。因此,该指令也会用结果的前8位覆盖存储器中的下一个字节。在这种情况下,该字节恰好是存储a
的值的位置(这解释了为什么a
在没有溢出时被0覆盖的原因,以及在它溢出时为1的情况)。
您需要将其替换为movb %%al, %0
,以便只对结果的最后8位进行字节存储。
我想我需要告知GCC我使用了AL和BL寄存器,但AX寄存器怎么样?它的 隐式使用来存储两个8位整数的乘积,所以我需要将它包含在列表中 破坏寄存器?
是的 - 您应该告诉GCC您更改了值的任何寄存器(并且,正如另一个答案中指出的nategoose,您可能应该告诉它您也在更改标志)。所以这里的clobber列表应该是"%ax", "%bl", "cc"
(AX包含AL,因此您不需要明确提及AL。)
答案 1 :(得分:2)
您应该使用-S
选项编译代码,并查看* .s文件。所有的程序集都在用分号分隔的同一行上,我相信分号在gnu汇编程序中开始注释。您需要在所有汇编指令的末尾添加“\ n”(或更好的“\ n \ t”)。
您可能还想将“cc”添加到clobber列表中。
此外,GCC有一种方法可以指定输入既可以是您可能感兴趣的汇编输入也可以是输出。
此外,最好让GCC决定输入的位置,而不是强制它们在内存中。我似乎记得GCC的内联汇编对x86的约束意味着“无论是在寄存器中还是在内存中”,它可以用于无关紧要的情况,但你可能不应该有很多“mov”指令因为GCC最大的工作之一是在实际计算指令之间找出最好的移动指令集,所以你的内联汇编的开始和结束。例如,在你的代码中,GCC要做的最好的事情就是将常量10和25存储在你用来开始的寄存器中。