次のコードが冗長な計算を引き起こす可能性があるかどうか、またはコンパイラ固有ですか?
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
変更されていないことを証明できる場合、ループから計算を引き上げることができます。
回答:
はい、strlen()
反復ごとに評価されます。理想的な状況下では、オプティマイザは値が変わらないと推測できる可能性がありますが、私は個人的にはそれに依存しません。
私は次のようなことをします
for (int i = 0, n = strlen(ss); i < n; ++i)
または多分
for (int i = 0; ss[i]; ++i)
反復中に文字列の長さが変化しない限り。可能であれば、strlen()
毎回呼び出すか、より複雑なロジックで処理する必要があります。
strlen
とにかく実行されるループであるため、2番目の方法がはるかに望ましいです。
strlen
れ__attribute__((pure))
ます。ここで、は、コンパイラが複数の呼び出しを省略できるようにマークされています。GCCの属性
はい、ループを使用するたびに。その後、毎回文字列の長さを計算します。次のように使用します。
char str[30];
for ( int i = 0; str[i] != '\0'; i++)
{
//Something;
}
上記のコードstr[i]
ではi
、ループがサイクルを開始するたびに、位置の文字列内の特定の1文字のみを検証するため、必要なメモリが少なくなり、より効率的です。
詳細については、このリンクを参照してください。
以下のコードでは、ループが実行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; }
、あなたの答えのコードであなたがやっていることと全く同じです。文字列を2回繰り返すよりも1回繰り返すほうが時間効率が良いとは主張していませんが、どちらかが多かれ少なかれメモリを使用していることはありません。または、文字列の長さを保持するために使用される変数を参照していますか?
良いコンパイラーは毎回それを計算するわけではないかもしれませんが、すべてのコンパイラーがそれを行うとは確信できません。
それに加えて、コンパイラstrlen(ss)
はそれを変更しないことを知っている必要があります。これはss
、for
ループで変更されない場合にのみ当てはまります。
たとえばss
、for
ループ内で読み取り専用関数を使用するが、ss
-parameterをとして宣言しないconst
場合、コンパイラーはそれss
がループ内で変更されていないことを認識できずstrlen(ss)
、すべての反復で計算する必要があります。
ss
で変更してはなりませんfor
。ループで呼び出された関数からアクセスおよび変更できないようにする必要があります(引数として渡されるため、またはグローバル変数またはファイルスコープ変数のため)。const-qualificationも要因となる可能性があります。
restrict
はC99の目的の1つです。
ss
、forループでは、そのパラメータが宣言されている場合でもconst char*
、コンパイラはまだ()それは知っているいずれかの場合を除き長さを再計算する必要があるss
のconstオブジェクトへのポイントを、単にconstへのポインタであるのとは対照的に、または(b)関数をインライン化するか、そうでなければ読み取り専用であることを確認できます。変更されたオブジェクトがconstでなく、文字列リテラルではない場合、キャストして変更することは有効であるため、const char*
パラメーターを取得することは、ポイントされたデータを変更しないという約束char*
ではありません。
もし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
にsize_t
またはいくつかの中でそれを分割byte
値。私の不正なスレッドはそのアドレスにバイトを書き込むだけで、コンパイラはそれがに関連していることを理解する方法を知っているでしょうss
。
int a = 0; do_something(); printf("%d",a);
基づいて、最適化できないと主張do_something()
できますa
。実際のところ、gcc 4.5はdo_something(); printf("%d",0);
-O3 を使用して最適化します
はい。strlenは、iが増加するたびに計算されます。
あなたがいる場合、SSを変更しなかったとのループで、それは手段のロジックには影響しませんそれ以外の場合は、影響を与えます。
次のコードを使用する方が安全です。
int length = strlen(ss);
for ( int i = 0; i < length ; ++ i )
{
// blabla
}
今日(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の暗示/希望的思考がうまくいかないことも意味します。
簡単にテストできます:
char nums[] = "0123456789";
size_t end;
int i;
for( i=0, end=strlen(nums); i<strlen(nums); i++ ) {
putchar( nums[i] );
num[--end] = 0;
}
ループ条件は、ループを再開する前に、繰り返しごとに評価されます。
また、文字列の長さを処理するために使用するタイプにも注意してください。stdioでsize_t
定義されているものでなければなりませんunsigned int
。比較してキャストすると、int
深刻な脆弱性の問題が発生する可能性があります。
まあ、誰かがそれが「賢い」現代のコンパイラによってデフォルトで最適化されていると言っていることに気づきました。ちなみに、最適化せずに結果を見てください。私が試した:
最小限の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
修飾されていない限り、それを最適化しようとしたコンパイラを信頼するのは嫌です。このような最適化が正当である場合もありますが、そのようなケースがない場合にそのようなケースを確実に特定するために必要な労力restrict
は、妥当な方法で、ほぼ確実にメリットを超えます。const restrict
ただし、文字列のアドレスに修飾子が含まれている場合は、それだけで、他に何も見なくても最適化を正当化できます。
プレトリアの答えについて詳しく述べると、次のことをお勧めします。
for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
auto
strlenが返すタイプを気にしたくないからです。C ++ 11コンパイラー(たとえばgcc -std=c++0x
、完全にC ++ 11ではないが、自動型は機能します)がそれを行います。i = strlen(s)
比較したいので0
(下記参照)i > 0
0との比較は、他の数値と比較して(わずかに)速いためです。欠点はi-1
、文字列の文字にアクセスするために使用する必要があることです。