现代编译器(或者也许自 C89 以来就已经存在)是否会在条件表达式求值期间用短路求值代码来替代下面的情况?
char mystring[32] = "this is a long line";
if((strnlen(mystring, 32)) > 2)
{
return 1;
}
Run Code Online (Sandbox Code Playgroud)
因为在处理过程中考虑了右操作数strnlen(...),并且当内部C字符串的运行长度strnlen(...)超过外部条件表达式的右操作数(在本例中为2)时,strnlen(...)会爆发?
<?也许,取决于编译器。让我们看一些使用 gcc 13.2.0 和 clang 17.0.1 编译的示例,它们均处于优化级别-O3并启用了扩展(请注意,这strnlen是 POSIX,而不是标准 C)。
int p() {
char mystring[32] = "this is a long line";
return strnlen(mystring, 32) > 2;
}
Run Code Online (Sandbox Code Playgroud)
clang 和 gcc 都将其优化mov eax, 1; ret为. 这是因为他们知道调用的行为strnlen并且可以替换调用的返回值,而无需在运行时对其进行评估。(在 gcc 中,这是通过 实现的__builtin_strnlen)。
如果该strnlen函数不是已知的内置函数,但可以内联:
inline size_t my_strnlen(char const* s, size_t n) {
for (size_t i = 0; i != n; ++i)
if (s[i] == 0)
return i;
return n;
}
int p() {
char mystring[32] = "this is a long line";
return my_strnlen(mystring, 32) > 2;
}
Run Code Online (Sandbox Code Playgroud)
这里 clang进行了优化,mov eax, 1但 gcc 发出了一个循环。
最后,对于标记pure为告诉优化器它没有副作用的未知谓词:
__attribute__((pure)) int f(char);
inline size_t my_strnlen_f(char const* s, size_t n) {
for (size_t i = 0; i != n; ++i)
if (f(s[i]))
return i;
return n;
}
int p() {
char mystring[32] = "this is a long line";
return my_strnlen_f(mystring, 32) > 2;
}
Run Code Online (Sandbox Code Playgroud)
gcc 再次发出循环;clang 发出一些相当笨拙的代码(怎么了ebx?),尽管如此,它表明它知道f需要调用不超过 3 次,并且使用前 3 个字符的字符代码 - 它优化了整个字符串:
p: # @p
push rbx
mov edi, 116 # 't'
call f@PLT
xor ebx, ebx
test eax, eax
je .LBB2_1
.LBB2_3:
mov eax, ebx
pop rbx
ret
.LBB2_1:
mov edi, 104 # 'h'
call f@PLT
test eax, eax
jne .LBB2_3
mov edi, 105 # 'i'
call f@PLT
xor ebx, ebx
test eax, eax
sete bl
mov eax, ebx
pop rbx
ret
Run Code Online (Sandbox Code Playgroud)
- 如果我没有预先分配字符串长度会有什么影响吗?
不,在这种情况下,C 语言只会将缓冲区大小设置为字符串文字的大小(字符串长度 + 1 作为终止符)。
- 如果我从 IF 内部表达式中删除括号,会有什么影响吗?
不,优化器在不包含这些语法细节的程序表示上运行。
- 如果我将操作数和运算符切换为 ,会有影响吗
<?
几乎肯定不是,优化器能够理解它们是等价的。