数値が大きすぎる場合、次のメモリ位置にあふれますか?


30

私は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の最大値を保持できることを知っています。それで、それを越えて、次のメモリアドレスに「あふれ」、そのアドレスで要素2が「-2147483648」として表示されるのでしょうか。しかし、出力では次のアドレスが値4、5を保持していることを示しているため、それは実際には意味がありません。数値が次のアドレスにあふれた場合、そのアドレスに格納されている値は変更されません?

MIPS Assemblyでのプログラミングと、プログラム中にアドレスが値を変更するのを見て、それらのアドレスに割り当てられた値が変わることを漠然と覚えています。

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

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

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

船外に行ったら申し訳ありません。私はこれから一日中大きな脳のおならをしていました。


10
文字列のオーバーランと混同される可能性があります。
ロビーディー

19
宿題:単純なCPUを変更して、流出しないようにます。ロジックははるかに複雑になり、最初は役に立たずにあらゆる場所でセキュリティホールを保証する「機能」が必要になります。
フィハグ

4
本当に大きな数値が必要な場合は、大きな数値を収めるために使用するメモリ量を増やす数値表現を使用できます。プロセッサ自体はこれを行うことができず、C言語の機能ではありませんが、ライブラリはそれを実装できます-一般的なCライブラリはGNU Multiple Precision算術ライブラリです。ライブラリは、算術演算に加えてパフォーマンスコストがかかる数値を格納するためにメモリを管理する必要があります。多くの言語には、この種のものが組み込まれています(コストを回避しません)。
Steve314

1
簡単なテストを書いてください。私はCプログラマーではありませんint c = INT.MAXINT; c+=1;
JonH

2
@JonH:問題は、未定義動作のオーバーフローです。ACコンパイラーはそのコードを見つけ、無条件にオーバーフローするため、到達不能なコードであると推測する場合があります。到達不能なコードは問題にならないため、排除できます。最終結果:コードが残っていません。
–MSalters

回答:


48

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

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

型が8ビットしか格納できず1010101010101、ケースを使用して値を強制的に格納しようとすると、ビット単位で見た場合、最後の8ビット、またはになり01010101ます。

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

実用的な観点から、これが決して起こらないようにしたいだけです。これらの種類のオーバーフローは、多くの場合、解決が困難な欠陥をもたらします。言い換えれば、すべての価値が数十億になる可能性があると思われる場合は、使用しないでくださいint


52
4バイトintのシステムで作業している場合、int変数を2,147,483,647に設定してから1を追加すると、変数には-2147483648が含まれます。=> いいえ、それはUndefined Behaviorなので、ループしたり、何か他のものを完全に実行したりします。私は...オーバーフローの有無に基づいてチェックを最適化コンパイラを見て、たとえば無限ループを持っている
マシューM.を

ごめん、はい、あなたは正しいです。ここに「通常」を追加する必要がありました。
ロボット

言語の観点から見た@MatthieuM 、それは本当です。ここで説明している特定のシステムでの実行という点では、それはまったくナンセンスです。
ホッブズ

@hobbs:問題は、未定義の動作のためにコンパイラがプログラムを破壊すると、実際にプログラムを実行すると、実際にはメモリの上書きに匹敵する予期しない動作が発生することです。
マチューM.

24

符号付き整数オーバーフローは未定義の動作です。この場合、プログラムは無効です。コンパイラはこれをチェックする必要がないため、合理的な処理を実行するように見える実行可能ファイルを生成する場合がありますが、実行される保証はありません。

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

https://stackoverflow.com/q/18195715/951890も参照してください


符号付き整数オーバーフローは、符号なし整数オーバーフローと同様に明確に定義されています。ワードに$ N $ビットがある場合、符号付き整数オーバーフローの上限は$$ 2 ^ {N-1} -1 $$($ -2 ^ {N-1} $に折り返す)になりますが、符号なし整数オーバーフローの上限は$$ 2 ^ N-1 $$です($ 0 $にラップアラウンドします)。加算と減算の同じメカニズム、表現できる数値範囲($ 2 ^ N $)と同じサイズ。オーバーフローのちょうど異なる境界。
ロバートブリストージョンソン

1
@ robertbristow-johnson:C標準に準拠していません。
ヴォーンケイト

まあ、標準は時々時代錯誤です。SOの参照を見ると、直接ヒットするコメントが1つあります。「しかし、ここで重要なのは、2の補数の符号付き算術以外を使用するアーキテクチャが現代世界に残っていないということです。たとえば、PDP-1は純粋な歴史的遺物です
。–

私はそれ C標準にないと仮定しますが、私は通常のバイナリ演算が使用されない実装があるかもしれないと仮定しintます。グレイコードまたはBCDまたはEBCDICを使用できると思います。グレーコードまたはEBCDICを使用して算術演算を行うハードウェアを設計する理由はありませんが、ここでもunsigned、バイナリを使用してint2の補数以外で署名する理由はわかりません。
ロバートブリストージョンソン

14

そのため、ここには2つのことがあります。

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

言語レベルで:

Cで:

  • オーバーフローとアンダーフローは、符号なし整数のモジュロ演算として定義されているため、それらの値は「ループ」です
  • オーバーフローとアンダーフローは符号付き整数の未定義の動作であるため、何でも起こります

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

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

になります:

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

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

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

注:Clangまたはgccでは-fsanitize=undefined、デバッグで使用して、符号なし整数のアンダーフロー/オーバーフローで中止する未定義の動作サニタイザーをアクティブにします。

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

注:Clangまたはgccでは-fsanitize=address、デバッグで使用して、アドレスサニタイザーをアクティブにします。これは、範囲外アクセスで中止されます。


マシンレベルで

実際に使用するアセンブリ命令とCPUに依存します。

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

レジスタまたはメモリのどちらで発生した場合でも、どちらの場合もCPUはオーバーフロー時にメモリを上書きしません。


最後の3つのモードは署名されていますか?(2の補数であるため、最初のものは重要ではありません。)
デデュプリケーター

1
@Deduplicator:ミルCPUプログラミングモデルの概要によると、符号付き加算と符号なし加算に異なるオペコードがあります。両方のオペコードが4つのモードをサポートする(そして、さまざまなビット幅とスカラー/ベクトルで動作できる)と期待しています。それから再び、今のところ蒸気のハードウェアです;)
マチューM.

4

@StevenBurnapの回答をさらに進めると、これが起こる理由は、コンピューターがマシンレベルでどのように機能するかによるものです。

配列はメモリ(RAMなど)に保存されます。算術演算が実行されると、メモリ内の値が算術を実行する回路の入力レジスター(ALU:Arithmetic Logic Unit)にコピーされ、入力レジスターのデータに対して演算が実行され、結果が生成されます。出力レジスタ内。この結果は、メモリ内の正しいアドレスでメモリにコピーされ、メモリの他の領域はそのまま残ります。


4

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

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

次に、正確に巨大な整数を計算する必要がある場合(たとえば、10進数で2568桁すべての1000の階乗を計算する場合)、bigint(別名任意精度の数値(またはbignum))が必要です。効率的なbigint算術のアルゴリズムは非常に賢く、通常、特殊なマシン命令を使用する必要があります(たとえば、プロセッサにキャリー付きのワードを追加する場合)。したがって、その場合は、GMPlibなどの既存の bigintライブラリを使用することを強くお勧めします

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.