この関数が文字列の正しい長さを返すのはなぜですか?(charポインタのインクリメント)


12

これは文字列の文字数を数える関数です:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) {
        i++;
    }
    return i;
}

なぜこれは正しい長さを返すのですか?

この関数を単純なStringで呼び出すとしましょう"a"。次にs、whileループでインクリメントされるため、sおよびの値iは両方とも0です。

回答:


10

の値s++はの元の値でs、インクリメントの前に、次のシーケンスポイントの前の不特定の時間にインクリメントが発生します。

したがって*s++、と*(s++)同等ですs。どちらもの元の値を逆参照します。別の同等の表現は*(0, s++)次のとおりであり、気の弱い人のためのものではありません。0[s++]

あなたの関数の型を使用する必要があることに注意してくださいsize_tするためにi、その戻り値の型:

size_t str_len(const char *s) {
    size_t i = 0;
    while (*s++) {
        i++;
    }
    /* s points after the null terminator */
    return i;
}

ループごとに1つの増分がある、より効率的なバージョンを次に示します。

size_t str_len(const char *s) {
    const char *s0 = s;
    while (*s++) {
        /* nothing */
    }
    return s - 1 - s0;
}

2番目の段落の奇妙な表現について不思議に思う人のために:

  • 0, s++,左部分を評価し、次にその値を構成する右部分を評価するコンマ演算子のインスタンスです。したがって、(0, s++)と同等(s++)です。

  • 0[s++]等価で(s++)[0]且つ*(0 + s++)又は*(s++ + 0)どのように簡略化します*(s++)。式でのポインタとインデックス式の転置は[]あまり一般的ではなく、特に有用ではありませんが、C標準に準拠しています。


カンマ演算子が明確であることを確認してください。取り除いてください。, s++悪いことが起こります:)
デビッドC.ランキン

6

この関数を単純な文字列 "a"で呼び出すとしましょう。次に、whileループでsがインクリメントされるため、sの値は0であり、iも0です。

その例では、がをsポイントして'a'"a"ます。次に、増分され、iさらに増分されます。ここsで、nullターミネーターをポイントし、iです1。したがって、ループの次の実行で*(s++)は、is '\0'(つまり0)なので、ループが終了し、i(that's 1)の現在の値が返されます。

通常、ループは文字列内の各文字に対して1回実行され、その後NULLターミネーターで停止するため、このようにして文字がカウントされます。


sは角かっこで囲まれているので、最初にインクリメントされると思いました(つまり、現在は '/ 0'を指しています)。したがって、whileループはfalseであり、iは増分されません。
LOR

2
@lor、ポストインクリメント演算子を思い出してください。これは、インクリメントする前にs保持されたものに評価されます。あなたが説明しているのは(実際には1つ少なくカウントされ、空の文字列が渡されるとUBを呼び出す)の動作です。++s
Toby Speight

2

それは完全に理にかなっています:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) { //<-- increments the pointer to char till the end of the string
                    //till it finds '\0', that is, if s = "a" then s is 'a'
                    // followed by '\0' so it increments one time
        i++; //counts the number of times the pointer moves forward
    }
    return i;
}

「しかしs、括弧内にある。それが私が最初に増加されると思った理由だ」

これが、ポインタではなく文字がインクリメントされる理由です。たとえば(*s)++、がある場合、この場合、ポインタではなく文字がインクリメントされます。逆参照とは、ポインター自体ではなく、ポインターによって参照される値を操作していることを意味します。

どちらの演算子も優先順位は同じですが、右から左への関連性があるため*s++、ブラケットを使用せずにポインタをインクリメントすることもできます。


ただし、sは括弧内にあります。それが最初にインクリメントされると思った理由です。(「a」のような単純な文字列がある場合、sは「/ 0」を指すようになります)。条件がwhile(0)になったため、whileループに入ることがありません。
LOR

2

ポストインクリメント演算子はオペランドの値を1増やします は、オペランドの値をますが、式の値は、インクリメント操作の前のオペランドの元の値です。

渡された引数が想定しstr_len()ています"a"。ではstr_len()、ポインタは、s文字列の最初の文字を指しています"a"。ではwhileループ:

while(*(s++)) {
.....
.....

sインクリメントされますが、式内のの値はs、インクリメントの前に指している文字へのポインタ、つまり最初の文字へのポインタになります。ポインタが逆参照されると、文字を与えます。次の反復では、ポインタはnull文字である次の文字を指しています。ときに逆参照され、それが与えるとループは終了となります。は、stringのnull文字を過ぎた1つの要素を指すことに注意してください。'a's'a's\0s0s"a"

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.