web-dev-qa-db-ja.com

ループ状態で使用した場合、strlenは複数回計算されますか?

次のコードが冗長な計算を引き起こす可能性があるかどうか、またはコンパイラ固有ですか?

_for (int i = 0; i < strlen(ss); ++i)
{
    // blabla
}
_

iが増加するたびにstrlen()が計算されますか?

108
daisy

はい、strlen()は反復ごとに評価されます。理想的な状況下では、オプティマイザが値が変わらないと推測できる可能性がありますが、私は個人的にはそれに依存しません。

私は次のようなことをします

_for (int i = 0, n = strlen(ss); i < n; ++i)
_

または多分

_for (int i = 0; ss[i]; ++i)
_

反復中に文字列の長さが変化しない限り。可能であれば、毎回strlen()を呼び出すか、より複雑なロジックで処理する必要があります。

136
Mike Seymour

はい、ループを使用するたびに。その後、毎回文字列の長さを計算します。次のように使用します。

char str[30];
for ( int i = 0; str[i] != '\0'; i++)
{
//Something;
}

上記のコードではstr[i]は、ループがサイクルを開始するたびに、位置iの文字列内の特定の1文字のみを検証するため、必要なメモリが少なくなり、より効率的です。

詳細はこちら Link を参照してください。

以下のコードでは、ループが実行されるたびにstrlenは文字列全体の長さをカウントするため、効率が悪くなり、より多くの時間とメモリを必要とします。

char str[];
for ( int i = 0; i < strlen(str); i++)
{
//Something;
}
14
codeDEXTER

良いコンパイラーは毎回それを計算するわけではないかもしれませんが、すべてのコンパイラーが計算することは確かではないと思います。

さらに、コンパイラーはstrlen(ss)が変更されないことを知っている必要があります。これは、ssループでforが変更されていない場合にのみ当てはまります。

たとえば、ssループのforで読み取り専用関数を使用するが、ssパラメーターをconstとして宣言しない場合、コンパイラーssがループ内で変更されておらず、反復ごとにstrlen(ss)を計算する必要があることを知ることさえできません。

9
Misch

ssのタイプがconst char *であり、ループ内でconstnessをキャストしない場合、最適化がオンになっていると、コンパイラはstrlenを1回だけ呼び出す可能性があります。 。しかし、これは確かに信頼できる行動ではありません。

strlenの結果を変数に保存し、この変数をループで使用する必要があります。追加の変数を作成したくない場合は、何をしているのかに応じて、ループを逆にして逆方向に反復することで回避できる場合があります。

for( auto i = strlen(s); i > 0; --i ) {
  // do whatever
  // remember value of s[strlen(s)] is the terminating NULL character
}
4
Praetorian

正式にはそうです、strlen()はすべての反復で呼び出されることが期待されています。

とにかく、いくつかの賢いコンパイラ最適化の存在の可能性を否定したくはありません。これにより、最初の呼び出しの後にstrlen()への連続した呼び出しが最適化されます。

3
alk

述語コード全体は、forループのすべての反復で実行されます。 strlen(ss)呼び出しの結果をメモするために、コンパイラーは少なくとも

  1. 関数strlenには副作用がありませんでした
  2. ssが指すメモリは、ループの期間中変化しません

コンパイラはこれらのどちらも認識していないため、最初の呼び出しの結果を安全にメモできません。

3
JaredPar

はい、strlen(ss)はコードが実行されるたびに計算されます。

2
Hisham Muneer

はい、strlen()関数が呼び出されます毎回ループが評価されます。

効率を向上させたい場合は、常にローカル変数にすべてを保存することを忘れないでください...時間がかかりますが、非常に便利です。

以下のようなコードを使用できます。

String str="ss";
int l = strlen(str);

for ( int i = 0; i < l ; i++ )
{
    // blablabla
}
2
Rajan

はい、strlen(ss)は各反復で長さを計算します。何らかの方法でssを増やし、iも増やす場合。無限ループになるでしょう。

2
Kumar Shorav

今日では一般的ではありませんが、20年前の16ビットプラットフォームでは、これをお勧めします。

for ( char* p = str; *p; p++ ) { /* ... */ }

コンパイラーの最適化があまり良くない場合でも、上記のコードを使用すると、優れたアセンブリコードが得られます。

2
Luciano

はい。 strlenは、iが増加するたびに計算されます。

ssを変更しなかった場合ループ内で指定すると、それが意味しますロジックには影響しませんそれ以外の場合は影響します。

次のコードを使用する方が安全です。

int length = strlen(ss);

for ( int i = 0; i < length ; ++ i )
{
 // blabla
}
2

ああ、それは、理想的な状況下でさえ、気が進まないでしょう!

今日(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___に違反しない限り)。

stillの場合、両方のコンパイラーがstrlen()そのループの1回の繰り返し を実行します。すごい。

これは、@ Praetorianと@JaredParの暗示/希望的思考がうまくいかないことも意味します。

1
einpoklum

はい。テストは、ssがループ内で変更されないことを認識していません。変更されないことがわかっている場合は、次のように記述します。

int stringLength = strlen (ss); 
for ( int i = 0; i < stringLength; ++ i ) 
{
  // blabla 
} 
1
DanS

まあ、誰かがそれが「賢い」現代のコンパイラによってデフォルトで最適化されていると言っていることに気づきました。ちなみに、最適化せずに結果を見てください。私は試した:
最小限の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
     .....
0
Tebe

プレトリアの答えについて詳しく述べると、次のことをお勧めします。

_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_を使用する必要があることです。

0
steffen

はい、簡単な言葉で。そして、コンパイラーが望んでいるまれな状況では、ssに変更がまったく加えられていないことが判明した場合の最適化ステップとして、小さなノーがあります。しかし、安全な状態では、それをYESと考えるべきです。 multithreadedやイベント駆動型プログラムのような状況があり、それをNOと見なすとバグが発生する可能性があります。プログラムの複雑さをあまり改善しないので、安全にプレイしてください。

0
Pervez Alam

はい。

strlen()は、iが増加し、最適化されないときに毎回計算されます。

以下のコードは、コンパイラがstrlen()を最適化しない理由を示しています。

for ( int i = 0; i < strlen(ss); ++i )
{
   // Change ss string.
   ss[i] = 'a'; // Compiler should not optimize strlen().
}
0
Amir Saniyan

簡単にテストできます:

char nums[] = "0123456789";
size_t end;
int i;
for( i=0, end=strlen(nums); i<strlen(nums); i++ ) {
    putchar( nums[i] );
    num[--end] = 0;
}

ループ条件は、ループを再開する前に、繰り返しのたびに評価されます。

また、文字列の長さを処理するために使用するタイプにも注意してください。そのはず size_tとして定義されていますunsigned int stdioで。比較してintにキャストすると、深刻な脆弱性の問題が発生する可能性があります。

0
Rsh