C/C++で符号なし左シフトの前にマスクするのは偏執的すぎる?
質問
この質問は、私が C/C++ で暗号アルゴリズム (例: SHA-1) を実装し、プラットフォームに依存しない移植可能なコードを書き、そして徹底的に 未定義の動作 .
標準化された暗号アルゴリズムから、これを実装するよう求められたとします。
b = (a << 31) & 0xFFFFFFFF
ここで
a
と
b
は符号なし32ビット整数である。結果において、最下位32ビットより上のビットはすべて破棄されていることに注意してください。
最初の素朴な近似として、私たちは次のように仮定するかもしれません。
int
はほとんどのプラットフォームで 32 ビット幅であると仮定し、次のように記述します。
unsigned int a = (...);
unsigned int b = a << 31;
このコードがどこでも使えるわけではないことは、次の理由からわかります。
int
はあるシステムでは 16 ビット幅、他のシステムでは 64 ビット幅、そしておそらく 36 ビット幅であるためです。しかし
stdint.h
を使えば、このコードを改善することができます。
uint32_t
型に変更することができます。
uint32_t a = (...);
uint32_t b = a << 31;
では、もういいんですね?私は何年もそう思っていたのですが ... そうでもないんです。あるプラットフォームで、あるとします。
// stdint.h
typedef unsigned short uint32_t;
C/C++で算術演算を行う際のルールとして、型(例えば
short
よりも狭い場合
int
よりも狭い場合、その幅は
int
に拡大され、すべての値が収まる場合は
unsigned int
でなければ
例えば、コンパイラが定義した
short
を32ビット(符号付き)と定義し
int
を48ビット(符号付き)とする。すると、このようなコード行になります。
uint32_t a = (...);
uint32_t b = a << 31;
は事実上、意味することになります。
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
なお
a
は
int
に昇格します。
ushort
(すなわち
uint32
) に適合するのは
int
(すなわち
int48
).
しかし、今度は問題が発生しました。
符号付き整数型の符号ビットに非ゼロビットを左シフトすることは、未定義の動作です。
. この問題が発生したのは、私たちの
uint32
に昇格したためです。
int48
- に昇格する代わりに
uint48
(に昇格するのではなく(左遷でOKなところ)。
以下は私の質問です。
-
私の推論は正しく、これは理論的には正当な問題なのでしょうか?
-
すべてのプラットフォームで、次の整数型は幅が 2 倍であるため、この問題は無視しても安全ですか?
-
このような入力を事前にマスクすることによって、この病的な状況を正しく防御することは良いアイデアでしょうか?
b = (a & 1) << 31;
. (これは必ずしもすべてのプラットフォームで正しいでしょう。しかし、これでは速度が重要な暗号アルゴリズムが必要以上に遅くなってしまう可能性があります)。
明確化/編集しました。
-
C か C++、あるいはその両方についての回答を受け付けます。少なくともどちらかの言語の答えが知りたいのです。
-
プリマスクロジックはビット回転を損なう可能性があります。例えば、GCCはコンパイル時に
b = (a << 31) | (a >> 1);
をアセンブリ言語の32ビットビット・ローテーション命令にコンパイルします。しかし、左シフトを事前にマスクすると、新しいロジックがビット回転に変換されない可能性があり、これは、今、1ではなく、4つのオペレーションが実行されることを意味します。
どのように解決するのですか?
ヒントとなるのは
この質問
の UB の可能性について
uint32 * uint32
の算術演算について、C や C++では次のような簡単な方法で動作するはずです。
uint32_t a = (...);
uint32_t b = (uint32_t)((a + 0u) << 31);
整数の定数
0u
は型
unsigned int
. これにより
a + 0u
を
uint32_t
または
unsigned int
のどちらか広い方です。型はランクを持つので
int
以上であるため、これ以上の昇格は発生せず、シフトは左オペランドが
uint32_t
または
unsigned int
.
への最後のキャストは
uint32_t
は単に狭窄変換に関する潜在的な警告を抑制するだけです (例えば
int
が64ビットの場合)。
まともなCコンパイラーは、ゼロを追加することは無意味であり、符号なしシフトの後にプリマスクが効果を持たないことを確認するよりも負担が少ないことを確認することができるはずです。
関連
-
[解決済み】構造体のベクター初期化について
-
[解決済み】Visual Studio 2015で「非標準の構文。'&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】cc1plus:エラー:g++で認識されないコマンドラインオプション"-std=c++11"
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み】リンカーエラーです。"リンカ入力ファイルはリンクが行われていないため未使用"、そのファイル内の関数への未定義参照
-
[解決済み】Enterキーを押して続行する
-
[解決済み】std::cin.getline( ) vs. std::cin
-
[解決済み】演算子のオーバーロード C++; <<操作のパラメータが多すぎる
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】符号なし整数のオーバーフローは定義されているのに、符号あり整数のオーバーフローは定義されていないのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】coutはstdのメンバではない
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】 != と =! の違いと例(C++の場合)
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】Enterキーを押して続行する
-
[解決済み] 変数サイズのオブジェクトが初期化されないことがある c++
-
[解決済み] なぜuint_fast32_tではなく、uint32_tが好まれるのですか?