20

strchr()メソッドの独自の実装を作成しようとしました。

これで、次のようになります。

char *mystrchr(const char *s, int c) {
    while (*s != (char) c) {
        if (!*s++) {
            return NULL;
        }
    }
    return (char *)s;
}

最後の行は元々

return s;

しかし、sはconstであるため、これは機能しませんでした。このキャスト(char *)が必要であることがわかりましたが、正直なところ、そこで何をしているのかわかりません:(誰かが説明できますか?

4

5 に答える 5

22

これは実際にはC標準のstrchr()関数の定義の欠陥だと思います。(間違っていることが証明されてうれしいです。)(コメントに答えて、それが本当に欠陥であるかどうかは議論の余地があります。私見ではまだデザインが貧弱です。安全に使用できますが、安全に使用するのは簡単すぎます。)

C規格の内容は次のとおりです。

char *strchr(const char *s, int c);

strchr関数は、 sが指す文字列内で最初に出現するc ( char に変換)を検索します。終了ヌル文字は文字列の一部と見なされます。

つまり、このプログラムは次のようになります。

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *s = "hello";
    char *p = strchr(s, 'l');
    *p = 'L';
    return 0;
}

文字列リテラルへのポインタをへのポインタとして注意深く定義していますが、文字列リテラルを変更するため、未定義の動作があります。少なくともgccはこれについて警告せず、プログラムはセグメンテーション違反で終了します。const char

問題はstrchr()、引数を取ることです。つまり、-を指すconst char*データを変更しないことを約束しますが、呼び出し元が同じデータを変更できるようにするプレーンを返します。schar*

別の例を示します。未定義の動作はありませんが、キャストなしで修飾されたオブジェクトを静かに変更しconstます(さらに考えてみると、未定義の動作があると思います)。

#include <stdio.h>
#include <string.h>

int main(void) {
    const char s[] = "hello";
    char *p = strchr(s, 'l');
    *p = 'L';
    printf("s = \"%s\"\n", s);
    return 0;
}

つまり、(あなたの質問に答えるために)のC実装は、結果をに変換するか、同等のことを行うstrchr()ために結果をキャストする必要があると思います。const char*char*

これが、C ++がC標準ライブラリに加えるいくつかの変更の1つでstrchr()、同じ名前の2つのオーバーロードされた関数に置き換えられる理由です。

const char * strchr ( const char * str, int character );
      char * strchr (       char * str, int character );

もちろん、Cはこれを行うことはできません。

strchr別の方法は、2つの関数に置き換えることでした。1つはaを取り、const char*を返し、const char*もう1つはaを取り、char*を返しchar*ます。strchrC ++とは異なり、2つの関数は異なる名前(おそらくと)を持つ必要がありstrcchrます。

(歴史的には、すでに定義されたconst後にCに追加されました。これは、おそらく既存のコードを壊さずに維持する唯一の方法でした。)strchr()strchr()

strchr()この問題を抱えているのは、C標準ライブラリ関数だけではありません。影響を受ける機能のリスト(このリストは完全だと思いますが、保証はしません)は次のとおりです。

void *memchr(const void *s, int c, size_t n);
char *strchr(const char *s, int c);
char *strpbrk(const char *s1, const char *s2);
char *strrchr(const char *s, int c);
char *strstr(const char *s1, const char *s2);

(すべてで宣言されてい<string.h>ます)および:

void *bsearch(const void *key, const void *base,
    size_t nmemb, size_t size,
    int (*compar)(const void *, const void *));

(で宣言されてい<stdlib.h>ます)。これらの関数はすべてconst、配列の最初の要素を指すデータへのポインターを受け取り、その配列の要素への非constポインターを返します。

于 2013-01-16T21:26:33.837 に答える
14

非変更関数からconstデータへの非constポインターを返す方法は、実際にはC言語でかなり広く使用されているイディオムです。いつもきれいというわけではありませんが、かなり確立されています。

ここでの根拠は単純です。strchrそれ自体は変更を加えない操作です。strchrただし、定数文字列と非定数文字列の両方の機能が必要です。これにより、入力の定数が出力の定数に伝播されます。CとC++のどちらも、この概念をエレガントにサポートしていません。つまり、両方の言語で、const-correctnessによるリスクを回避するために、2つの実質的に同一の関数を作成する必要があります。

C ++では、同じ名前の2つの関数を宣言することで、関数のオーバーロードを使用できます。

const char *strchr(const char *s, int c);
char *strchr(char *s, int c);

Cでは、関数のオーバーロードがないため、この場合にconst-correctnessを完全に適用するには、次のような異なる名前の2つの関数を提供する必要があります。

const char *strchr_c(const char *s, int c);
char *strchr(char *s, int c);

場合によってはこれが正しいことかもしれませんが、通常(そして当然のことながら)面倒であり、C標準に関係していると見なされます。1つの関数のみを実装することで、この状況をよりコンパクトな(ただし、よりリスクの高い)方法で解決できます。

char *strchr(const char *s, int c);

これは、非constポインタを入力文字列に返します(出口でキャストを使用して、まったく同じように)。このアプローチは言語の規則に違反しませんが、呼び出し元にそれらに違反する手段を提供することに注意してください。データの恒常性を捨てることにより、このアプローチは、関数自体から呼び出し元に恒常性を監視する責任を単に委任します。呼び出し元が何が起こっているかを認識し、「うまくプレイする」ことを覚えている限り、つまりconst修飾ポインターを使用してconstデータを指す限り、そのような関数によって作成されたconst-correctnessの壁の一時的な違反は即座に修復されます。

このトリックは、不要なコードの重複を減らすための完全に受け入れられるアプローチだと思います(特に関数のオーバーロードがない場合)。標準ライブラリはそれを使用します。自分が何をしているのかを理解していれば、それを避ける理由もありません。

さて、あなたの実装に関してはstrchr、文体の観点からは奇妙に見えます。サイクルヘッダーを使用して、操作している全範囲(完全な文字列)を反復処理し、内部を使用しifて早期終了条件をキャッチします

for (; *s != '\0'; ++s)
  if (*s == c)
    return (char *) s;

return NULL;

しかし、そのようなことは常に個人的な好みの問題です。誰かがただ

for (; *s != '\0' && *s != c; ++s)
  ;

return *s == c ? (char *) s : NULL;

s関数内の関数パラメーター()を変更することは悪い習慣であると言う人もいるかもしれません。

于 2013-01-17T01:03:45.187 に答える
1

キーワードはconst、パラメーターを変更できないことを意味します。

として宣言されており、関数の戻り型がであるsため、直接戻ることはできませんでした。コンパイラーがそれを許可した場合、制限をオーバーライドすることが可能になります。sconst char *schar *const

明示的なキャストを追加して、char*実行していることを知っていることをコンパイラーに通知します(ただし、Ericが説明したように、実行しなかった方がよいでしょう)。

更新:文脈のために、エリックの答えを引用しています。彼はそれを削除したようです。

const char *であるため、sを変更しないでください。

代わりに、char *型の結果を表すローカル変数を定義し、メソッド本体のsの代わりにそれを使用します。

于 2013-01-16T21:04:10.913 に答える
0

関数の戻り値は、文字への定数ポインターである必要があります。

strchrを受け入れ、const char*また戻る必要がconst char*あります。char *戻り値が入力文字配列を指しているため、潜在的に危険な非定数を返しています(呼び出し元は定数引数が一定のままであることを期待している可能性がありますが、その一部がポインターとして返される場合は変更可能です)。

一致する文字が見つからない場合、関数の戻り値はNULLである必要があります。

また、探しているキャラクターが見つからない場合もstrchr戻ってくるはずです。NULL文字が見つからないときにNULL以外を返す場合、またはこの場合はsの場合、呼び出し元(動作がstrchrと同じであると考える場合)は、結果の最初の文字が実際に一致すると見なす可能性があります(NULLの戻り値はありません)。一致したかどうかを判断する方法はありません)。

(それがあなたが意図したことであるかどうかはわかりません。)

これを行う関数の例を次に示します。

私はこの関数についていくつかのテストを作成して実行しました。クラッシュの可能性を回避するために、いくつかの非常に明白な健全性チェックを追加しました。

const char *mystrchr1(const char *s, int c) {
    if (s == NULL) {
        return NULL;
    }
    if ((c > 255) || (c < 0)) {
        return NULL;
    }
    int s_len;
    int i;
    s_len = strlen(s);
    for (i = 0; i < s_len; i++) {
        if ((char) c == s[i]) {
            return (const char*) &s[i];
        }
    }
    return NULL;
}
于 2013-01-16T21:15:01.160 に答える
0

に渡される文字列リテラルを変更するためにのchar*結果を使用しようとするコードを作成するときはいつでも、コンパイラエラーが発生することは間違いありません。mystrchrmystrchr

文字列リテラルの変更は、プログラムの異常終了やサービス拒否攻撃につながる可能性があるため、セキュリティ上ノーノーです。コンパイラは、文字列リテラルを関数takeingに渡すと警告を表示する場合がありますが、char*必須ではありません。

どのようにstrchrを正しく使用しますか?例を見てみましょう。

これは、すべきでないことの例です。

#include <stdio.h>
#include <string.h>

/** Truncate a null-terminated string $str starting at the first occurence 
 *  of a character $c. Return the string after truncating it.
 */
const char* trunc(const char* str, char c){
  char* pc = strchr(str, c);
  if(pc && *pc && *(pc+1)) *(pc+1)=0;
  return str;
}

strポインタを介して文字列リテラルを変更する方法をご覧くださいpc。それはブエノではありません。

これを行う正しい方法は次のとおりです。

#include <stdio.h>
#include <string.h>

/** Truncate a null-terminated string $str of $sz bytes starting at the first 
 *  occurrence of a character $c. Write the truncated string to the output buffer 
 *  $out.
 */
char* trunc(size_t sz, const char* str, char c, char* out){
  char* c_pos = strchr(str, c);
  if(c_pos){
    ptrdiff_t c_idx = c_pos - str;
    if((size_t)n < sz){
      memcpy(out, str, c_idx); // copy out all chars before c
      out[c_idx]=0; // terminate with null byte
    }
  }
   return 0; // strchr couldn't find c, or had serious problems
}

によって返されたポインタstrchrを使用して、文字列内の一致する文字のインデックスを計算する方法を確認してください。次に、インデックス(その時点までの長さから1を引いたものに等しい)を使用して、文字列の目的の部分を出力バッファにコピーします。

「ああ、それはばかげている!それが私をmemcpyにするだけなら、私はstrchrを使いたくない」と思うかもしれません。そう感じれば、whileループや、などを使用しても解決できなかった、などstrchrのユースケースに遭遇したことはありません。strchrを正しく使用するよりも実際にクリーンな場合があります。strrchrisspaceisalnum

于 2021-10-15T09:53:38.293 に答える