【发布时间】:2019-10-19 07:08:49
【问题描述】:
考虑以下小函数:
void foo(int* iptr) {
iptr[10] = 1;
__asm__ volatile ("nop"::"r"(iptr):);
iptr[10] = 2;
}
使用 gcc,this compiles to:
foo:
nop
mov DWORD PTR [rdi+40], 2
ret
特别注意,第一次写入iptr,iptr[10] = 1根本不会发生:内联asm nop是函数中的第一件事,只有2的最后一次写入出现(在 ASM 调用之后)。显然编译器决定它只需要提供iptr 的值的最新版本本身,而不是它指向的内存。
我可以用memory clobber 告诉编译器内存必须是最新的,如下所示:
void foo(int* iptr) {
iptr[10] = 1;
__asm__ volatile ("nop"::"r"(iptr):"memory");
iptr[10] = 2;
}
这会产生预期的代码:
foo:
mov DWORD PTR [rdi+40], 1
nop
mov DWORD PTR [rdi+40], 2
ret
但是,这是一个太强的条件,因为它告诉编译器所有内存必须被写入。例如,在以下函数中:
void foo2(int* iptr, long* lptr) {
iptr[10] = 1;
lptr[20] = 100;
__asm__ volatile ("nop"::"r"(iptr):);
iptr[10] = 2;
lptr[20] = 200;
}
期望的行为是让编译器优化第一次写入lptr[20],而不是第一次写入iptr[10]。 "memory" clobber 无法实现这一点,因为这意味着两个写入都必须发生:
foo2:
mov DWORD PTR [rdi+40], 1
mov QWORD PTR [rsi+160], 100 ; lptr[10] written unecessarily
nop
mov DWORD PTR [rdi+40], 2
mov QWORD PTR [rsi+160], 200
ret
有没有办法告诉接受 gcc 扩展 asm 语法的编译器,asm 的输入包括指针和它可以指向的任何东西?
【问题讨论】:
-
@HadiBrais - 是的,但这只是
=的一个副作用,这意味着asm 可能会更改指针值,因此gcc必须进行两次写入(因为他们可能会写入到不同的位置)。但是,这并不意味着 gcc 必须在调用内联 asm 之前执行 write before(尽管在这种情况下发生了这种情况),因此它通常不起作用(您可以构建一个类似的示例失败的地方)。 -
另外,你不想
"+r"吗?我认为使用"=r"甚至不需要编译器将指针值传递给asm? -
你想要 "具体来说,一个 "m" (*(const float (*)[]) fptr) 会告诉编译器整个数组对象是一个输入,任意长度." 部分来自answer to the linked duplicate。
-
确实如此。但是,如果答案有帮助,那么我想将其标记为重复就可以了。将来可能会发现您的问题的访问者将被指出正确的方向。还是您提出其他解决方案?我们也可以请彼得在这里发布答案,以便他获得学分。
-
@Jester:我认为我们可以使用关于这个主题的独立规范问答。我一直计划在某个时候写一个简单的例子来显示不需要的死存储消除。将它放入我的循环数组答案只是权宜之计。我们想要一个问题,其标题概括了问题,问题正文演示了问题,答案显示了当前最佳实践的虚拟内存输入或输出操作数,并使用 cast-to-array 语法。这对于通过链接向人们显示问题存在是有好处的。
标签: c gcc assembly clang inline-assembly