nal*_*zok 26 c casting bitwise-operators
int v, sign;
// or, to avoid branching on CPUs with flag registers (IA32):
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then -1, else 0.
Run Code Online (Sandbox Code Playgroud)
此语句为变量符号指定变量v的符号(-1或0).我想知道为什么(int)((unsigned int)((int)v)用而不是普通的v?
Jon*_*ler 32
请注意,您已经在问题中提取了表达式的片段(您引用的(int)((unsigned int)((int)v)左括号(比右括号多一个)).赋值语句的RHS表达式是完整的:
-(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
Run Code Online (Sandbox Code Playgroud)
如果添加一些空格,您会发现:
-(int) ( (unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1) );
^ ^ ^^ ^ ^ ^ ^
| +------------++------+ +--------------------------+ |
+----------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)
也就是说,外部(int)演员适用于所有:
((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
Run Code Online (Sandbox Code Playgroud)
铸造的内部铸件(int)是空的; 它的结果立即转为unsigned int.的(unsigned int)铸造确保了右移被很好地定义.表达式作为一个整体确定最高有效位是0还是1.外部int将结果转换回a int,然后将-其否定,因此表达式为-1if v为负且0if v为零或正 - 这是什么评论说.
chq*_*lie 20
引用C标准6.5.7p5:
E1 >> E2的结果是E1右移E2位位置.如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1/2E2的商的整数部分.如果E1具有带符号类型和负值,则结果值是实现定义的.
作者正在撰写如何实现一个函数sign(int v),该函数返回-1负数,有效返回00和正数.一个天真的方法是:
int sign(int v) {
if (v < 0)
return -1;
else
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但是,此解决方案可以编译为执行比较的代码,并对由比较设置的CPU标志进行分支.这是低效的.他提出了一个更简单,更直接的解决方案:
sign = -(v > 0);
Run Code Online (Sandbox Code Playgroud)
但是这个计算仍然需要对不直接作为布尔值产生比较结果的CPU进行比较和分支.具有标志寄存器的CPU通常在比较指令或甚至大多数算术指令时设置各种标志.所以他提出了另一种基于移位符号位的解决方案,但正如上面标准所规定的那样,他不能依赖于右移右值的结果.
转换v为unsigned删除此问题,因为正确指定了右移无符号值.假设符号位处于最高位置,对于所有现代处理器都是如此,但不是由C标准强制要求,右移(unsigned)v一个小于其类型中的位数的值会产生1负值的值,0否则.否定结果应产生-1负值v和0正值和零的预期值v.但是表达式是无符号的,因此明确的否定将产生UINT_MAX或者0,当存储到一个int或者甚至只是作为一个时,它会导致算术溢出(int).将此结果转换回int正确否定之前计算所需的结果,-1对于负数v和0正数或零v.
算术溢出通常是良性的,并且被大多数程序员广泛忽略,但是现代编译器倾向于利用其未定义来执行积极的优化,因此依赖于预期但无根据的行为是不明智的,并且最好在所有情况下避免算术溢出.
表达式可以简化为:
sign = -(int)((unsigned)v >> (sizeof(int) * CHAR_BIT - 1));
Run Code Online (Sandbox Code Playgroud)
请注意,如果将右移定义为复制平台的位(当前CPU几乎是通用的行为),则表达式将更加简单(假设int v):
sign = v >> (sizeof(v) * CHAR_BIT - 1)); // works on x86 CPUs
Run Code Online (Sandbox Code Playgroud)
bithacks页面https://graphics.stanford.edu/~seander/bithacks.html,非常有启发性,包含一个详细的解释:
int v; // we want to find the sign of v
int sign; // the result goes here
// CHAR_BIT is the number of bits per byte (normally 8).
sign = -(v < 0); // if v < 0 then -1, else 0.
// or, to avoid branching on CPUs with flag registers (IA32):
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
// or, for one less instruction (but not portable):
sign = v >> (sizeof(int) * CHAR_BIT - 1);
Run Code Online (Sandbox Code Playgroud)
对于32位整数,上面的最后一个表达式求值为sign = v >> 31.这是一种比显而易见的方式更快的操作,sign = - (v <0).这个技巧有效,因为当有符号整数向右移位时,最左边的位的值被复制到其他位.当值为负时,最左边的位为1,否则为0; 所有1位给出-1.不幸的是,这种行为是特定于架构的.
作为结语,我建议使用最易读的版本并依赖编译器来生成最有效的代码:
sign = -(v < 0);
Run Code Online (Sandbox Code Playgroud)
正如在这个有启发性的页面上可以验证的那样:http://gcc.godbolt.org/# 编译上面的代码gcc -O3 -std=c99 -m64确实为上面的所有解决方案生成了下面的代码,甚至是最天真的if/ else声明:
sign(int):
movl %edi, %eax
sarl $31, %eax
ret
Run Code Online (Sandbox Code Playgroud)
Tom*_*zes 10
它首先是铸造int,然后是unsigned int,然后它正在执行转移,然后它会重新投入int,最后它会否定结果并存储它sign.无符号转换是可能影响结果的转换,因为它将强制逻辑移位(将填零),而不是算术移位(将签名扩展).
请注意,他们实际上想要算术移位,但我不相信C保证其可用性,这可能是为什么他们手动执行逻辑移位符号位的否定.