20

次のプログラム (C99) を検討してください。

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

int main(void)
{
    printf("Enter int in range %jd .. %jd:\n > ", INTMAX_MIN, INTMAX_MAX);
    intmax_t i;
    if (scanf("%jd", &i) == 1)
        printf("Result: |%jd| = %jd\n", i, imaxabs(i));
}

私が理解しているように、これには次のような簡単にトリガーできる未定義の動作が含まれています。

Enter int in range -9223372036854775808 .. 9223372036854775807:
 > -9223372036854775808
Result: |-9223372036854775808| = -9223372036854775808

質問:

  1. ユーザーが間違った番号を入力すると、「コードは、コンパイラのファンシーをストロークするコードパスをトリガーすることが許可されています」のように、これは本当に未定義の動作ですか? それとも、完全に定義されていない他のフレーバーですか?

  2. 衒学的なプログラマーは、標準で保証されていない仮定をせずに、これを防ぐにはどうすればよいでしょうか?

(関連する質問がいくつかありますが、上記の質問 2 に回答するものは見つかりませんでした。そのため、重複を提案する場合は、それが回答されていることを確認してください。)

4

7 に答える 7

10

の結果imaxabsが表現できない場合、2 の補数を使用すると発生する可能性があり、動作は undefinedです。

7.8.2.1 imaxabs 関数

  1. imaxabs 関数は、整数 j の絶対値を計算します。結果を表すことができない場合、動作は未定義です。221)

221) 最も負の数の絶対値は、2 の補数では表すことができません。

仮定を行わず、常に定義されているチェックは次のとおりです。

intmax_t i = ... ;
if( i < -INTMAX_MAX )
{
    //handle error
}

(この if ステートメントは、1 の補数または符号の大きさの表現を使用している場合は使用できないため、コンパイラは到達不能コードの警告を出す可能性があります。コード自体はまだ定義されており、有効です。)

于 2016-02-07T09:34:10.813 に答える
7

衒学的なプログラマーは、標準で保証されていない仮定をせずに、これを防ぐにはどうすればよいでしょうか?

1 つの方法は、符号なし整数を使用することです。符号なし整数のオーバーフロー動作は、符号付き整数から符号なし整数に変換するときの動作と同様に明確に定義されています。

したがって、以下は安全であると思います(一部の本当にあいまいなシステムでは恐ろしく壊れていることが判明しました。改善されたバージョンについては、投稿の後半を参照してください)

uintmax_t j = i;
if (j > (uintmax_t)INTMAX_MAX) {
  j = -j;
}
printf("Result: |%jd| = %ju\n", i, j);

では、これはどのように機能するのでしょうか。

uintmax_t j = i;

これにより、符号付き整数が符号なし整数に変換されます。正の場合、値は同じままです。負の場合、値は 2 n増加します(n はビット数)。これにより、大きな数 (INTMAX_MAX より大きい) に変換されます。

if (j > (uintmax_t)INTMAX_MAX) {

元の数値が正の場合 (したがって INTMAX_MAX 以下の場合)、これは何もしません。元の数値が負の場合、if ブロックの内部が実行されます。

  j = -j;

番号は否定されます。否定の結果は明らかに負であるため、符号なし整数として表すことはできません。したがって、2 n増加します。

したがって、代数的に負​​の i の結果は次のようになります

j = - (i + 2 n ) + 2 n = -i


賢いですが、このソリューションは仮定をしています。これは、C 標準で許可されている INTMAX_MAX == UINTMAX_MAX の場合に失敗します。

うーん、これを見てみましょう ( https://busybox.net/~landley/c99-draft.htmlを読んでいますが、これは明らかに標準化前の最後の C99 ドラフトです。最終的な標準で何か変更があった場合は教えてください。

最初の u の有無のみが異なる typedef 名が定義されている場合、それらは 6.2.5 で説明されているように、対応する符号付きおよび符号なしの型を示すものとします。実装は、対応する型も提供せずに型を提供してはなりません。

6.2.5でわかります

符号付き整数型ごとに、対応する (ただし異なる) 符号なし整数型 (キーワード unsigned で指定) があり、同じ量のストレージ (符号情報を含む) を使用し、同じ配置要件があります。

6.2.6.2でわかりました

#1

unsigned char 以外の unsigned 整数型の場合、オブジェクト表現のビットは、値ビットとパディング ビットの 2 つのグループに分けられます (後者のいずれかが存在する必要はありません)。N 個の値のビットがある場合、各ビットは 1 から 2N-1 までの異なる 2 のべき乗を表し、その型のオブジェクトは純粋な 2 進数表現を使用して 0 から 2N-1 までの値を表すことができなければならない。これは、値表現として知られるものとします。パディングビットの値は規定されていない.39)

#2

符号付き整数型の場合、オブジェクト表現のビットは、値ビット、パディング ビット、符号ビットの 3 つのグループに分けられます。パディング ビットは必要ありません。正確に 1 つの符号ビットがあります。値ビットである各ビットは、対応する符号なし型のオブジェクト表現の同じビットと同じ値を持つものとします (符号付き型に M 個の値ビットがあり、符号なし型に N 個の値ビットがある場合、M<=N)。符号ビットがゼロの場合、結果の値には影響しません。

そうです、あなたは正しいようですが、符号付きと符号なしの型は同じサイズでなければなりませんが、符号なしの型が符号付きの型よりも1つ多いパディングビットを持つことは有効であるようです。


わかりました、上記の分析に基づいて、最初の試行で欠陥を明らかにしたので、より偏執的なバリアントを作成しました。これには、最初のバージョンから 2 つの変更点があります。

j > (uintmax_t)INTMAX_MAX ではなく i < 0 を使用して、負の数をチェックします。これは、INTMAX_MAX == UINTMAX_MAX の場合でも、アルゴリズムが -INTMAX_MAX 以上の数値に対して正しい結果を生成することを意味します。

INTMAX_MAX == UINTMAX_MAX、INTMAX_MIN == -INTMAX_MAX -1、i == INTMAX_MIN のエラー ケースの処理を追加します。これにより、簡単にテストできる if 条件内で j=0 が発生します。

C 標準の要件から、INTMAX_MIN を -INTMAX_MAX -1 より小さくすることはできないことがわかります。これは、符号ビットが 1 つしかなく、値のビット数が対応する符号なしの型と同じかそれ以下でなければならないためです。小さい数値を表すためのビット パターンはまったく残っていません。

uintmax_t j = i;
if (i < 0) {
  j = -j;
  if (j == 0) {
    printf("your platform sucks\n");
    exit(1);
  }
}
printf("Result: |%jd| = %ju\n", i, j);

@plugwash 2501が正しいと思います。たとえば、-UINTMAX_MAX 値は 1: (-UINTMAX_MAX + (UINTMAX_MAX + 1)) になり、if によってキャッチされません。– ハイド 58分前

うーん、

INTMAX_MAX == UINTMAX_MAX および i = -INTMAX_MAX と仮定

uintmax_t j = i;

このコマンドの後 j = -INTMAX_MAX + (UINTMAX_MAX + 1) = 1

もし (私 < 0) {

i は 0 より小さいので、if 内でコマンドを実行します。

j = -j;

このコマンドの後 j = -1 + (UINTMAX_MAX + 1) = UINTMAX_MAX

これが正解なので、エラーの場合にトラップする必要はありません。

于 2016-02-07T11:29:22.853 に答える
4

2 補数システムでは、絶対値が範囲外になるため、最も負の値の絶対数を取得することは実際には未定義の動作です。また、UB は実行時に発生するため、コンパイラが支援できることは何もありません。

それを防ぐ唯一の方法は、入力をタイプの最も負の値と比較することINTMAX_MINです(表示するコード内)。

于 2016-02-07T08:52:32.527 に答える
2

したがって、整数の絶対値を計算すると、1 つのケースで未定義の動作が呼び出されます。実際、未定義の動作は回避できますが、1 つのケースで正しい結果を出すことは不可能です。

ここで、整数を 3 倍することを考えてみましょう。ここには、もっと深刻な問題があります。この操作は、すべてのケースの 2/3 で未定義の動作を呼び出します! そして、すべての int 値 x の 3 分の 2 では、値 3x を持つ int を見つけることはまったく不可能です。これは絶対値問題よりもはるかに深刻な問題です。

于 2016-02-07T22:38:14.853 に答える
1

いくつかのビットハックを使用したい場合があります:

int v;           // we want to find the absolute value of v
unsigned int r;  // the result goes here 
int const mask = v >> sizeof(int) * CHAR_BIT - 1;

r = (v + mask) ^ mask;

これは次の場合にうまく機能しINT_MIN < v <= INT_MAXます。の場合、未定義の動作を引き起こすことなくv == INT_MIN残ります。INT_MIN

ビット演算を使用して、1 の補数および符号-絶対値システムでこれを処理することもできます。

参照: https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

于 2016-02-07T09:51:32.527 に答える
0

このhttp://linux.die.net/man/3/imaxabsによると

ノート

最も負の整数の絶対値を取得しようとすることは定義されていません。

全範囲を処理するには、コードに次のようなものを追加できます

    if (i != INTMAX_MIN) {
        printf("Result: |%jd| = %jd\n", i, imaxabs(i));
    } else {  /* Code around undefined abs( INTMAX_MIN) /*
        printf("Result: |%jd| = %jd%jd\n", i, -(i/10), -(i%10));
    }

編集: abs(INTMAX_MIN) は 2 の補数マシンでは表現できないため、表現可能な範囲内の 2 つの値が文字列として出力時に連結されます。%jd はサポートされている形式ではなかったため、printf には %lld が必要でしたが、gcc でテストされました。

于 2016-02-07T09:34:57.967 に答える