web-dev-qa-db-ja.com

数値が大きすぎる場合、次のメモリ位置に波及しますか?

私はCプログラミングをレビューしてきましたが、気になることがいくつかあります。

このコードを例にとってみましょう:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

Intが最大値の正の2,147,483,647を保持できることを知っています。では、1つ先に進むと、次のメモリアドレスに「スピルオーバー」して、そのアドレスで要素2が「-2147483648」として表示されるのでしょうか。しかし、出力では次のアドレスが値4、5を保持しているとまだ出力されているので、それは実際には意味がありません。数値が次のアドレスに波及した場合、そのアドレスに格納されている値は変更されません。 ?

MIPSアセンブリでのプログラミングと、プログラム中にアドレスが値を変更するのを、それらのアドレスに割り当てられた値が変化することをぼんやりと覚えています。

私が間違って覚えていない限り、ここに別の質問があります:特定のアドレスに割り当てられた番号がタイプよりも大きい場合(myArray [2]のように)、その後のアドレスに格納されている値に影響しませんか?

例:アドレス0x10010000にint myNum = 40億があります。もちろん、myNumは40億を格納できないため、そのアドレスでは負の数として表示されます。この大きな数を格納できないにもかかわらず、0x10010004の後続のアドレスに格納されている値には影響しません。正しい?

メモリアドレスは、特定のサイズの数字/文字を保持するのに十分なスペースがあります。サイズが制限を超えると、異なる方法で表示されます(intに40億を格納しようとするが、負の数として表示されます)。そのため、次のアドレスに格納されている数字や文字には影響しません。

船外に行ったらすみません。私はこれから一日中、主要な脳オナラをしている。

29
stumpy

いいえ、違います。 Cでは、変数には、処理するメモリアドレスの固定セットがあります。 4バイトのintsを使用するシステムで作業していて、int変数を2,147,483,647に設定してから1を追加すると、変数には通常-2147483648が含まれます。 (ほとんどのシステムで。動作は実際には未定義です。)他のメモリ位置は変更されません。

本質的に、コンパイラーは型に対して大きすぎる値を割り当てることを許可しません。これにより、コンパイラエラーが生成されます。大文字と小文字を区別すると、値は切り捨てられます。

ビット単位で見ると、型が8ビットしか格納できない場合、値に1010101010101を大文字と小文字で強制しようとすると、最終的に下位8ビット、つまり01010101になります。

あなたの例では、myArray[2]に何をしたかに関係なく、myArray[3]には「4」が含まれます。 「スピルオーバー」はありません。あなたは4バイト以上のものを入れようとしています、それはハイエンドですべてを取り除き、下の4バイトを残します。ほとんどのシステムでは、これは-2147483648になります。

実用的な観点からは、これが決して起こらないことを確認したいだけです。これらの種類のオーバーフローは、多くの場合、解決が難しい欠陥をもたらします。つまり、値が数十億になる可能性があると考えられる場合は、intを使用しないでください。

48
Gort the Robot

符号付き整数オーバーフローは未定義の動作です。これが発生した場合、プログラムは無効です。コンパイラーはこれを確認する必要がないため、妥当なことを行うように見える実行可能ファイルを生成する可能性がありますが、そうなる保証はありません。

ただし、符号なし整数オーバーフローは明確に定義されています。これはUINT_MAX + 1を法としてラップします。変数が占有していないメモリは影響を受けません。

参照 https://stackoverflow.com/q/18195715/95189

24
Vaughn Cato

したがって、ここに2つあります。

  • 言語レベル:Cのセマンティクスとは
  • マシンレベル:使用するアセンブリ/ CPUのセマンティクスは何ですか

言語レベル:

C:

  • オーバーフローとアンダーフローはnsigned整数のモジュロ演算として定義されているため、それらの値は「ループ」
  • オーバーフローとアンダーフローはndefined Behavior for signed integersなので、何でも起こります

「何でも」の例が欲しい人のために、私は見ました:

for (int i = 0; i >= 0; i++) {
    ...
}

に変わります:

for (int i = 0; true; i++) {
    ...
}

はい、これは正当な変化です。

これは、奇妙なコンパイラの変換が原因で、オーバーフロー時にメモリを上書きする潜在的なリスクが実際に存在することを意味します。

注:Clangまたはgccでは、デバッグで-fsanitize=undefinedを使用して ndefined Behavior Sanitizer をアクティブにします。これにより、符号付き整数のアンダーフロー/オーバーフローが発生します。

または、配列へのインデックス付け(チェックされていない)の操作の結果を使用してメモリを上書きできることを意味します。残念ながら、これはアンダーフロー/オーバーフローの検出がない場合の可能性がはるかに高くなります。

注:Clangまたはgccでは、デバッグで-fsanitize=addressを使用して Address Sanitizer をアクティブにします。これにより、範囲外のアクセスで中止されます。


マシンレベル

実際に使用するアセンブリ命令とCPUによって異なります。

  • x86では、 [〜#〜] add [〜#〜] はオーバーフロー/アンダーフローで2の補数を使用し、OF(オーバーフローフラグ)を設定します
  • 将来のMill CPUでは、Add:に4つの異なるオーバーフローモードがあります。
    • モジュロ:2補数のモジュロ
    • トラップ:トラップが生成され、計算が停止します
    • 飽和:アンダーフローで最小値またはオーバーフローで最大値にスタックする
    • 倍幅:結果は倍幅レジスタで生成されます

レジスタまたはメモリのどちらで問題が発生しても、オーバーフロー時にCPUがメモリを上書きすることはありません。

14
Matthieu M.

@StevenBurnapの答えをさらに進めるために、これが発生する理由は、コンピューターレベルでのコンピューターの動作方法にあります。

アレイはメモリ(RAMなど)に保存されます。算術演算が実行されると、メモリ内の値が、演算を実行する回路の入力レジスタにコピーされます(ALU: 算術論理演算ユニット )。その後、演算は入力レジスタ。出力レジスタに結果を生成します。その後、この結果はメモリ内の正しいアドレスにコピーされ、メモリの他の領域はそのまま残ります。

4
Pharap

最初に(C99標準を想定)、 <stdint.h> 標準ヘッダーを含めて、そこで定義されているタイプの一部、特にint32_tを使用することができます。またはuint64_tは、64ビットの符号なし整数などです。パフォーマンス上の理由から、int_fast16_tのようなタイプを使用したい場合があります。

符号なしの算術演算が隣接するメモリ位置にこぼれない(またはオーバーフローしない)ことを説明する他の回答を読んでください。 未定義の動作signedオーバーフローに注意してください。

次に、exactlyの巨大な整数を計算する必要がある場合(たとえば、すべての2568桁が10進数の1000の階乗を計算する場合)、bigintsが必要です。別名 任意の精度の数値 (またはbignums)。効率的なbigint演算のアルゴリズムは非常に賢く、通常は専用の機械命令を使用する必要があります(たとえば、プロセッサーにキャリーがある場合は、キャリー付きのWordを追加するものもあります)。したがって、その場合は GMPlib のようないくつかのexistingbigintライブラリを使用することを強くお勧めします