CとC++が大好きなのと同じように、ヌルで終わる文字列の選択に頭を悩ませるしかありません。
std::basic_string
テンプレートを使用してこれを少し修正しましたが、nullで終了した文字列を期待するプレーンな文字配列は依然として普及しています。ヒープの割り当てが必要なため、これも不完全です。これらのことのいくつかは、Cよりも最近明らかになったので、Cがそれらのことを知らないことは理にかなっています。しかし、Cが登場する前のいくつかはかなり単純でした。明らかに優れた長さのプレフィックスの代わりに、ヌルで終わる文字列が選択されるのはなぜですか?
[〜#〜] edit [〜#〜]:一部の人はfactsを要求したので(そしてそれらは好きではなかった上記の効率性のポイントについては、既に説明しましたが、いくつかの点から生じています。
以下の回答から、これらはヌル終端文字列がより効率的ないくつかのケースです:
上記のどれも長さと連結とほぼ同じくらい一般的ではありません。
以下の回答にはもう1つ断言があります。
ただし、これは正しくありません。ヌルで終了し、プレフィックスの長さのある文字列の場合と同じ時間です。 (Nullで終了した文字列は、新しい終端を配置したい場所にNULLを貼り付けるだけで、長さのプレフィックスはプレフィックスから単に減算します。)
馬の口 から
BCPL、B、またはCのいずれも、言語の文字データを強力にサポートしていません。それぞれ、文字列を整数のベクトルのように扱い、いくつかの規則で一般的な規則を補足します。 BCPLとBの両方で、文字列リテラルは、セルにパックされた文字列の文字で初期化された静的領域のアドレスを示します。 BCPLでは、最初のパックされたバイトには文字列の文字数が含まれます。 Bでは、カウントはなく、文字列は特殊文字で終了します。Bは
*e
。この変更は、8ビットまたは9ビットのスロットにカウントを保持することによって引き起こされる文字列の長さの制限を部分的に回避するために行われました。
デニス・M・リッチー、C言語の開発
Cには、言語の一部として文字列がありません。 Cの「文字列」は、charへの単なるポインタです。だからあなたは間違った質問をしているのかもしれません。
「文字列型を除外する理由は何ですか」がより関連性があるかもしれません。それに対して、Cはオブジェクト指向言語ではなく、基本的な値型のみを持っていることを指摘します。文字列は、他の型の値を何らかの方法で組み合わせて実装する必要がある、より高いレベルの概念です。 Cは抽象化の下位レベルにあります。
これは馬鹿げた質問や悪い質問だと言っているのではなく、文字列を表現するCの方法が最良の選択であることを指摘したいだけです。 Cには、文字列をデータ型としてバイト配列と区別するメカニズムがないという事実を考慮すると、質問はより簡潔になります。これは、今日のコンピューターの処理能力とメモリ能力を考慮して、最良の選択ですか?おそらくない。しかし、後知恵は常に20/20であり、すべてが:)
質問はLength Prefixed Strings (LPS)
vs zero terminated strings (SZ)
のように求められますが、ほとんどの場合、接頭辞付きの長さの文字列の利点が明らかになります。それは圧倒的に思えるかもしれませんが、正直に言うと、LPSの欠点とSZの利点も考慮する必要があります。
私が理解しているように、質問は「ゼロ終了文字列の利点は何ですか?」と尋ねる偏った方法として理解されるかもしれません。
ゼロ終端文字列の利点(なるほど):
"this\0is\0valid\0C"
。文字列ですか?または4つの文字列?または大量のバイト...char a[3] = "foo";
は有効なC(C++ではない)であり、aに最後のゼロを挿入しません。char*
にキャストするときに非常に奇妙な動作をさせる必要があります。つまり、文字列のアドレスを返すのではなく、実際のデータを返すということです。そうは言っても、標準C文字列が実際に非効率的であるというまれなケースで文句を言う必要はありません。ライブラリが利用可能です。その傾向に従えば、標準Cには正規表現サポート関数が含まれていないことを不満に思うはずです...しかし、その目的で使用できるライブラリがあるため、それは実際の問題ではないことを誰もが知っています。したがって、文字列操作の効率が必要な場合、 bstring のようなライブラリを使用しないのはなぜですか?それともC++文字列ですか?
[〜#〜] edit [〜#〜]:最近、 D文字列 を見ました。選択したソリューションがサイズプレフィックスでもゼロ終端でもないことを確認するのは十分に興味深いです。 Cのように、二重引用符で囲まれたリテラル文字列は、不変のchar配列の省略形であり、言語にはそれを意味する文字列キーワードもあります(不変のchar配列)。
ただし、D配列はC配列よりもはるかに豊富です。静的配列の場合、実行時に長さがわかっているため、長さを保存する必要はありません。コンパイラはコンパイル時にそれを持っています。動的配列の場合、長さは利用可能ですが、Dのドキュメントには、どこに保存されるかは記載されていません。私たちが知っているすべてのことについて、コンパイラーは、それをあるレジスターに保持するか、文字データから遠く離れて格納されたある変数に保持するかを選択できます。
通常のchar配列または非リテラル文字列には最終的なゼロがないため、プログラマがDからC関数を呼び出したい場合、プログラマーはそれ自体を配置する必要があります。ただし、リテラル文字列の特定のケースでは、Dコンパイラはまだ各文字列の終わり(C文字列に簡単にキャストしてC関数を簡単に呼び出すことができるように?)が、このゼロは文字列の一部ではありません(Dは文字列サイズでカウントしません)。
私を失望させた唯一のことは、文字列がutf-8であることですが、マルチバイト文字を使用している場合でも、長さは明らかにバイト数を返します(少なくとも私のコンパイラgdcでは本当です)。それがコンパイラのバグなのか、それが目的なのかははっきりしていません。 (OK、私はおそらく何が起こったのかを知っているでしょう。Dコンパイラにutf-8を使用すると言うには、最初に愚かなバイトオーダーマークを付ける必要があります。特にUTF- 8 ASCII互換)。
歴史的な理由があり、 これはウィキペディアの :
C(およびその派生言語)が開発された時点では、メモリは非常に限られていたため、1バイトのオーバーヘッドを使用して文字列の長さを格納するのが魅力的でした。当時唯一の一般的な代替手段は、通常「パスカル文字列」と呼ばれていましたが(BASICの初期バージョンでも使用されていました)、先頭バイトを使用して文字列の長さを格納していました。これにより、文字列にNULを含めることができ、長さの検索に必要なメモリアクセスは1つだけです(O(1)(一定)時間)。しかし、1バイトは長さを255に制限します。この長さの制限はCストリングの問題よりもはるかに制限的であったため、一般にCストリングが勝ちました。
Calavera は right ですが、人々が彼の主張を理解していないように見えるため、コード例をいくつか示します。
最初に、Cが何であるかを考えてみましょう。すべてのコードが機械語にかなり直接翻訳されている単純な言語です。すべての型はレジスターとスタックに収まり、オペレーティングシステムまたは大きなランタイムライブラリを実行する必要はありません。これらのwriteこの日には競合他社が存在しないことを考えれば、非常に適したタスクです)。
Cがstring
やint
などのchar
型を持っている場合、それはレジスタまたはスタックに収まらない型であり、メモリの割り当てが必要になります(すべてのサポートインフラストラクチャを使用して)任意の方法で処理されます。これらはすべて、Cの基本的な教義に反します。
したがって、Cの文字列は次のとおりです。
char s*;
そのため、長さの接頭辞が付いていると仮定しましょう。 2つの文字列を連結するコードを書きましょう。
char* concat(char* s1, char* s2)
{
/* What? What is the type of the length of the string? */
int l1 = *(int*) s1;
/* How much? How much must I skip? */
char *s1s = s1 + sizeof(int);
int l2 = *(int*) s2;
char *s2s = s2 + sizeof(int);
int l3 = l1 + l2;
char *s3 = (char*) malloc(l3 + sizeof(int));
char *s3s = s3 + sizeof(int);
memcpy(s3s, s1s, l1);
memcpy(s3s + l1, s2s, l2);
*(int*) s3 = l3;
return s3;
}
別の代替方法は、構造体を使用して文字列を定義することです。
struct {
int len; /* cannot be left implementation-defined */
char* buf;
}
この時点で、すべての文字列操作には2つの割り当てが必要になります。これは、実際には、ライブラリを処理してそれを処理することを意味します。
面白いのは...そのような構造体がCに存在するdo!これらは、ユーザー処理への日々のメッセージの表示には使用されません。
したがって、Calaveraのポイントは次のとおりです。Cには文字列型はありません。それで何かをするためには、ポインタを取り、それを2つの異なる型へのポインタとしてデコードする必要があります。それは文字列のサイズと非常に関連性があり、「実装定義」として残すことはできません。
今、Ccanはとにかくメモリを処理し、ライブラリのmem
関数(<string.h>
、偶数!)メモリーをポインターとサイズのペアとして処理するために必要なすべてのツールを提供します。 Cのいわゆる "strings"は、たった1つの目的のために作成されました。テキスト端末向けのオペレーティングシステムを作成するコンテキストでメッセージを表示します。そして、そのためには、ヌル終端で十分です。
明らかにパフォーマンスと安全性のために、strlen
またはそれに相当するものを繰り返し実行するのではなく、文字列を操作している間、文字列の長さを維持したいでしょう。ただし、文字列の内容の直前に固定位置に長さを格納することは、非常に悪い設計です。 JörgenがSanjitの答えに対するコメントで指摘したように、文字列の末尾を文字列として扱うことはできません。たとえば、path_to_filename
または filename_to_extension
新しいメモリを割り当てることなく(および障害とエラー処理の可能性を招くことなく)不可能。そしてもちろん、文字列の長さフィールドが何バイトを占めるべきか誰も同意できないという問題があります(長い文字列の処理を妨げる16ビットフィールドまたは24ビットフィールドを使用する多くの悪い「パスカル文字列」言語)。
プログラマが長さを保存するかどうか、場所、方法を選択できるようにするCの設計は、はるかに柔軟で強力です。しかし、もちろんプログラマーは賢くなければなりません。 Cは、クラッシュしたり、停止したり、敵にルートを与えたりするプログラムで愚かさを罰します。
遅延、レジスターの質素性、および任意の言語のアセンブリー、特にアセンブリーの1ステップであるCを考慮した移植性(したがって、多くのアセンブリーのレガシー・コードを継承します)。ヌル文字はこれらのASCII日では役に立たないので、おそらく(EOF control char)と同じくらい良い)として同意するでしょう。
擬似コードで見てみましょう
function readString(string) // 1 parameter: 1 register or 1 stact entries
pointer=addressOf(string)
while(string[pointer]!=CONTROL_CHAR) do
read(string[pointer])
increment pointer
合計1レジスタ使用
ケース2
function readString(length,string) // 2 parameters: 2 register used or 2 stack entries
pointer=addressOf(string)
while(length>0) do
read(string[pointer])
increment pointer
decrement length
使用される合計2つのレジスタ
当時は近視眼的に見えるかもしれませんが、コードとレジスターのfru約を考えてみてください(当時はPREMIUMでしたが、当時はパンチカードを使用していました)。したがって、(プロセッサの速度をkHz単位でカウントできる場合)高速であるため、この「ハック」は非常に優れており、レジスタレスプロセッサに簡単に移植できます。
引数のために、2つの一般的な文字列操作を実装します
stringLength(string)
pointer=addressOf(string)
while(string[pointer]!=CONTROL_CHAR) do
increment pointer
return pointer-addressOf(string)
複雑さO(n)ここで、ほとんどの場合、パスカル文字列はO(1)です。これは、文字列の長さが文字列構造(また、この操作はより早い段階で実行する必要があることを意味します)。
concatString(string1,string2)
length1=stringLength(string1)
length2=stringLength(string2)
string3=allocate(string1+string2)
pointer1=addressOf(string1)
pointer3=addressOf(string3)
while(string1[pointer1]!=CONTROL_CHAR) do
string3[pointer3]=string1[pointer1]
increment pointer3
increment pointer1
pointer2=addressOf(string2)
while(string2[pointer2]!=CONTROL_CHAR) do
string3[pointer3]=string2[pointer2]
increment pointer3
increment pointer1
return string3
複雑さO(n)で、文字列の長さを追加しても操作の複雑さは変わりませんが、時間は3倍短くなります。
一方、Pascal文字列を使用する場合、レジスタの長さとビットエンディアンを考慮してAPIを再設計する必要があります。Pascal文字列は、長さが1バイト(8ビット)で格納されているため、255文字(0xFF) )、さらに長い文字列(16ビット->何でも)が必要な場合は、コードの1つのレイヤーのアーキテクチャを考慮する必要があります。これは、長い文字列が必要な場合、ほとんどの場合、互換性のない文字列APIを意味します.
例:
1つのファイルは、8ビットコンピューターで追加された文字列APIで書き込まれ、32ビットコンピューターで読み取る必要があります。レイジープログラムは、4バイトが文字列の長さであると見なし、その多くのメモリを割り当てますその後、そのバイト数を読み取ろうとします。別の場合は、PPC 32バイト文字列読み取り(リトルエンディアン)をx86(ビッグエンディアン)に読み込みます。もちろん、一方が他方によって書き込まれていることがわからない場合は、問題が発生します。 1バイトの長さ(0x00000001)は16777216(0x0100000)になり、1バイトの文字列を読み取るための16 MBです。もちろん、1つの標準に同意する必要がありますが、16ビットのユニコードでもほとんどエンディアンではありません。
もちろん、Cにも問題がありますが、ここで提起された問題による影響はほとんどありません。
多くの点で、Cは原始的でした。そして、私はそれを愛していました。
これはアセンブリ言語よりも上位のステップであり、記述と保守がはるかに簡単な言語とほぼ同じパフォーマンスを提供します。
Nullターミネータは単純であり、言語による特別なサポートは必要ありません。
振り返ってみると、それほど便利ではないようです。しかし、私は80年代にアセンブリ言語を使用していましたが、当時は非常に便利でした。ソフトウェアは絶えず進化しており、プラットフォームとツールはますます洗練されていると思います。
Cが文字列をPascalの方法で実装したと仮定すると、長さで接頭辞を付けることにより、7文字の文字列は3文字の文字列と同じデータ型ですか?答えが「はい」の場合、前者を後者に割り当てるときにコンパイラはどのようなコードを生成する必要がありますか?文字列は切り捨てられるべきですか、それとも自動的にサイズ変更されますか?サイズを変更する場合、スレッドセーフにするために、その操作をロックで保護する必要がありますか? Cアプローチ側は、好むと好まざるとにかかわらず、これらのすべての問題を回避しました。
どういうわけか私は質問を理解し、Cの長さ接頭辞付き文字列のコンパイラサポートがないことを意味します。次の例は、少なくとも、次のような構成で、文字列の長さがコンパイル時にカウントされる独自のC文字列ライブラリを開始できることを示しています:
#define PREFIX_STR(s) ((prefix_str_t){ sizeof(s)-1, (s) })
typedef struct { int n; char * p; } prefix_str_t;
int main() {
prefix_str_t string1, string2;
string1 = PREFIX_STR("Hello!");
string2 = PREFIX_STR("Allows \0 chars (even if printf directly doesn't)");
printf("%d %s\n", string1.n, string1.p); /* prints: "6 Hello!" */
printf("%d %s\n", string2.n, string2.p); /* prints: "48 Allows " */
return 0;
}
ただし、文字列ポインタを具体的に解放するときと、静的に割り当てられたとき(リテラルchar
配列)に注意する必要があるため、これには問題はありません。
編集:質問に対するより直接的な答えとして、私の意見では、これはCが文字列の長さを(コンパイル時定数として)利用できる両方をサポートする方法であり、必要であれば、ポインタとゼロ終端のみを使用する場合のメモリオーバーヘッド。
もちろん、標準ライブラリは一般に文字列の長さを引数として受け取らないため、長さの抽出はchar * s = "abc"
のような単純なコードではないため、ゼロで終了する文字列を使用することをお勧めします。私の例が示しています。
「32ビットマシンでも、文字列を使用可能なメモリのサイズにすることができる場合、長さの接頭辞付き文字列は、nullで終わる文字列よりも3バイトだけ広くなります。」
まず、短い文字列の場合、余分な3バイトがかなりのオーバーヘッドになる可能性があります。特に、長さゼロの文字列は4倍のメモリを使用するようになりました。一部のユーザーは64ビットマシンを使用しているため、長さゼロの文字列を格納するために8バイトが必要か、プラットフォームがサポートする最長の文字列に文字列形式が対応できません。
対処するアライメントの問題もあるかもしれません。 「solo\0second\0\0four\0five\0\0seventh」のような7つの文字列を含むメモリブロックがあるとします。 2番目の文字列はオフセット5から始まります。ハードウェアは4の倍数のアドレスに32ビット整数を揃える必要があるため、パディングを追加する必要があり、オーバーヘッドがさらに増加します。 C表現は、比較するとメモリ効率が非常に高くなります。 (メモリ効率は優れています。たとえば、キャッシュのパフォーマンスに役立ちます。)
まだ言及されていない点が1つあります。Cが設計されたとき、「char」が8ビットではないマシンがたくさんありました(今日でもそうでないDSPプラットフォームがあります)。文字列に長さ接頭辞を付けると決定した場合、「char」の長さの接頭辞をいくつ使用する必要がありますか? 2を使用すると、8ビット文字と32ビットのアドレス空間を持つマシンの文字列長に人為的な制限が課され、16ビット文字と16ビットのアドレス空間を持つマシンのスペースが無駄になります。
任意の長さの文字列を効率的に保存したい場合、および 'char'が常に8ビットの場合、速度とコードサイズを多少犠牲にして、偶数のプレフィックスが付いた文字列をスキームとして定義できますNはN/2バイト長で、奇数値Nと偶数値M(逆読み)が前に付いた文字列は((N-1)+ M * char_max)/ 2などになります。文字列を保持するために一定量のスペースを提供すると主張する場合、そのスペースに先行して十分なバイト数が最大長を処理できるようにする必要があります。ただし、「char」が常に8ビットであるとは限らないため、文字列の長さを保持するのに必要な「char」の数はCPUアーキテクチャによって異なるため、このようなスキームは複雑になります。
Null終了により、高速なポインターベースの操作が可能になります。
このブログ投稿 のJoel Spolskyによると、
それは、UNIXとCプログラミング言語が発明されたPDP-7マイクロプロセッサがASCIZ文字列型を持っているためです。 ASCIZは、「最後にZ(ゼロ)が付いたASCII」を意味しました。
ここで他のすべての答えを見た後、これが本当であっても、Cがヌルで終わる「文字列」を持つ理由の一部にすぎないと確信しています。この投稿は、文字列のような単純なものが実際に非常に難しいことがあるということを非常に明らかにしています。
理論的根拠ではなくであるが、長さエンコードのカウンターポイント
メモリに関する限り、特定の形式の動的な長さのエンコーディングは静的な長さのエンコーディングよりも優れており、すべて使用法に依存します。証拠としてUTF-8を見てください。基本的に、単一の文字をエンコードするための拡張可能な文字配列です。これは、拡張バイトごとに1ビットを使用します。 NUL終端は8ビットを使用します。長さプレフィックス64ビットを使用することで、合理的に無限長と呼ぶこともできます。余分なビットのケースをヒットする頻度が決定要因です。極端に大きな文字列は1つだけですか? 8ビットまたは64ビットを使用している場合、誰が気にしますか?多くの小さな文字列(つまり英語の単語の文字列)?その場合、プレフィックスコストは大きな割合になります。
時間を節約できる長さの接頭辞付き文字列は本物ではないです。提供されたデータに長さを提供する必要があるかどうか、コンパイル時にカウントするか、文字列としてエンコードする必要がある動的データが本当に提供されるかどうか。これらのサイズは、アルゴリズムのある時点で計算されます。 nullで終了する文字列のサイズを格納する別の変数を提供できます。これにより、時間の節約が無駄になります。最後に余分なNULがあります...しかし、長さエンコードにそのNULが含まれていない場合、文字通り2つの間に違いはありません。アルゴリズムの変更はまったく必要ありません。コンパイラ/ランタイムに実行させる代わりに、手動で自分で設計する必要があるだけです。 Cは主に手動で物事を行うことについてです。
オプションの長さプレフィックスはセールスポイントです。アルゴリズムの追加情報は必ずしも必要ではないので、すべての文字列に対してそれを行う必要があるため、事前計算+計算時間がO(n)を下回ることはありません。 (つまり、ハードウェア乱数ジェネレータ1-128。「無限の文字列」から引き出すことができます。文字列を非常に高速に生成するとします。したがって、文字列の長さは常に変化します。多くのランダムなバイトがあります。リクエスト後に取得できる次の未使用バイトが必要です。デバイスで待機することもできますが、文字のバッファを先読みすることもできます。無駄な計算の無駄。nullチェックの方が効率的です。)
長さプレフィックスは、バッファオーバーフローに対する適切な保護手段ですか?ライブラリ関数の適切な使用と実装も同様です。不正なデータを渡すとどうなりますか?私のバッファの長さは2バイトですが、関数に7であることを伝えます! 例:gets()が既知のデータで使用されることを意図していた場合、テストした内部バッファチェックがあった可能性がありますコンパイルされたバッファとmalloc()呼び出しで、まだ仕様に従います。未知のSTDINが未知のバッファに到達するためのパイプとして使用することを意図していた場合、バッファサイズがわからないことは明らかです。これは、長さargが無意味であることを意味します。さらに言えば、一部のストリームと入力に長さプレフィックスを付けることはできません。できません。つまり、長さのチェックはアルゴリズムに組み込まれる必要があり、タイピングシステムの魔法の部分ではありません。 TL; DR NUL終了は決して安全である必要はありませんでした。
counter-counter point: NULターミネーションはバイナリで迷惑です。ここでlength-prefixを実行するか、何らかの方法でNULバイトを変換する必要があります:エスケープコード、範囲の再マッピングなど。これはもちろん、より多くのメモリ使用量/削減された情報/より多くの操作/バイトを意味します。ここでは、長さプレフィックスが主に戦争に勝ちます。変換の唯一の利点は、長さプレフィックス文字列をカバーするために追加の関数を記述する必要がないことです。つまり、より最適化されたsub-O(n)ルーチンでは、コードを追加することなく、それらをO(n)同等のものとして自動的に動作させることができます。欠点は、もちろん時間/メモリ/ NULの重い文字列で使用した場合の圧縮の無駄バイナリデータを操作するために複製するライブラリの量に応じて、長さプレフィックス文字列だけで作業することは理にかなっているかもしれません。長さプレフィックス文字列でも同じことができます... -1長さはNUL終了を意味し、NUL終了文字列をlength-terminated内で使用できます。
連結: "O(n + m)vs O(m)"連結後の文字列の全長としてmを参照していると仮定しています両方ともその数の操作を最小限に抑える必要があるためです(文字列1にタックオンすることはできません。再割り当てが必要な場合はどうなりますか?)。また、nは、事前計算のために必要のない神話的な量の操作であると想定しています。その場合、答えは簡単です:事前計算。 Ifあなたは常に再割り当てする必要のない十分なメモリがあると主張しており、それがbig-O表記の基礎ですより簡単:文字列1の終わりに割り当てられたメモリでバイナリ検索を実行します。明らかに、文字列1の後に無限のゼロの大きな見本があり、reallocを心配しません。そこで、log(n)に簡単にnを追加しましたが、やっと試してみました。 log(n)を思い出すと、実際のコンピューターでは本質的に64に過ぎません。これは、本質的にO(m)であるO(64 + m)と言っているようなものです。 (そして、はい、そのロジックは、今日使用されているrealデータ構造の実行時分析で使用されています。頭のてっぺんではありません。 )
Concat()/ Len() 再び:結果をメモします。簡単です。可能/必要に応じて、すべての計算を事前計算に変換します。これはアルゴリズムの決定です。言語の強制的な制約ではありません。
NUL終端を使用すると、文字列の接尾辞の受け渡しが簡単/可能になります。 length-prefixの実装方法によっては、元の文字列を破壊する可能性があり、不可能な場合もあります。コピーを要求し、O(1)の代わりにO(n)を渡す。
引数の受け渡し/逆参照は、NUL終了の場合と長さの接頭辞の場合の方が少ない明らかに、渡す情報が少ないためです。長さが必要ない場合は、これにより多くのフットプリントが節約され、最適化が可能になります。
チートできます。それは本当に単なるポインタです。あなたはそれを文字列として読む必要があると誰が言いますか?単一の文字またはフロートとして読みたい場合はどうしますか?逆を行い、フロートを文字列として読み取りたい場合はどうなりますか?注意が必要な場合は、NUL終了でこれを行うことができます。 length-prefixを使用してこれを行うことはできません。通常はポインターとは明らかに異なるデータ型です。ほとんどの場合、文字列をバイト単位で作成し、長さを取得する必要があります。もちろん、entirefloat(おそらく内部にNULがある)のようなものが必要な場合は、とにかくバイト単位で読み取る必要がありますが、詳細は決定するためにあなたに任されています。
TL; DRバイナリデータを使用していますか?いいえの場合、NUL終端によりアルゴリズムの自由度が高まります。はいの場合、コード量対速度/メモリ/圧縮が主な関心事です。 2つのアプローチまたはメモ化のブレンドが最適かもしれません。
Cを取り巻く多くの設計上の決定は、最初に実装されたとき、パラメータの受け渡しがいくぶん高価だったという事実に起因しています。たとえば、.
void add_element_to_next(arr, offset)
char[] arr;
int offset;
{
arr[offset] += arr[offset+1];
}
char array[40];
void test()
{
for (i=0; i<39; i++)
add_element_to_next(array, i);
}
versus
void add_element_to_next(ptr)
char *p;
{
p[0]+=p[1];
}
char array[40];
void test()
{
int i;
for (i=0; i<39; i++)
add_element_to_next(arr+i);
}
後者は、2つではなく1つのパラメーターを渡すだけでよいため、わずかに安くなりました(したがって、推奨されました)。呼び出されるメソッドが配列のベースアドレスやその中のインデックスを知る必要がない場合、2つを組み合わせた単一のポインタを渡す方が値を個別に渡すよりも安価です。
Cが文字列の長さをエンコードできる多くの合理的な方法がありますが、それまでに発明されたアプローチには、文字列のベースアドレスを受け入れるために文字列の一部を操作できる必要なすべての機能があります。 2つの別個のパラメーターとしての目的のインデックス。ゼロバイトの終端を使用することで、その要件を回避できました。他のアプローチは今日のマシンでより良いでしょう(現代のコンパイラはしばしばレジスタにパラメーターを渡し、memcpyはstrcpy()と同等の方法では最適化できません)十分な量産コードはゼロに終端された文字列を使用します。
PS--一部の操作でわずかな速度のペナルティと、より長い文字列でのわずかな余分なオーバーヘッドと引き換えに、文字列で動作するメソッドに文字列へのポインタを直接受け入れることができたはずですbounds-checked文字列バッファー、または別の文字列の部分文字列を識別するデータ構造。 「strcat」のような関数は、[現代の構文]のようなものに見えるでしょう。
void strcat(unsigned char *dest, unsigned char *src)
{
struct STRING_INFO d,s;
str_size_t copy_length;
get_string_info(&d, dest);
get_string_info(&s, src);
if (d.si_buff_size > d.si_length) // Destination is resizable buffer
{
copy_length = d.si_buff_size - d.si_length;
if (s.src_length < copy_length)
copy_length = s.src_length;
memcpy(d.buff + d.si_length, s.buff, copy_length);
d.si_length += copy_length;
update_string_length(&d);
}
}
K&R strcatメソッドより少し大きいですが、境界チェックをサポートしますが、K&Rメソッドはサポートしません。さらに、現在の方法とは異なり、任意の部分文字列を簡単に連結することが可能です。
/* Concatenate 10th through 24th characters from src to dest */
void catpart(unsigned char *dest, unsigned char *src)
{
struct SUBSTRING_INFO *inf;
src = temp_substring(&inf, src, 10, 24);
strcat(dest, src);
}
Temp_substringによって返される文字列のライフタイムは、s
およびsrc
のライフタイムによって制限されることに注意してください。これは、メソッドが渡されるのにinf
を必要とする理由です。 )。
メモリコストの観点から、最大64バイトの文字列とバッファには1バイトのオーバーヘッドがあります(ゼロで終わる文字列と同じ)。長い文字列は、わずかに多くなります(2バイト間で許容されるオーバーヘッドの量と、必要な最大値が時間/スペースのトレードオフになるかどうか)。長さ/モードバイトの特別な値は、文字列関数にフラグバイト、ポインタ、およびバッファ長(他の文字列に任意にインデックスを付けることができる)を含む構造が与えられたことを示すために使用されます。
もちろん、K&Rはそのようなものを実装しませんでしたが、それはおそらく文字列処理に多くの労力を費やしたくないためです-今日でも多くの言語がかなり貧弱に見える領域です。