GCC 确定哪些操作数是输出还是来自冒号的输入,而不是从缺少 "=" 或 "+" 推断出来的。是的,这是多余的,但不,您对此无能为力。
您可以将多个冒号放在同一行,例如
asm("" ::: "memory") 是编写编译器屏障的常用方法。因此,包含必要的冒号并不痛苦。你可以写asm("..." :: "m"(input) );,如果这实际上是安全的,没有任何输出或破坏。
但是你通常需要告诉编译器你修改了一个寄存器,无论是输出操作数还是clobber,所以你经常需要所有三个冒号。或者,如果您不编写任何寄存器,通常是因为您正在包装一些“系统”指令,这些指令具有某种效果,您通常不希望编译器重新排序内存访问。喜欢invlpg。或者例如:
asm("clflush %0" ::"m"(*ptr) : "memory");
应该有一个内存破坏器来在任何较早的存储(可能是同一行)之后命令缓存行刷新。
您的代码有一些语法错误,以及正确性/UB,至少如果您打算添加内存操作数,而不是常量0。
在 GNU C Extended Asm 中,模板字符串非常类似于 printf 格式字符串,供编译器替换您使用 %something 的操作数。 (是的,创建文本以提供给汇编器 as 只是一个愚蠢的文本替换。GCC 不“理解”你的 asm,这就是为什么你必须使用 input/output/clobber 向编译器准确地描述它约束)。
如果您需要文字 %,例如 AT&T 语法中的寄存器名称,例如 %rcx,您必须实际编写 %%rcx。
(通常最好避免硬编码寄存器;使用虚拟输出操作数让编译器选择要使用的寄存器。您甚至可以命名它们,例如%[input])
我假设您打算将内存源操作数添加到 RCX。那将是 %0。
$0 是立即数 0,即 RCX += 0,因此该指令实际上只修改 FLAGS。
假设您的意思是 add %0, %%rcx,您的代码会写入一个寄存器 (RCX)。 您必须告诉编译器您修改的寄存器/内存。否则它可能在 RCX 中有一个 C 变量,并期望在 asm 语句之后读取它的值。所以无论如何你都需要一个(虚拟)输出或一个clobber。
(如果您确实实际上是指"add $0, %%rcx",那么唯一的架构效果就是设置 FLAGS。i386 / amd64 的内联 asm 已经隐含了一个 "cc" clobber,所以我们没有告诉编译器该副作用。)
您对"add %0, %%rcx" 的安全选择包括:
使用破坏者:
asm ("add %0, %%rcx" // %0 expands the first operand, $0 was an immediate
:
: "Irm"(Example) /* input, also allow reg or 32-bit immediate */
: "rcx"
);
使用虚拟输出操作数(并将其设为volatile,就像没有输出操作数时隐含的那样)。请注意,我们可以省略 asm 语句的 : clobbers 部分。
uint64_t dummy;
asm volatile ("add %0, %%rcx"
: "=c"(dummy); // "c" forces picking cl/cx/ecx/rcx based on size
: "Irm"(Example)
);
// without volatile, the asm statement can be optimized away if you don't read dummy later
请注意,添加周围的push %%rcx ; pop %%rcx 并不安全:它会修改 RSP,这可能会影响编译器为 "m"(Example) 选择的寻址模式,并且它会踩到 RSP 下方的红色区域。
请参阅https://stackoverflow.com/tags/inline-assembly/info 了解更多信息。