你还没有说你正在为哪个处理器生成代码......我假设它是一台 RISC 机器,因为你的减法指令有 3 个操作数,但你没有说是哪一个。我将向您展示 x86 的外观,因为我知道答案是正确的。
我还假设您使用的asm volatile
是遵循gcc 标准的编译器。
无论如何,假设你有一个这样的结构:
struct mystruct {
unsigned char a;
const char *b;
int c;
};
int a_or_c(mystruct *str) {
int a_val = str->a;
return a_val & 1 ? str->c : (a_val >> 1);
}
编译器不会为此生成特别好的代码——它有七条指令,我们可以做得更好,因为我们知道一条指令可以同时测试“& 1”和右移。
要指定一个寄存器,你可以使用r
“register”,我相信你知道。要指定输入/输出寄存器,请使用+r
. 要指定像结构偏移量这样的常量,请使用i
“立即”。那么汇编器语法将是,例如:
int a_or_c_asm(mystruct *str) {
int a_val = str->a;
asm("shr $1,%0\n\t"
"cmovcl %c2(%1), %0"
: "+r"(a_val)
: "r"(str), "i"(offsetof(mystruct, c))
: "memory" // tell the compiler that our code reads from memory
);
return a_val;
}
这里的技巧是你必须使用%c2
而不是仅仅%2
让内联汇编器输出 a2
而不是$2
,因为 x86 汇编器在寻址模式中对偏移使用不同的语法,而不是直接操作数。x86 中的减法指令如下所示,例如:
asm("subq %0, %%rsp"
"... other instructions ..."
: // no output operands
: "i"(offsetof(mystruct, c)));
// expands to subq $16, %rsp for x86-64
根据您的评论,我假设您需要 ARM32 语法。为此,您的减法指令将如下所示:
asm("sub sp, %0"
"... other instructions ..."
: // no output operands
: "i"(offsetof(mystruct, c)));
// expands to sub sp, #8 for ARM
(强制Godbolt:为ARM正确编译和组装。)
(强制Godbolt:为x86正确编译和组装。)
请注意,gcc 假定堆栈指针在任何汇编块的末尾与在开始时相同;您的装配块的其余部分必须包含将 sp 恢复为其原始值的指令。
对于 ARM32,我的示例如下所示 - 请注意在 ARM32 中使用寄存器加偏移寻址模式的不同语法:
int a_or_c_asm(mystruct *str) {
int a_val = str->a;
asm("lsrs %0, #1\t\n" // shift and set flags
"ldrcs %0, [%1, %2]" // load (predicated on Carry Set)
: "+r"(a_val)
: "r"(str), "i"(offsetof(mystruct, c))
: "memory" // we access memory that isn't a declared input.
);
return a_val;
}
当然,这一切都是为了展示如何将已知的常量值传递给 gcc 的内联 asm 语法。更常见的替代方法是"m"(str->c)
使用用于 . 的输入操作数ldrcs %0, %1
,这样您就不必使用 offsetof 宏。
此外,"memory"
您可以传递一个虚拟输入操作数来告诉编译器该c
字段是一个输入,而不是使用 ,但实际上您仍然可以自己形成寻址模式;有关这方面的更多信息,请参阅 如何指示可以使用内联 ASM 参数*指向*的内存?