【发布时间】:2020-09-11 00:03:56
【问题描述】:
我有一个相对较小的 C 函数。
#define ROTL16(x, r) (((x) << (r)) | (x >> (16 - (r))))
void a_inverse(uint16_t *left, uint16_t *right) {
*right ^= *left;
*right = ROTL16(*right, 14);
*left -= *right;
*left = ROTL16(*left, 7);
}
通过提交指向单个 uint32_t 整数的低 16 位和高 16 位的 uint16_t 指针调用该函数。
uint32_t whole;
uint16_t * left = (uint16_t *)&whole;
uint16_t * right = left + 1;
a_inverse(left, right);
但该功能未按预期执行。 所以我一味加了volatile关键字。
#define ROTL16(x, r) (((x) << (r)) | (x >> (16 - (r))))
void a_inverse(volatile uint16_t *left, volatile uint16_t *right) {
*right ^= *left;
*right = ROTL16(*right, 14);
*left -= *right;
*left = ROTL16(*left, 7);
}
这次它可以正常工作了。
将输出程序集与 -O3 进行比较,我没有发现任何问题
这是一个非易失性版本
a_inverse:
.cfi_startproc
endbr64
movzwl (%rsi), %ea
xorw (%rdi), %ax
rorw $2, %ax
movw %ax, (%rsi)
movzwl (%rdi), %edx
subl %eax, %edx
movl %edx, %eax
rolw $7, %ax
movw %ax, (%rdi)
ret
.cfi_endproc
这是带有volatile关键字的那个
a_inverse:
.cfi_startproc
endbr64
movzwl (%rdi), %edx
movzwl (%rsi), %eax
xorl %edx, %eax
movw %ax, (%rsi)
movzwl (%rsi), %eax
movzwl (%rsi), %edx
sall $14, %eax
shrw $2, %dx
orl %edx, %eax
movw %ax, (%rsi)
movzwl (%rsi), %edx
movzwl (%rdi), %eax
subl %edx, %eax
movw %ax, (%rdi)
movzwl (%rdi), %eax
movzwl (%rdi), %edx
sall $7, %eax
shrw $9, %dx
orl %edx, %eax
movw %ax, (%rdi)
ret
.cfi_endproc
而且我不明白为什么这些函数的行为不同。
你能帮我理解可能是什么原因吗?
预期/意外结果的唯一区别是那些 volatile 用法。
删除任一volatile 都会导致意外结果。
【问题讨论】:
-
您是如何确定“功能未按预期执行”的?
-
您确定
left和right是相邻的并且不会意外重叠吗?你如何传递输入?确保+ 1不是字节。 -
那么你有一个严格的混叠违规,结果是未定义的行为。这可能会或可能不会解释差异,但由于您使用的是 GCC,您可以尝试使用
-fno-strict-aliasing编译第一个版本,看看它是否会有所不同。 -
我倾向于猜测问题实际上出在调用者身上。编译器(没有
-fno-strict-aliasing)可能会根据类型分析假设对a_inverse()的调用无法修改whole的值,并根据正确性执行优化。 -
您需要提供一个完整的程序来重现您所看到的结果,特别是minimal reproducible example,然后任何人才能正确回答您的问题。
标签: c assembly gcc x86 strict-aliasing