35

私は C で言語インタープリターを作成しており、私のstring型には次のlengthような属性が含まれています。

struct String
{
    char* characters;
    size_t length;
};

このため、C には組み込みのサポートが含まれていないため、この種の文字列を手動で処理するインタープリターで多くの時間を費やさなければなりません。基本的な C に準拠するためだけに、単純な null で終わる文字列に切り替えることを検討しましたが、そうしない理由はたくさんあるようです。

null を探す代わりに「長さ」を使用すると、境界チェックが組み込まれます。

その長さを見つけるには、文字列全体をトラバースする必要があります。

null で終わる文字列の途中で null 文字を処理するには、余分な作業を行う必要があります。

Null で終わる文字列は、Unicode をうまく処理できません。

null で終わらない文字列は、より多くインターンすることができます。つまり、"Hello, world" と "Hello" の文字は、長さが異なるだけで同じ場所に格納できます。これは、null で終わる文字列では実行できません。

文字列スライス (注: 私の言語では文字列は不変です)。明らかに、2 番目の方が遅くなります (そして、よりエラーが発生しやすくなります。両方の関数のbeginとにエラー チェックを追加することを検討してください)。end

struct String slice(struct String in, size_t begin, size_t end)
{
    struct String out;
    out.characters = in.characters + begin;
    out.length = end - begin;

    return out;
}

char* slice(char* in, size_t begin, size_t end)
{
    char* out = malloc(end - begin + 1);

    for(int i = 0; i < end - begin; i++)
        out[i] = in[i + begin];

    out[end - begin] = '\0';

    return out;
}

結局のところ、ヌル終了文字列を使用する必要があるかどうかについては、もはや考えていません。C がヌル終了文字列を使用する理由について考えています。

だから私の質問は次のとおりです:私が見逃しているヌル終了の利点はありますか?

4

10 に答える 10

33

Joel のBack to Basics から:

C 文字列がこのように機能するのはなぜですか? これは、UNIX と C プログラミング言語が発明された PDP-7 マイクロプロセッサが ASCIZ 文字列型を持っていたためです。ASCIZ は、「最後に Z (ゼロ) がある ASCII」を意味します。

これは文字列を保存する唯一の方法ですか? いいえ、実際には、これは文字列を格納する最悪の方法の 1 つです。重要なプログラム、API、オペレーティング システム、クラス ライブラリの場合は、ペストのような ASCIZ 文字列を避ける必要があります。

于 2009-08-10T06:15:08.570 に答える
19

通常の解決策は、長さを維持し、ヌル ターミネータを維持するという両方を行うことです。余分な作業はそれほど多くなく、文字列を任意の関数にいつでも渡す準備ができていることを意味します。

長さを検出するのにかかる時間は長さに依存するという明らかな理由から、null で終わる文字列は多くの場合、パフォーマンスを低下させます。プラス面としては、これらは C で文字列を表す標準的な方法であるため、ほとんどの C ライブラリを使用したい場合は、それらをサポートする以外に選択肢はほとんどありません。

于 2009-08-10T06:12:14.470 に答える
9

ヌル終端文字列の利点の 1 つは、文字列を 1 文字ずつ調べている場合、文字列をアドレス指定するために 1 つのポインターのみを保持する必要があることです。

while (*s)
{
    *s = toupper(*s);
    s++;
}

一方、番兵のない文字列の場合は、ポインタとインデックスのいずれかの 2 ビットの状態を維持する必要があります。

while (i < s.length)
{
    s.data[i] = toupper(s.data[i]);
    i++;
}

...または現在のポインターと制限:

s_end = s + length;
while (s < s_end)
{
    *s = toupper(*s);
    s++;
}

CPU レジスタが不足している (そしてコンパイラがそれらを割り当てるのが苦手だった) とき、これは重要でした。さて、それほどではありません。

于 2009-08-10T06:45:34.390 に答える
8

長さにも問題があります。

  • 長さには余分なストレージが必要です (現在はそれほど問題ではありませんが、30 年前には大きな要因でした)。

  • 文字列を変更するたびに長さを更新する必要があるため、全体的にパフォーマンスが低下します。

  • NUL で終わる文字列を使用しても、長さを使用したり、最後の文字へのポインターを格納したりできるため、多くの文字列操作を行っている場合でも、string-with-length のパフォーマンスに匹敵することができます。

  • NUL で終了する文字列ははるかに単純です。NUL ターミネータはstrcat、文字列の末尾を決定するなどのメソッドで使用される規則にすぎません。したがって、構造体を使用するのではなく、通常の char 配列に格納できます。

于 2009-08-10T06:14:20.270 に答える
7

利点の 1 つは、ヌル終了では、ヌル終了文字列の末尾もヌル終了文字列になることです。N 番目の文字で始まる部分文字列を渡す必要がある場合 (バッファー オーバーランがない場合)、文字列処理関数に問題なく、そこにオフセットされたアドレスを渡すだけです。サイズを別の方法で保存する場合は、新しい文字列を作成する必要があります。

于 2009-08-10T06:09:01.123 に答える
6

少し話題から外れていますが、説明した方法よりも長さのプレフィックス付きの文字列を実行するためのより効率的な方法があります。次のような構造体を作成します (C99 以降で有効)。

struct String 
{
  size_t length;
  char characters[0];
}

これにより、現在の構造体と同じように、'characters' 要素を char* として使用できる構造体が作成されます。ただし、違いは、文字列ごとに 2 つではなく、1 つの項目のみをヒープに割り当てることができることです。次のように文字列を割り当てます。

mystr = malloc(sizeof(String) + strlen(cstring))

例 - 構造体の長さ (これはちょうど size_t です) に、その後に実際の文字列を配置するのに十分なスペースを加えたものです。

C99 を使用したくない場合は、"char characters[1]" を使用して、文字列の長さから 1 を引いて割り当てることもできます。

于 2009-08-20T20:13:46.597 に答える
4

いくつかの仮説を捨てるだけです:

  • null で終わる文字列の「間違った」実装を取得する方法はありません。ただし、標準化された構造体は、ベンダー固有の実装を持つことができます。
  • 構造体は必要ありません。ヌル終了文字列は、char* の特殊なケースであるため、いわば「組み込み」です。
于 2009-08-10T06:14:38.210 に答える
2

0-termination は、一部の操作の型チェックとパフォーマンスに関して貧弱な方法であることは間違いありません。このページの回答は、その起源と用途をすでに要約しています。

Delphi が文字列を格納する方法が気に入りました。(可変長)文字列の前に長さ/最大長を維持していると思います。このようにして、互換性のために文字列をヌルで終了させることができます。

あなたのメカニズムに関する私の懸念: - 追加のポインタ - あなたの言語のコア部分における不変性 si; 通常、文字列型は不変ではないため、再考すると大変なことになります。「変更時にコピーを作成」メカニズムを実装する必要があります-mallocの使用(ほとんど効率的ではありませんが、簡単にするためにここに含めることができますか?)

幸運を; 独自のインタープリターを作成することは、主にプログラミング言語の文法と構文を理解する上で非常に教育的です! (少なくとも、私にとってはそうでした)

于 2009-08-10T09:53:19.610 に答える
2

ほとんどの場合、配列 + len メソッドを好みますが、ヌル終了を使用する正当な理由があります。

32ビットシステムを取ります。

7 バイトの文字列を格納するには
char * + size_t + 8 バイト = 19 バイト

7 バイトの null ターム文字列を格納するには、
char * + 8 = 16 バイト。

null-term 配列は、文字列のように不変である必要はありません。null char を配置するだけで、c-string を喜んで切り捨てることができます。コードを作成する場合は、新しい文字列を作成する必要があり、これにはメモリの割り当てが含まれます。

文字列の使用方法によっては、文字列とは対照的に、文字列が c-string で可能なパフォーマンスに匹敵することは決してありません。

于 2009-08-10T06:14:04.937 に答える
0

主な理由は、標準がchar以外の型のサイズについて具体的に何も言っていないことだと思います。しかし、 sizeof(char) = 1 であり、文字列サイズには十分ではありません。

于 2009-08-10T06:07:18.710 に答える