在GCC手册的第6.43.2.5 Extended Asm - Clobbers节中,在“内存”修补程序的解释中,提到了避免刷新寄存器的技巧:
如果你知道在编译时访问的内存大小,你可以指定一个这样的内存输入:
{"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}
(假设您访问的是10个字节的字符串。)
我想我理解这个想法,但我不清楚如何使用它以及这个技巧有什么含义 - 除了向GCC提供更多类型信息。 最重要的是出现了三个问题:
volatile
限定符?
我想我可以,因为它会被声明为输出。因为我们喜欢这样的例子:这段代码是否有意义并且合法吗?它似乎有效。
#include <iostream>
#include <cstdint>
void add_assembly(std::uint64_t * x) {
struct memory { std::uint64_t data[2]; } * p = reinterpret_cast<memory*>(x);
__asm__ (
"addq $1, %[x] \t\n"
"addq $5, 8%[x] \t\n"
: [x] "+m" (*p) // Bonus question: Why don't I need a "&" here?
: "m" (*p)
: "cc"
);
}
int main() {
std::uint64_t x[2];
x[0] = 3000;
x[1] = 7253;
std::cout << "before: " << x[0] << " " << x[1] << std::endl;
add_assembly(&x[0]); // add 1 to x[0], add 5 to x[1]
std::cout << "after: " << x[0] << " " << x[1] << std::endl;
return 0;
}
答案 0 :(得分:1)
我对这个答案中的所有内容都不是100%肯定,但我最近自己也看过这个问题。
我是否也可以将其用作输出或输入/输出操作数并修改数据?
是的,但您可能会因单独的r
(注册)或m
(可能是复杂的寻址模式)约束而感觉更好。这是特别的。如果要在循环中递增指针,则为true,因此需要在寄存器中使用它。 m
约束可以使%0
扩展为(%rsi, %rdx, 4)
或其他内容。
如果我使用这个技巧,我还需要&#34;内存&#34;揍?
没有。这告诉gcc哪些内存可能被修改。 "memory"
表示所有内存都可能被修改。
我是否可以安全地删除仅访问内存时所需的
volatile
限定符?
你的意思是asm volatile (...)
?是。仅当周围的代码不使用其输出(或者它没有任何输出)时,才需要volatile
来使gcc不移动或消除asm
语句。只要你告诉gcc你的asm在内存中产生的结果,并使用那个结果,就不应该使用volatile
,所以gcc可以像任何其他黑盒函数一样进行优化。 (当然,假设你的asm是一个纯粹的&#34;函数,只取决于它所声明的输入。)
例子很好。 :)你的看起来不错。
我认为你指定一个读写输入操作数是不够的;你需要指定它两次,作为输入和输出。 +
约束的措辞使我认为&#34;阅读和写作&#34;意味着gcc将您留在其中的值视为变量的新值,但我认为情况并非如此。
我认为在x86上,m
约束等同于o
(可抵消)约束。但要注意语法;你不希望8%[x]
变成88(%rsp)
。也许8 + %[x]
?不确定。留一个空格,所以它是一个语法错误而不是一个不同的数字。
\t\n
很傻。您希望选项卡位于每行的开头。
asm (
"inc %[x] \n\t" // inc is shorter than `add`
"addq $5, 8 %[x] \n\t" // this `add` writes all flags, preventing partial-flag stalls / slowdowns
: [x] "+m" (*p) // Bonus question: Why don't I need a "&" here?
// because you aren't writing a temporary to the output before reading the input
: "m" (*p)
: "cc"
);