web-dev-qa-db-ja.com

C ++:配列内の2バイトをunsignedshortにキャストする方法

私はレガシーC++アプリケーションに取り組んでおり、間違いなく自分の快適ゾーンの外にいます(良いことです)。誰かが私にいくつかのポインターを与えるほど親切であるかどうか疑問に思いました(しゃれを意図した)。

Unsignedchar配列の2バイトをunsignedshortにキャストする必要があります。バイトは連続しています。

私がやろうとしていることの例として:

ソケットから文字列を受け取り、それをunsignedchar配列に配置します。最初のバイトは無視できますが、次の2バイトはunsignedcharに変換する必要があります。これはWindowsでのみ発生するため、ビッグ/リトルエンディアンの問題はありません(私が知っていることです)。

これが私が今持っているものです(明らかに機能していません):

//packetBuffer is an unsigned char array containing the string "123456789" for testing
//I need to convert bytes 2 and 3 into the short, 2 being the most significant byte
//so I would expect to get 515 (2*256 + 3) instead all the code I have tried gives me
//either errors or 2 (only converting one byte
unsigned short myShort;
myShort = static_cast<unsigned_short>(packetBuffer[1])
19
user38784

さて、あなたは文字を短い値に広げています。必要なのは、2バイトをショートとして解釈することです。 static_castunsigned char*からunsigned short*にキャストできません。 void*にキャストしてから、unsigned short*にキャストする必要があります。

unsigned short *p = static_cast<unsigned short*>(static_cast<void*>(&packetBuffer[1]));

これで、pを逆参照して、短い値を取得できます。ただし、このアプローチの問題は、unsigned char *からvoid *にキャストしてから、別のタイプにキャストすることです。標準は、アドレスが同じままであることを保証しません(さらに、そのポインターの逆参照は未定義の動作になります)。より良いアプローチは、ビットシフトを使用することです。これは常に機能します。

unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];

これはおそらくあなたが気にかけていることをはるかに下回っていますが、これを行うと簡単に整列されていないアクセスを取得する可能性があることに注意してください。 x86は寛容であり、アラインされていないアクセスによって引き起こされるアボートは内部でキャッチされ、値のコピーと戻りが発生するため、アプリは違いを認識しません(ただし、アラインされたアクセスよりも大幅に低速です)。ただし、このコードがx86以外で実行される場合(ターゲットプラットフォームについては言及していないため、x86デスクトップWindowsを想定しています)、これを行うとプロセッサデータが異常終了し、手動でコピーする必要があります。データをキャストする前に、整列されたアドレスにデータを送信します。

つまり、このアクセスを頻繁に行う場合は、読み取りが整列されないようにコードを調整することを検討すると、パフォーマンス上の利点が得られます。

4
ctacke

上記のビットシフトにはバグがあります。

_unsigned short p = (packetBuffer[1] << 8) | packetBuffer[2];
_

packetBufferがバイト(8ビット幅)の場合、上記のシフトはpacketBufferをゼロに変換し、_packetBuffer[2];_のみを残します。

それにもかかわらず、これは依然としてポインタよりも優先されます。上記の問題を回避するために、数行のコードを無駄にします(非常に文字通りのゼロ最適化を除く)。その結果、同じマシンコードになります。

_unsigned short p;
p = packetBuffer[1]; p <<= 8; p |= packetBuffer[2];
_

または、いくつかのクロックサイクルを節約し、ビットを最後からずらさないようにするには:

_unsigned short p;
p = (((unsigned short)packetBuffer[1])<<8) | packetBuffer[2];
_

あなたはポインタに注意しなければなりません、オプティマイザはあなたを噛みます、そしてメモリアラインメントと他の問題の長いリスト。はい、正しく実行するとより速くなり、間違って実行するとバグが長時間残り、最も望ましくないときに発生する可能性があります。

あなたが怠け者で、8ビット配列で16ビットの計算をしたいとします。 (リトルエンディアン)

_unsigned short *s;
unsigned char b[10];

s=(unsigned short *)&b[0];

if(b[0]&7)
{
   *s = *s+8;
   *s &= ~7;
}

do_something_With(b);

*s=*s+8;

do_something_With(b);

*s=*s+8;

do_something_With(b);
_

完全にバグのないコンパイラが期待するコードを作成するという保証はありません。 do_something_with()関数に送信されたバイト配列bは、_*s_操作によって変更されることはありません。上記のコードには、そうすべきだと言っているものはありません。コードを最適化しないと、この問題が発生しない可能性があります(誰かがコンパイラーまたはコンパイラーのバージョンを最適化または変更するまで)。デバッガーを使用する場合、この問題が発生することはありません(手遅れになるまで)。

コンパイラーはsとbの間の接続を認識しません。これらは、2つの完全に別個の項目です。オプティマイザは、_*s_に多数の演算があることを確認し、その値をレジスタに保持し、最後にのみメモリに保存できるため、_*s_をメモリに書き戻さないことを選択できます(ある場合) )。

上記のポインタの問題を修正するには、3つの基本的な方法があります。

  1. sを揮発性として宣言します。
  2. 和集合を使用します。
  3. タイプを変更するときは常に1つまたは複数の関数を使用してください。
3
old_timer

Unsignedcharポインターをunsignedshortポインターにキャストしないでください(さらに言えば、小さいデータ型のポインターから大きいデータ型にキャストします)。これは、アドレスが正しく配置されると想定されているためです。より良いアプローチは、バイトを実際の符号なしの短いオブジェクトにシフトするか、memcpyを符号なしの短い配列にシフトすることです。

間違いなく、この制限を回避するためにコンパイラ設定を調整できますが、これは非常に微妙なことであり、コードが渡されて再利用されると、将来壊れます。

2
sep
unsigned short myShort = *(unsigned short *)&packetBuffer[1];
2
PiNoYBoY82

多分これは非常に遅い解決策ですが、私はあなたと共有したいだけです。プリミティブまたは他のタイプを変換する場合は、和集合を使用できます。下記参照:

union CharToStruct {
    char charArray[2];
    unsigned short value;
};


short toShort(char* value){
    CharToStruct cs;
    cs.charArray[0] = value[1]; // most significant bit of short is not first bit of char array
    cs.charArray[1] = value[0];
    return cs.value;
}

16進数未満の値で配列を作成し、toShort関数を呼び出すと、3の短い値が得られます。

char array[2]; 
array[0] = 0x00;
array[1] = 0x03;
short i = toShort(array);
cout << i << endl; // or printf("%h", i);
2
ilkayaktas

静的キャストの構文は異なります。さらに、ポインターを操作する必要があります。実行したいことは次のとおりです。

unsigned short *myShort = static_cast<unsigned short*>(&packetBuffer[1]);
1
arul

Windowsでは次のものを使用できます。

unsigned short i = MAKEWORD(lowbyte,hibyte);
0
Richard

これは古いスレッドだと思います。ここでの提案をすべて試したとは言えません。私はmfcに慣れているだけで、uintを2バイトに変換し、ソケットのもう一方の端に戻す方法を探していました。

ネット上にはビットシフトの例がたくさんありますが、実際にはどれも機能していないようです。多くの例は非常に複雑に見えます。つまり、uintから2バイトを取得し、それらをネットワーク経由で送信し、もう一方の端でuintに接続し直すということです。

これは私が最終的に思いついた解決策です:

 class ByteConverter 
 {
 public:
 static void uIntToBytes(unsigned int theUint、char * bytes)
 {
 unsigned int tInt = theUint; 
 
 void * uintConverter =&tInt; 
 char * theBytes =(char *)uintConverter; 
 
 bytes [0] = theBytes [0]; 
 bytes [1] = theBytes [1]; 
} 
 static unsigned int bytesToUint(char * bytes)
 {
 unsigned theUint = 0; 
 
 void * uintConverter =&theUint; 
 char * thebytes =(char *)uintConverter; 
 
 thebytes [0 ] = bytes [0]; 
 thebytes [1] = bytes [1]; 
 
 return theUint; 
} 
}; 

このように使用されます:

 unsigned int theUint; 
 char bytes [2]; 
 CString msg;
ByteConverter :: uIntToBytes(65000、bytes); theUint = ByteConverter :: bytesToUint(bytes);
msg.Format(_T( "theUint =%d")、theUint); AfxMessageBox(msg、MB_ICONINFORMATION | MB_OK);

これが誰かを助けることを願っています。

0
Pete
char packetBuffer[] = {1, 2, 3};
unsigned short myShort = * reinterpret_cast<unsigned short*>(&packetBuffer[1]);

私はいつもこれをしなければなりませんでした。ビッグエンディアンは明らかな問題です。マシンが不整合な読み取りを嫌う場合、実際に得られるのは誤ったデータです! (そして書く)。

テストキャストとアサーションを記述して、正しく読み取られるかどうかを確認することをお勧めします。したがって、ビッグエンディアンのマシン、またはさらに重要なことに、不整合な読み取りを嫌うマシンで実行すると、追跡が難しい奇妙な「バグ」ではなく、アサートエラーが発生します;)

0
user34537

入力が文字列であることを誰も見ませんでした!

/* If it is a string as explicitly stated in the question.
 */
int byte1 = packetBuffer[1] - '0'; // convert 1st byte from char to number.
int byte2 = packetBuffer[2] - '0';

unsigned short result = (byte1 * 256) + byte2;

/* Alternatively if is an array of bytes.
 */
int byte1 = packetBuffer[1];
int byte2 = packetBuffer[2];

unsigned short result = (byte1 * 256) + byte2;

これにより、他のほとんどのソリューションが特定のプラットフォームで発生する可能性のあるアライメントの問題も回避されます。注shortは少なくとも2バイトです。ほとんどのシステムでは、2バイトに整列されていない短いポインタ(またはシステムのsizeof(short)が何であれ)を逆参照しようとすると、メモリエラーが発生します。

0
Martin York