このビデオ を見ていました。 Bjarne Stroustrup は、unsigned intsがエラーを起こしやすく、バグにつながることを示しています。したがって、本当に必要な場合にのみ使用してください。また、Stack Overflowに関する質問の1つを読みました(ただし、どちらを覚えていないか)unsigned intsを使用するとセキュリティバグにつながる可能性があります。
それらはどのようにセキュリティバグにつながりますか?誰かが適切な例を挙げてそれを明確に説明できますか?
考えられる側面の1つは、アンダーフローが大きな数をもたらすため、符号なし整数がループでやや見つけにくい問題を引き起こす可能性があることです。 (符号なし整数でも!)このバグのバリアントを何回作成したかは数えられません。
_for(size_t i = foo.size(); i >= 0; --i)
...
_
定義により、_i >= 0
_は常にtrueであることに注意してください。 (最初にこれを引き起こすのは、i
が署名されている場合、コンパイラはsize()
の_size_t
_でオーバーフローの可能性について警告することです).
言及された他の理由があります 危険-ここで使用される符号なしの型! 、私の意見では、最も強いのは、符号付きと符号なしの間の暗黙的な型変換です。
大きな要因の1つは、ループロジックが難しくなることです:配列の最後の要素(実際には発生します)を除くすべての要素を反復処理したいと想像してください。したがって、関数を記述します。
void fun (const std::vector<int> &vec) {
for (std::size_t i = 0; i < vec.size() - 1; ++i)
do_something(vec[i]);
}
よさそうですね。非常に高い警告レベルでもきれいにコンパイルできます! ( Live )これをコードに追加すると、すべてのテストがスムーズに実行され、忘れてしまいます。
さて、後で、誰かが空のvector
を関数に渡します。符号付き整数があれば、願わくば sign-compareコンパイラの警告 に気づき、適切なキャストを導入し、そもそもバグのあるコードを公開していないはずです。
しかし、符号なし整数を使用した実装では、ラップしてループ条件がi < SIZE_T_MAX
になります。災害、UB、そしておそらくクラッシュ!
セキュリティバグの原因を知りたいですか?
これはセキュリティの問題でもあり、特に buffer overflow です。これを悪用する可能性のある方法の1つは、do_something
が攻撃者が観察できることを行う場合です。彼はどの入力がdo_something
に入力されたのかを見つけることができ、その方法では攻撃者がアクセスできないはずのデータがメモリからリークされます。これは Heartbleed bug に似たシナリオになります。 (彼の コメント で指摘してくれたラチェットフリークに感謝します。)
質問に答えるためだけにビデオを見ることはありませんが、1つの問題は、符号付きの値と符号なしの値を混在させた場合に発生する可能性のある混乱した変換です。例えば:
#include <iostream>
int main() {
unsigned n = 42;
int i = -42;
if (i < n) {
std::cout << "All is well\n";
} else {
std::cout << "ARITHMETIC IS BROKEN!\n";
}
}
プロモーションルールは、比較のためにi
がunsigned
に変換され、大きな正の数と驚くべき結果が得られることを意味します。
「インターフェースの符号付きおよび符号なしの型」、C++レポート、1995年9月 スコット・マイヤーズによる既存の回答の変形と見なすことはできますが、特に符号なしの型を避けることが重要です- interfaces。
問題は、インターフェイスのクライアントが発生する可能性のある特定のエラーを検出できなくなることです(そして、couldがエラーを発生させると、will それらを作ります)。
そこに与えられた例は次のとおりです。
_template <class T> class Array { public: Array(unsigned int size); ...
_
このクラスの可能なインスタンス化
_int f(); // f and g are functions that return int g(); // ints; what they do is unimportant Array<double> a(f()-g()); // array size is f()-g()
_
f()
とg()
によって返される値の差は、非常に多くの理由で負の値になる場合があります。 Array
クラスのコンストラクターは、この差を暗黙的にunsigned
に変換される値として受け取ります。したがって、Array
クラスの実装者として、誤って渡された_-1
_の値と非常に大きな配列の割り当てを区別することはできません。
Unsigned intの大きな問題は、unsigned int 0から1を引くと、結果が負の数ではなく、結果が開始した数より小さくないが、結果は可能な限り最大のunsigned int値になることです。 。
unsigned int x = 0;
unsigned int y = x - 1;
if (y > x) printf ("What a surprise! \n");
そして、これがunsigned intエラーを起こしやすいものです。もちろん、unsigned intは、動作するように設計されたとおりに動作します。あなたが何をしているかを知っていて間違いを犯さないなら、それは絶対に安全です。しかし、ほとんどの人は間違いを犯します。
優れたコンパイラーを使用している場合は、コンパイラーが生成するすべての警告をオンにして、間違いの可能性がある危険なことを行うときに通知します。
符号なし整数型の問題は、サイズに応じて、2つの異なるもののいずれかを表す場合があることです。
int
(例えばuint8
)より小さい符号なしの型はnumbersの範囲で0..2ⁿ-1を保持し、それらの計算は範囲を超えない限り整数演算の規則に従って動作しますint
タイプ。現在の規則では、そのような計算がint
の範囲を超える場合、コンパイラはコードで好きなことを行うことができ、時間と因果律を否定することさえできます(一部のコンパイラは正確にそれを行います!) 、計算の結果がint
より小さい符号なしの型に割り当てられる場合でも。unsigned int
およびそれ以上は、mod2ⁿの合同な整数の抽象ラッピング代数リングのメンバーを保持します。つまり、計算が0..2ⁿ-1の範囲外になった場合、システムは値を範囲内に戻すために必要な2ⁿの倍数を加算または減算します。したがって、uint32_t x=1, y=2;
が与えられた場合、式x-y
は、int
が32ビットより大きいかどうかに応じて、2つの意味のいずれかを持ちます。
int
が32ビットより大きい場合、式は数値1から数値2を減算し、数値-1を生成します。 uint32_t
型の変数は、int
のサイズに関係なく値-1を保持できず、-1を格納すると、そのような変数は0xFFFFFFFFを保持しますが、値が符号なしの型に強制されない限り、または符号付き数量-1のように動作します。int
が32ビット以下の場合、式はuint32_t
値2に追加されるとuint32_t
値1を生成するuint32_t
値を生成します(つまり、uint32_t
値0xFFFFFFFF)。私見、CとC++が新しい符号なしの型を定義する場合、この問題はきれいに解決できます。 unum32_tおよびuwrap32_t] unum32_t
は、int
のサイズに関係なく、常に数値として動作します(おそらく、int
が32ビットの場合、減算または単項マイナスの右辺演算を次に大きい符号付き型に昇格させる必要があります。小さい)、wrap32_t
は常に代数リングのメンバーとして振る舞います(int
が32ビットより大きい場合でもプロモーションをブロックします)。しかし、そのような型が存在しない場合、移植可能なコードはどこでも型強制を必要とすることが多いため、移植可能でクリーンなコードを書くことはしばしば不可能です。
CおよびC++の数値変換規則はビザンチンの混乱です。符号なしの型を使用すると、純粋に符号付きの型を使用するよりもはるかに大きな混乱にさらされます。
たとえば、一方が符号付きでもう一方が符号なしの2つの変数を比較する単純なケースを考えてみましょう。
別の例として、同じサイズの2つの符号なし整数を乗算することを検討してください。