web-dev-qa-db-ja.com

12バイト未満のバッファを12バイトバッファに書き込むとどうなりますか?

当然のことながら、バッファーを超えるとエラーが発生します(またはオーバーフローが発生します)が、12バイトのバッファーで使用されているのが12バイト未満の場合はどうなりますか?それは可能ですか、空の末尾は常に0で埋められますか?役立つ可能性のある直交質問:インスタンス化されたが、まだアプリケーションで使用されていない場合、バッファには何が含まれていますか?

Visual Studioでいくつかのペットプログラムを調べましたが、0(またはヌル文字)が付加されているようですが、これが言語/コンパイラによって異なるMS実装であるかどうかはわかりません。

28
hexadec0079

ゼロで満たされたバッファを検討してください。

[00][00][00][00][00][00][00][00][00][00][00][00]

それでは、10バイトを書き込みましょう。 1から増加する値

[01][02][03][04][05][06][07][08][09][10][00][00]

そして今度は、今回は0xFFの4倍です。

[FF][FF][FF][FF][05][06][07][08][09][10][00][00]

12バイトバッファーで使用される12バイト未満の場合はどうなりますか?それは可能ですか、空の末尾は常に0で埋められますか?

必要なだけ書き込むと、残りのバイトは変更されません。

役立つ可能性のある直交質問:インスタンス化されたが、まだアプリケーションで使用されていない場合、バッファには何が含まれていますか?

指定なし。以前にこのメモリを使用したプログラム(またはプログラムの他の部分)によって残されたジャンクを予期します。

Visual Studioでいくつかのペットプログラムを調べましたが、0(またはヌル文字)が付加されているようですが、これが言語/コンパイラによって異なる可能性のあるMS実装かどうかはわかりません。

それはまさにあなたがそれがそう思うものです。誰かが今回あなたのためにそれをしたが、それが再び起こるという保証はない。クリーニングコードを添付するコンパイラフラグである可能性があります。 MSVCの一部のバージョンは、デバッグでは実行されたがリリースでは実行されなかったときに、新しいメモリを0xCDで埋めていました。また、メモリをプロセスに渡す前に消去するシステムセキュリティ機能にすることもできます(したがって、他のアプリをスパイすることはできません)。 memsetを使用して、重要な場所でバッファを初期化することを常に忘れないでください。最終的に、特定の値を含めるために新しいバッファに依存している場合は、readmeで特定のコンパイラフラグを使用することを義務付けます。

しかし、クリーニングは本当に必要ではありません。 12バイト長のバッファーを使用します。 7バイトで埋めます。それからどこかにそれを渡します-そして、あなたは「ここにあなたのための7バイトがあります」と言います。バッファのサイズは、バッファから読み取るときに関係ありません。他の関数は、できる限りではなく、記述したとおりに読み取ることを期待します。実際、Cでは、通常、バッファーの長さを知ることはできません。

サイドノート:

当然、バッファを超えるとエラーが発生します(またはオーバーフローが発生します)

そうではありません、それが問題です。これが大きなセキュリティ問題である理由です。エラーは発生せず、プログラムは続行しようとするため、意図しない悪意のあるコンテンツを実行する場合があります。そのため、ASLRなど、プログラムがクラッシュする可能性を増やし、破損したメモリで継続する可能性を減らすメカニズムをOSに追加する必要がありました。したがって、後付けのガードに依存せず、自分でバッファ境界を監視してください。

11
Agent_L

次の例をご覧ください(グローバルではなく、コードブロック内):

char data[12];
memcpy(data, "Selbie", 6);

またはこの例でも:

char* data = new char[12];
memcpy(data, "Selbie", 6);

上記のどちらの場合でも、dataの最初の6バイトはSelbi、およびedataの残りの6バイトは「指定なし」と見なされます(何でもかまいません)。

それは可能ですか、空の末尾は常に0で埋められますか?

まったく保証されていません。私が知っている唯一のアロケーターは、ゼロバイトの充填を保証する calloc です。例:

char* data = calloc(12,1);  // will allocate an array of 12 bytes and zero-init each byte
memcpy(data, "Selbie");

インスタンス化されたが、アプリケーションでまだ使用されていない場合、バッファには何が含まれていますか?

技術的には、最新のC++標準に従って、アロケータによって配信されるバイトは技術的には「指定なし」と見なされます。ガベージデータ(何でも)であると想定する必要があります。コンテンツについて何も仮定しません。

Visual Studioを使用したビルドのデバッグでは、多くの場合、0xcc または 0xcd値ですが、リリースビルドの場合はそうではありません。ただし、WindowsおよびVisual Studioには、ゼロ初期化メモリ割り当てを保証できるコンパイラフラグとメモリ割り当て技術がありますが、移植性はありません。

17
selbie

C++には、グローバル、自動、静的などのストレージクラスがあります。初期化は、変数の宣言方法によって異なります。

char global[12];  // all 0
static char s_global[12]; // all 0

void foo()
{
   static char s_local[12]; // all 0
   char local[12]; // automatic storage variables are uninitialized, accessing before initialization is undefined behavior 
}

いくつかの興味深い詳細 ここ

11
Matthew Fisher

プログラムは、値がゼロの文字であるヌルターミネータで終了するため、文字列の長さを認識しています。

これが、文字列をバッファに収めるには、文字列とヌルターミネータを合わせるために、バッファが文字列の文字数より少なくとも1文字長くなければならない理由です。

バッファ内のそれ以降のスペースは変更されません。以前にデータがあった場合、それはまだそこにあります。これは、ごみと呼ばれるものです。

まだ使用していないという理由だけで、このスペースがゼロで埋められていると仮定するのは間違っています。プログラムがその時点に到達する前に、その特定のメモリスペースが何に使用されたのかわかりません。初期化されていないメモリは、その中にあるものがランダムで信頼できないかのように処理する必要があります。

4
Havenard

これまでの回答はすべて非常に優れており、非常に詳細ですが、OPはCプログラミングの新機能のようです。したがって、Real Worldの例が役立つと思いました。

6本のボトルを収納できる段ボール飲料ホルダーがあるとします。ガレージの中に座っているので、6本のボトルの代わりに、ガレージの隅に蓄積するさまざまな不快なものが含まれています:クモ、ネズミの家など.

コンピュータバッファは、割り当てた直後は少し似ています。あなたは本当に何が入っているのか確信が持てません、あなたはそれがどれだけ大きいかを知っているだけです。

では、ホルダーに4本のボトルを入れたとしましょう。ホルダーのサイズは変更されていませんが、4つのスペースに何があるかがわかりました。疑わしい内容を備えた他の2つのスペースは、まだそこにあります。

コンピューターのバッファーも同じ方法です。そのため、バッファの使用量を追跡するためにbufferSize変数が頻繁に表示されます。より良い名前はnumberOfBytesUsedInMyBufferかもしれませんが、プログラマーはとてつもなく簡潔になる傾向があります。

3
Doug Clutter

バッファの一部を書き込んでも、バッファの未書き込み部分には影響しません。事前にそこにあったものがすべて含まれます(最初にバッファを取得した方法に完全に依存します)。

他の回答ノートにあるように、静的変数とグローバル変数は0に初期化されますが、ローカル変数は初期化されません(代わりに事前にスタックにあったものが含まれます)。これはゼロオーバーヘッドの原則に沿ったものです。ローカル変数の初期化は、場合によっては不要で不要な実行時コストになりますが、静的変数とグローバル変数はデータセグメントの一部としてロード時に割り当てられます。

ヒープストレージの初期化はメモリマネージャのオプションですが、一般的には初期化もされません。

2
comingstorm

一般に、バッファがいっぱいになることはまったく珍しいことではありません。多くの場合、必要以上に大きなバッファを割り当てることをお勧めします。 (常に正確なバッファサイズを計算しようとすると、エラーが頻繁に発生し、多くの場合時間の無駄になります。)

バッファが必要以上に大きい場合、バッファに割り当てられたサイズよりも少ないデータが含まれている場合、明らかにそこにあるデータの量を追跡することが重要ですis一般に、これを行うには2つの方法があります。(1)別個の変数に保持されている明示的なカウント、または(2)\0文字などの「センチネル」値を使用して、 Cの文字列.

しかし、バッファのすべてが使用されているわけではない場合、未使用のエントリには何が含まれていますかという質問がありますか?

もちろん、1つの答えは、問題ではないです。それが「未使用」の意味です。使用されるエントリの値は、カウントまたはセンチネル値によって考慮されます。未使用の値は気にしません。

基本的に、バッファ内の未使用エントリの初期値を予測できる4つの状況があります。

  1. static durationで配列(文字配列を含む)を割り当てると、未使用のエントリはすべて0に初期化されます。

  2. 配列を割り当てて明示的な初期化子を与えると、未使用のエントリはすべて0に初期化されます。

  3. callocを呼び出すと、割り当てられたメモリはall-bits-0に初期化されます。

  4. strncpyを呼び出すと、宛先文字列は、サイズがnになるように\0文字で埋め込まれます。

他のすべての場合、バッファの未使用部分は予測不能であり、一般的に前回行ったもの(それが何を意味するものでも)を含んでいます。特に、自動期間(つまり、関数に対してローカルでstaticで宣言されていないもの)で初期化されていない配列の内容を予測することはできません。また、mallocで取得したメモリの内容を予測することはできません。 (時々、これらの2つのケースでは、メモリは最初はすべてビット0として開始する傾向がありますが、これに依存することは絶対にしたくないでしょう。)

1
Steve Summit

指定された初期化子を持たない静的な期間の宣言されたオブジェクト(関数の外側またはstatic修飾子で宣言されたオブジェクト)は、リテラルゼロで表される値に初期化されます(つまり、必要に応じて、整数ゼロ、浮動小数点ゼロ、またはNULLポインター、またはそのような値を含む構造体または共用体]。オブジェクト(自動期間の宣言を含む)の宣言に初期化子が含まれる場合、その初期化子によって値が指定される部分は指定どおりに設定され、残りは静的オブジェクトと同様にゼロに設定されます。

初期化子のない自動オブジェクトの場合、状況はやや曖昧です。次のようなものを考えます:

#include <string.h>

unsigned char static1[5], static2[5];

void test(void)
{
  unsigned char temp[5];
  strcpy(temp, "Hey");
  memcpy(static1, temp, 5);
  memcpy(static2, temp, 5);
}

標準では、testの初期化されていない部分をコピーしても、tempが未定義の動作を呼び出さないことは明らかです。少なくともC11の時点で、標準のテキストは、static1[4]およびstatic2[4]の値について保証されているかどうか、特に異なる値を保持している可能性があるかどうかについては明確ではありません。欠陥レポートは、標準がコンパイラがコードがあったかのように振る舞うことを禁止することを意図していなかったと述べています:

unsigned char static1[5]={1,1,1,1,1}, static2[5]={2,2,2,2,2};

void test(void)
{
  unsigned char temp[4];
  strcpy(temp, "Hey");
  memcpy(static1, temp, 4);
  memcpy(static2, temp, 4);
}

static1[4]static2[4]が異なる値を保持する可能性があります。規格では、さまざまな目的で品質コンパイラを使用するかどうかについては言及していませんshouldはその関数で動作します。規格は、プログラマがstatic1[4]static2[4]に同じ値を保持することを要求するが、その値が何であるかを気にする必要がない場合、関数の記述方法に関するガイダンスも提供しません。

1
supercat

ストレージクラス指定子、実装、およびその設定によって異なります。いくつかの興味深い例:-初期化されていないスタック変数は0xCCCCCCCCに設定できます-初期化されていないヒープ変数は0xCDCDCDCDに設定できます-未初期化の静的またはグローバル変数は0x00000000に設定できます-またはゴミ。これのいずれかについて仮定をすることは危険です。

1
Tim Randall

正しい答えは、書かれている文字数を常に追跡する必要があるということだと思います。読み取りや書き込みなどの低レベル関数と同様に、読み取りまたは書き込みの文字数を指定する必要があります。同様に、std :: stringはその実装の文字数を追跡します

1
izulh