有人提到,一个编写良好的 C 编译器应该在编译时(即不是运行时)执行移位运算符;例如,在此代码中左移 - <<. Can anyone attest to the validity of this?
代码:
constant unsigned int elements = length/8 + (length % y > 0 ? 1 : 0);
unsigned char bit_arr[elements];
伪代码:
bit_arr[i] |= (1 << j); // Set
bit_arr[i] &= ~(1 << j); // Unset
if( bit_arr[i] & (1 << j) ) // Test
你到底想问什么? 您的意思是“编译器会自行进行转换”吗? 如果这就是您所问的问题,答案是“这取决于”:)。 如果要移位的数字和移位的大小都是编译时常量,则编译器几乎肯定会进行移位(尽管不是必须这样做)。 否则,它将生成执行移位的较低级别代码(通常是单个机器指令)。
任何符合 C 标准的编译器都将提供移位运算符
<<
>>
,如标准第 §6.5.7 节中所指定。它们可以毫无问题地处理整数表达式(无论它们是否为常量)1)。但是,在负整数上使用它们时要小心,因为标准不强制要求符号传播(实际上,带有负左操作数的左移是未定义的行为,而右移是实现定义的)。此外,带符号整数的溢出行为是未定义的。
每个操作数应为整数类型。
所有这些东西都不再相关了,因为问题完全改变了。
就其价值而言,编译器可以而且通常会在所有操作数已知(并且启用优化)的情况下在编译时执行计算。
编译器没有义务将您的程序逐字(或您期望的方式)翻译成汇编语言或机器语言。 只要行为在语言标准的范围内相同,编译器就可以自由地翻译您的程序。 如果您的程序调用未定义的行为、特定于平台的行为或编译器定义的行为,那么一切都将失败。
编译器通常可能不会使用常量进行代码转换。 他们可能会在编译期间计算值并加载值。 他们可能会选择乘法或除法而不是移位。
更智能的编译器可能会评估您的表达式并使用更有效的公式来生成答案。 另一方面,编译器也可能会消除未使用答案的班次。
我建议关注程序的正确性和鲁棒性,而不是担心编译器如何优化代码。 当您的程序正确运行并且稳定后,如果您有额外的时间或者程序的大小或执行速度不令人满意,请进行优化。
编译器实现的转变的一个示例是以下示例(具有未定义的行为):
#include <stdio.h>
int main(void) {
const int i = 32;
printf("%d %d\n", 1 << 32, 1 << i);
return 0;
}
使用 Cygwin 上的 gcc 4.3.4,
gcc foo.c; ./a
给出 0 1
,而不是预期的 0 0
。 (gcc会给出编译器警告,并且使用-O3
,你确实得到了预期的结果,但有两个警告。)有关原因,请参阅SOGCC左移溢出。
如果编译器可以在编译时确定操作数的值,并且以适当的优化级别调用它,则它可能会在编译时执行该操作。
将变量标记为
const
可能有助于编译器进行该确定。在某些情况下,编译器也可以通过数据流分析自行完成此操作(例如:初始化为常量且从未修改的局部变量,...)。
j 是否依赖于 a 恒定与可变?
当然有。编译器如何在编译时计算结果,该结果取决于直到运行时才知道的操作数?
无论谁向您“提到”这一点,要么是被误导了,要么没有说出您所理解的内容。
每个 C 编译器都必须实现移位运算符,因为它们是语言的一部分。 j 没有任何限制。
编辑:既然你完全改变了问题:是的,为了优化,当然j需要是一个编译时常量,否则,编译器应该如何知道它在编译时的值,但是用常量操作数优化算术指令,是优化代码时首先要做的事情之一。实际上,我认为,即使没有激活优化级别,VC 和 gcc 也会这样做,因为这是显而易见的。