このプロジェクトでは、パケットを受信し、その一部を符号なし整数にキャストし、ビッグ エンディアンとリトル エンディアンの両方の結果を取得することになっています。当初は、バイト配列 (パケット) 内のポインターを、(uint32_be_t*)packet; のようなビッグエンディアン形式で受信した値を自動的に格納する符号なし整数型にキャストするだけでした。 (uint32_t*)packet を実行するときに自動的にリトルエンディアン形式に変換される方法と似ています。
これを自動的に行う型が見つからなかったため、「u32」という独自の構造体を作成することにしました。これには「get」、「」メソッドがあります。これはビッグエンディアン形式で値を取得し、「get_le」これは値をリトルエンディアン形式で取得します。ただし、これを行うとマイナスの結果が得られることに気づきましたリトルエンディアンの結果から。
struct u32 {
u8 data[4] = {};
uint32_t get() {
return ((uint32_t)data[3] << 0)
| ((uint32_t)data[2] << 8)
| ((uint32_t)data[1] << 16)
| ((uint32_t)data[0] << 24);
}
uint32_t get_le() {
return ((uint32_t)data[3] << 24)
| ((uint32_t)data[2] << 16)
| ((uint32_t)data[1] << 8)
| ((uint32_t)data[0] << 0);
}
};
パケットをシミュレートするために、文字配列を作成し、次のように u32* をそれにキャストしました。
int main() {
char ary[] = { 0x00, 0x00, 0x00, (char)0xF4 };
u32* v = (u32*)ary;
printf("%d %d\n", v->get(), v->get_le());
return 0;
}
しかし、結果は 244 -201326592 になります。
なぜこのようなことが起こっているのでしょうか? 「get_le」への戻り値の型はは uint32_t であり、最初の関数 "get" は "get" です。ビッグエンディアンの符号なし整数を返すはずですが、正しく実行されています。
余談ですが、これは頭に思いついたテストだったので、クラスの合間にライブラリに行ってテストしましたが、残念ながらオンライン コンパイラ (onlinegdb) を使用する必要があります。 Visual Studio でも同じように動作すると考えられます。また、コードを改善する方法についてご提案があれば、大変助かります。 Visual Studio 201 を使用しています9 であり、cstdlib の使用が許可されています。
2
char には実装定義の署名があります。 unsigned char を符号付き値として解釈しないようにするには、明示的に unsigned char を使用します。
– シャドウレンジャー
2020 年 9 月 3 日 16:23
2
符号なし整数には %u が必要ですが、符号付き整数には %d が必要です.
– スーパーストーマー
2020 年 9 月 3 日 16:24
1
u8 ->なぜ uint8_t ではないのでしょうか?
– ライカー
2020 年 9 月 3 日 16:31
1
他に何をしますか使用? << を入れる必要があるため、cout を使用するのは嫌いです。すべての単一の値の間に...パディングを追加したい場合は、大量のルールを使用する必要があります。 (SuperStormer のコメントから %x をテストした後) Printf を使用すると、「%08x」を挿入できます。 16 進値全体を他の値と同じように表示するには、単に「%.2f」と入力することもできます。 2 つの浮動小数点の場合...printf には愛すべき点がたくさんあります。
– ブランドン・ウールワース
2020 年 9 月 3 日 16:32
1
これが、printf を好まない人がいない理由です。これは少し双曲線的な表現です。みんなじゃないよアテス printf()
– ライカー
2020 年 9 月 3 日 16:34
------------------------
そうですね、敢えて言いますが、printf() フォーマット文字列では %d ではなく %u を使用したいのです!
%d は値が符号付きであると想定するため、最上位ビットが 1 の場合はマイナス符号が付きます。
2
これには気づきませんでした。 「%d」だと仮定しました。 「数字」を意味します。任意の値を出力するように意図した形で。 printf をもう一度見てみる必要があるかもしれません。
– ブランドン・ウールワース
2020 年 9 月 3 日 16:29
1
@BrandonWoolworth printf の致命的な欠陥は、引数の型がわからないことです。変更フラグを含め、適切な一致するフォーマット コードを使用するように十分に注意する必要があります。
– マーク・ランサム
2020 年 9 月 3 日 19:25
------------------------
同じタスクを実行する、より洗練された方法があります。代わりに uint32_t を使用してください。 std::memcpy を使用すると、未定義の動作を呼び出すことなく、char 配列と uint32_t の間で変換できます。これは std::bit_cast も同様です。 char* を int* として再解釈する動作は未定義です。 MSVC では許可されているため、これが問題の原因ではありませんが、実際には移植性がありません。
std::memcpy 変換またはポインタ キャストは、リトル エンディアンまたはビッグ エンディアンのいずれかのネイティブ バイト オーダーで行われます。
組み込み関数を使用してバイト順を変換できます。 MSVC の場合、これは次のようになります。
_byteswap_ulong(x); // unsigned long is uint32_t on Windows
_byteswap_ulong のドキュメントを参照してください。これは、単一の x86 bswap 命令にコンパイルされますが、一連のシフトではこれは起こりそうにありません。これにより、パフォーマンスが何倍にも向上します。10倍。移植可能なコードが必要な場合は、GCC と Clang に __builtin_bswap があります。
std::endian を使用するか、C++20 の __BYTE_ORDER__ マクロを使用してネイティブ エンディアンを検出できます。リトル エンディアンまたはビッグ エンディアンへの変換は、プラットフォームのエンディアンに応じて、何も行わないか、バイト スワップを実行するだけになります。
#include <bit>
#include <cstring>
#include <cstdint>
uint32_t bswap(uint32_t x) {
return _byteswap_ulong(x);
}
uint32_t to_be(uint32_t x) {
return std::endian::native == std::endian::big ? x : bswap(x);
}
uint32_t to_le(uint32_t x) {
return std::endian::native == std::endian::little ? x : bswap(x);
}
int main() {
char ary[4] = { 0, 0, 0, (char) 0xF4 };
uint32_t v;
std::memcpy(&v, &ary, 4);
printf("%u %u\n", to_be(v), to_le(v));
return 0;
}
1
いやあ、C++20 は素晴らしいですね。使用してみたいもの (つまり概念) を頻繁に目にしますが、残念ながらそれらの十分な量が MSVC に実装されておらず、目的のためだけに C++20 を使いたくありません。何かが使えないことを学びます。そうは言っても、コメントには感謝しています。パケットを受信しているため、パケットは特定の順序で受信され (エンディアンを検出できるかどうかはわかりません)、これらのマクロは使用できないことがわかっているため、このルートを選択しませんでした。そうしないと、winsock を使用することになります。メソッド (hton など)。
– ブランドン・ウールワース
2020 年 9 月 5 日 15:08