我不确定以下代码是否会导致冗余计算,或者它是否特定于编译器?
for (int i = 0; i < strlen(ss); ++i)
{
// blabla
}
strlen()
每次i
增加时都会计算?
ss
对循环内部执行的操作。
ss
永远不会被修改,则它可以将计算提升到循环之外。
我不确定以下代码是否会导致冗余计算,或者它是否特定于编译器?
for (int i = 0; i < strlen(ss); ++i)
{
// blabla
}
strlen()
每次i
增加时都会计算?
ss
对循环内部执行的操作。
ss
永远不会被修改,则它可以将计算提升到循环之外。
Answers:
是, strlen()
将在每次迭代中进行评估。在理想情况下,优化器有可能推断出价值不会改变,但我个人不会依靠这一点。
我会做类似的事情
for (int i = 0, n = strlen(ss); i < n; ++i)
或可能
for (int i = 0; ss[i]; ++i)
只要字符串在迭代过程中不会改变长度。如果可能,那么您将需要strlen()
每次调用,或者通过更复杂的逻辑进行处理。
strlen
无论如何都要执行的循环。
是的,每次使用循环时。然后它将每次计算字符串的长度。所以像这样使用它:
char str[30];
for ( int i = 0; str[i] != '\0'; i++)
{
//Something;
}
在上面的代码中,str[i]
仅验证位置字符串中的一个特定字符i
每次循环开始一个循环时,,因此它将占用较少的内存并且效率更高。
有关更多信息,请参见此链接。
在下面的代码中,每次循环运行strlen
都将计算整个字符串的长度,这将降低效率,花费更多时间并占用更多内存。
char str[];
for ( int i = 0; i < strlen(str); i++)
{
//Something;
}
strlen
,如果运行得太紧,您可能还应该考虑取消其他一些函数调用……
int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }
您在答案中的代码中所做的事情。我不是在争论一次而不是两次遍历字符串会节省更多时间,但是我看不到一个或另一个使用更多或更少的内存。还是您指的是用于保存字符串长度的变量?
一个好的编译器可能不会每次都计算它,但我认为您不能确定每个编译器都可以计算它。
除此之外,编译器必须知道,这strlen(ss)
不会改变。仅当ss
未for
循环更改时才如此。
例如,如果ss
在for
循环中使用只读函数,但未将ss
-parameter 声明为const
,则编译器甚至无法知道ss
循环中未更改该参数,因此必须strlen(ss)
在每次迭代中进行计算。
ss
不能在for
循环中更改;不能从循环中调用的任何函数访问和更改它(要么因为它作为参数传递,要么因为它是全局变量或文件作用域变量)。常量资格也可能是一个因素。
restrict
就是C99中要做的事情之一。
ss
在for循环中调用了一个只读函数,那么即使声明const char*
了其参数,编译器仍然需要重新计算长度,除非(a)知道ss
指向const对象, (b)可以内联该函数,否则可以看到它是只读的。接受const char*
参数并不保证不会修改所指向的数据,因为只要char*
修改后的对象不是const并且不是字符串文字,就可以强制转换并进行修改。
如果ss
是类型,const char *
并且您没有放弃const
循环内的本质,则编译器可能只会调用strlen
,那么如果打开了优化功能,一次。但这当然不是可以指望的行为。
您应该将strlen
结果保存在变量中,然后在循环中使用此变量。如果您不想创建其他变量,具体取决于您正在执行的操作,则可能不希望通过反转循环来向后迭代。
for( auto i = strlen(s); i > 0; --i ) {
// do whatever
// remember value of s[strlen(s)] is the terminating NULL character
}
strlen
完全是错误的。循环播放直到结束。
i > 0
?那不应该i >= 0
在这里吗?就我个人而言,strlen(s) - 1
如果从字符串上向后迭代,那么终止也\0
无需特别考虑。
i >= 0
仅在您初始化为时才起作用strlen(s) - 1
,但是如果您有一个长度为零的字符串,则初始值会下溢
i > 0
在初始循环进入时对表达式求值?如果不是这样,那么您是对的,零长度的情况肯定会打破循环。如果是的话,您“简单地”得到一个有符号的i
== -1 <0,因此如果条件为,则没有循环项i >= 0
。
strlen
的返回类型是无符号的,因此(strlen(s)-1) >= 0
对于零长度字符串,其求值为true。
整个谓词代码将在for
循环的每次迭代中执行。为了strlen(ss)
记住调用结果,编译器至少需要知道
strlen
无副作用ss
在循环期间不会更改编译器不知道这些事情,因此无法安全地记住第一次调用的结果
ss
为a size_t
或将其分配到多个byte
值中。然后,我弯曲的线程可以只将字节写入该地址,并且编译器将具有理解与之相关的方式ss
。
int a = 0; do_something(); printf("%d",a);
无法优化,do_something()
可能会导致您执行未初始化的int事情,或者可能会爬回堆栈并a
故意修改。实际上,gcc 4.5确实do_something(); printf("%d",0);
使用-O3 对其进行了优化
是的。我增加时都会计算strlen。
如果您没有在循环中更改ss with ,则它不会影响逻辑,否则会影响逻辑。
使用以下代码更安全。
int length = strlen(ss);
for ( int i = 0; i < length ; ++ i )
{
// blabla
}
是的,strlen(ss)
它将在每次迭代时计算长度。如果您ss
通过某种方式增加,同时也增加i
;会有无限循环。
截至今天(2018年1月)以及gcc 7.3和clang 5.0,如果您进行以下编译:
#include <string.h>
void bar(char c);
void foo(const char* __restrict__ ss)
{
for (int i = 0; i < strlen(ss); ++i)
{
bar(*ss);
}
}
因此,我们有:
ss
是一个常量指针。ss
被标记 __restrict__
ss
(嗯,除非违反__restrict__
)。并且还,两种编译器执行strlen()
该循环的每一个迭代。惊人。
这也意味着@Praetorian和@JaredPar的典故/如意算盘不会成功。
是。
strlen()
每次计算 i
增加且未优化。
下面的代码显示了为什么编译器不应该优化strlen()
。
for ( int i = 0; i < strlen(ss); ++i )
{
// Change ss string.
ss[i] = 'a'; // Compiler should not optimize strlen().
}
strlen
。
好吧,我注意到有人说默认情况下,任何“聪明”的现代编译器都会对其进行优化。顺便看看没有优化的结果。我试过:
最小的C代码:
#include <stdio.h>
#include <string.h>
int main()
{
char *s="aaaa";
for (int i=0; i<strlen(s);i++)
printf ("a");
return 0;
}
我的编译器:g ++(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3
生成汇编代码的命令:g ++ -S -masm = intel test.cpp
Gotten assembly code at the output:
...
L3:
mov DWORD PTR [esp], 97
call putchar
add DWORD PTR [esp+40], 1
.L2:
THIS LOOP IS HERE
**<b>mov ebx, DWORD PTR [esp+40]
mov eax, DWORD PTR [esp+44]
mov DWORD PTR [esp+28], -1
mov edx, eax
mov eax, 0
mov ecx, DWORD PTR [esp+28]
mov edi, edx
repnz scasb</b>**
AS YOU CAN SEE it's done every time
mov eax, ecx
not eax
sub eax, 1
cmp ebx, eax
setb al
test al, al
jne .L3
mov eax, 0
.....
restrict
-qualified。尽管在某些情况下,这种优化是合理的,但如果不restrict
采取任何合理措施,可靠地识别此类情况所需的工作几乎肯定会超过收益。const restrict
但是,如果字符串的地址具有限定符,则足以证明优化是合理的,而不必考虑其他任何条件。