4

符号なしの型から同じランクの符号付きの型にキャストすると、実装で定義された値が生成されることを理解しています。

C99 6.3.1.3:

  1. それ以外の場合、新しいタイプは署名され、値を表すことができません。結果が実装定義であるか、実装定義のシグナルが発生します。

これは、符号付き数値をバイトスワップする方法がわからないことを意味します。たとえば、周辺機器から2バイト、2の補数の符号付き値をリトルエンディアンの順序で受け取り、それらをビッグエンディアンのCPUで処理するとします。Cライブラリのバイトスワッピングプリミティブ(のようなntohs)は、符号なしの値で機能するように定義されています。データを符号なしに変換してバイトスワップできるようにした場合、後で符号付きの値を確実に回復するにはどうすればよいですか?

4

3 に答える 3

4

あなたがあなたの質問で言うように、結果は実装定義であるか、実装定義のシグナルが発生します-つまり、何が起こるかはプラットフォーム/コンパイラに依存します。

于 2012-05-03T16:49:07.240 に答える
3

実装で定義された動作を可能な限り回避しながら、符号付き数値をバイトスワップするには、より広い符号付き中間体を使用できます。これは、必要な符号付き値と同じ幅で符号な​​し型の全範囲を表すことができます。バイトスワップ。リトルエンディアンの16ビット数を例にとると:

// Code below assumes CHAR_BIT == 8, INT_MAX is at least 65536, and
// signed numbers are twos complement.
#include <stdint.h>

int16_t
sl16_to_host(unsigned char b[2])
{
    unsigned int n = ((unsigned int)b[0]) | (((unsigned int)b[1]) << 8);
    int v = n;
    if (n & 0x8000) {
        v -= 0x10000;
    }
    return (int16_t)v;
}

これが何をするかです。まず、リトルエンディアンの値をbホストエンディアンの符号なしの値に変換します(ホストが実際にどのエンディアンであるかに関係なく)。次に、その値をより広い符号付き変数に格納します。その値はまだ[0、65535]の範囲にありますが、現在は符号付きの数量です。intその範囲内のすべての値を表すことができるため、変換は標準によって完全に定義されています。

ここで重要なステップが来ます。符号なしの値の上位ビットである符号付きビットをテストし、それが真の場合は、符号付きの値から65536(0x10000)を減算します。これにより、範囲[32768、655535]が[-32768、-1]にマップされます。これは、2の補数の符号付き数値が正確にエンコードされる方法です。これはより広いタイプでも発生しているため、範囲内のすべての値が表現可能であることが保証されます。

最後に、幅の広い型をに切り捨てint16_tます。このステップには、避けられない実装定義の動作が含まれますが、確率1では、実装は期待どおりに動作するように定義します。実装で符号付きの数値に符号と大きさまたは1の補数の表現を使用するという、ありそうもないイベントでは、値-32768が切り捨てによって破壊され、プログラムがクラッシュする可能性があります。気にしないでください。

64ビットタイプが使用できない場合に32ビット数値をバイトワッピングするのに役立つ可能性のある別のアプローチは、符号ビットをマスクして個別に処理することです。

int32_t
sl32_to_host(unsigned char b[4])
{
    uint32_t mag = ((((uint32_t)b[0]) & 0xFF) <<  0) |
                   ((((uint32_t)b[1]) & 0xFF) <<  8) |
                   ((((uint32_t)b[2]) & 0xFF) << 16) |
                   ((((uint32_t)b[3]) & 0x7F) << 24);
    int32_t val = mag;
    if (b[3] & 0x80) {
        val = (val - 0x7fffffff) - 1;
    }
    return val;
}

減算が符号付きタイプで行われるようにするために、(val - 0x7fffffff) - 1ここでは、だけでなく、ここに記述しました。val - 0x80000000

于 2015-06-21T21:59:12.507 に答える
1

符号なしタイプから同じランクの符号付きタイプにキャストすると、実装定義の値が生成されることを理解しています。

Cの符号形式が実装定義であるため、実装定義になります。たとえば、2の補数は、そのような実装定義形式の1つです。

したがって、ここでの唯一の問題は、トランスミッションのいずれかの側が2の補数ではないかどうかです。これは、現実の世界では起こりそうにありません。私は、暗黒時代から自分の補数のコンピューターを不明瞭にし、絶滅させるために移植可能なプログラムを設計することを気にしません。

これは、符号付き数値をバイトスワップする方法がわからないことを意味します。たとえば、周辺機器から2バイト、2の補数の符号付き値をリトルエンディアンの順序で受け取り、それらをビッグエンディアンのCPUで処理するとします。

ここでの混乱の原因は、一般的な2の補数が、大きいまたは小さいエンディアンの送信者から送信され、大きい/小さい送信者によって受信されると考えていることだと思います。ただし、データ送信プロトコルはそのようには機能しません。エンディアンと符号の形式を明示的に指定します。したがって、双方がプロトコルに適応する必要があります。

そして、それが指定されると、ここにはロケット科学は実際にはありません。2つの生のバイトを受け取っています。それらを生データの配列に格納します。次に、それらを2の補数変数に割り当てます。プロトコルがリトルエンディアンを指定したとします。

int16_t val;
uint8_t little[2];

val = (little[1]<<8) | little[0];

ビットシフトには、エンディアンに依存しないという利点があります。したがって、上記のコードは、CPUが大きいか小さいかに関係なく機能します。したがって、このコードには醜い暗黙のプロモーションがたくさん含まれていますが、100%移植可能です。Cは、上記を次のように扱うことが保証されています。

val = (int16_t)( ((int)((int)little[1]<<8)) | (int)little[0] );

シフト演算子の結果タイプは、プロモートされた左オペランドのタイプです。|の結果タイプ バランス型(通常の関節変換)です。

符号付きの負の数をシフトすると未定義の動作が発生しますが、個々のバイトが符号なしであるため、シフトを回避できます。それらが暗黙的に昇格された場合でも、数値は正として扱われます。

また、int少なくとも16ビットであることが保証されているため、コードはすべてのCPUで機能します。

または、すべての暗黙的なプロモーション/変換を完全に除外するペダンティックスタイルを使用することもできます。

val = (int16_t) ( ((uint32_t)little[1] << 8) | (uint32_t)little[0] );

しかし、これには読みやすさが犠牲になります。

于 2015-06-22T07:51:13.770 に答える