问题描述
我的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);
}
通过提交指向单个uint16_t
整数的低16位和高16位的uint32_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,7);
}
这一次它可以正常工作。
这是一个非易失性版本
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,(%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,%edx
movzwl (%rdi),%eax
subl %edx,(%rdi)
movzwl (%rdi),%eax
movzwl (%rdi),%edx
sall $7,%eax
shrw $9,(%rdi)
ret
.cfi_endproc
我不明白为什么函数的行为会有所不同。
您能帮助我了解原因是什么吗?
预期/意外结果的唯一区别是volatile
的用法。
删除任一volatile
都会导致意外结果。
解决方法
主要问题不太可能出在函数a_inverse()
本身,并且volatile
限定参数的事实会改变行为,这是偶然的情况,如果数据实际上不是不稳定的。
很可能,问题的根源实际上在呼叫者中。确实,在注释中,您描述了如何计算参数指针,如下所示:
uint32_t *whole = ...;
uint16_t *left = (uint16_t *) whole;
uint16_t *right = left + 1;
大概跟在后面的呼叫诸如
a_inverse(left,right);
。允许强制转换为uint16_t *
类型。指针算术的允许性是有争议的,但实际上这不太可能成为问题。但是由于生成的left
和right
指针实际上并不指向uint16_t
对象,因此尝试使用它们访问它们指向的对象违反了严格的别名规则(C18,第6.5 / 7),因此会产生不确定的行为。
严格的别名规则规定,只能通过兼容类型+/-限定和签名差异的左值(可能是联合)或字符类型的左值来访问对象。 (后者的主要含义是,您可以通过char *
访问任何对象的表示形式。)这使编译器可以使用数据类型分析来告知优化选择。
GCC有一个标志-fno-strict-aliasing
,它告诉它不要使用类型分析来做出优化决策-基本上是强迫它假定任何指针都可以别名。如果您不想解决此问题,那么使用该标志可能会减轻它。
另一方面,我怀疑所有对指针的滥用都是对优化的错误尝试。我将重写该函数,而无需:
uint32_t a_inverse(uint32_t whole) {
uint16_t left = whole; // x86 is little-endian
uint16_t right = whole >> 16;
right ^= left;
right = ROTL16(right,14);
left -= right;
left = ROTL16(left,7);
return right << 16 + left;
}
...,并希望它至少与之一样快,尤其是因为没有混淆风险会限制可用的优化。