タイプに渡るポインターの解示の修正は、厳格な分離を破ります
-
27-10-2019 - |
質問
GCCを使用して特定のプログラムをコンパイルするときに、2つの警告を修正しようとしています。警告は次のとおりです。
警告:タイプに渡るポインターが厳格な拡張ルールを破る[-wstrict-aliasing
そして、2人の犯人は次のとおりです。
unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));
と
*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);
incoming_buf と outving_buf 次のように定義されています。
char incoming_buf[LIBIRC_DCC_BUFFER_SIZE];
char outgoing_buf[LIBIRC_DCC_BUFFER_SIZE];
これは、私が調査してきたその警告の他の例とは微妙に異なるようです。厳密なチェックを無効にするのではなく、問題を解決したいと思います。
組合を使用するための多くの提案がありました - この事件に適した組合は何でしょうか?
解決
まず、エイリアス違反の警告を受け取る理由を調べてみましょう。
エイリアシングルール 独自のタイプ、署名 /符号なしのバリアントタイプ、または文字タイプを通じてのみオブジェクトにアクセスできると言ってください(または)char
, signed char
, unsigned char
).
cは、エイリアシングルールに違反すると未定義の動作を呼び出すと言います(だからしないで!).
あなたのプログラムのこの行で:
unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));
ただし incoming_buf
配列はタイプです char
, 、あなたはそれらとしてそれらにアクセスしています unsigned int
. 。実際、式における抑制演算子の結果 *((unsigned int*)dcc->incoming_buf)
ofです unsigned int
タイプ。
これは、エイリアシングルールの違反です。 incoming_buf
配列を通して(上記のルールの概要を参照してください!) char
, signed char
また unsigned char
.
2番目の犯人にはまったく同じエイリアシングの問題があることに注意してください。
*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);
あなたはにアクセスします char
の要素 outgoing_buf
終えた unsigned int
, 、それはエイリアス違反です。
提案されたソリューション
問題を修正するには、アクセスするタイプでアレイの要素を直接定義するようにすることができます。
unsigned int incoming_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
unsigned int outgoing_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
(ちなみにの幅 unsigned int
実装は定義されているので、使用を検討する必要があります uint32_t
あなたのプログラムが想定している場合 unsigned int
32ビットです)。
この方法で保存できます unsigned int
タイプを介して要素にアクセスしてエイリアシングルールに違反することなく、配列内のオブジェクト char
, 、 このような:
*((char *) outgoing_buf) = expr_of_type_char;
また
char_lvalue = *((char *) incoming_buf);
編集:
私は自分の答えを完全に作り直しました。特に、プログラムがコンパイラからエイリアスの警告を取得する理由を説明します。
他のヒント
問題を修正するには、 しゃれやエイリアスをしないでください!タイプを読む唯一の「正しい」方法 T
タイプを割り当てることです T
必要に応じてその表現を入力します。
uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);
要するに、整数が必要な場合は、整数を作成する必要があります。それを言語に伝えられた方法で倒す方法はありません。
許可されている唯一のポインター変換は(一般的にI/Oの目的のために)アドレスを扱うことです 既存の変数 タイプの T
として char*
, 、またはむしろ、サイズの文字の配列の最初の要素へのポインターとして sizeof(T)
.
union
{
const unsigned int * int_val_p;
const char* buf;
} xyz;
xyz.buf = dcc->incoming_buf;
unsigned int received_size = ntohl(*(xyz.int_val_p));
簡素化された説明1. C ++標準では、データを自分で整列させようとする必要があると述べています。G++は、被験者に関する警告を生成するためにさらに1マイルになります。 2.アーキテクチャ/システムとコード内のデータアラインメントを完全に理解している場合にのみ試してみてください(たとえば、上記のコードはIntel 32/64; Alignment 1; Win/Linux/BSD/Macの確実なものです) 3.上記のコードを使用する唯一の実用的な理由は、コンパイラ警告を避けることです。
私がそうかもしれない場合、この場合、問題はntohlとhtonlおよび関連関数APIの設計です。それらは、数値戻りで数値引数として書かれているべきではありません。 (そして、はい、私はマクロの最適化ポイントを理解しています)彼らはバッファーへのポインターである「n」側として設計されるべきでした。これが完了すると、問題全体がなくなり、ルーチンはホストのエンディアンのどちらの場合でも正確です。たとえば(最適化しようとする試みがない):
inline void safe_htonl(unsigned char *netside, unsigned long value) {
netside[3] = value & 0xFF;
netside[2] = (value >> 8) & 0xFF;
netside[1] = (value >> 16) & 0xFF;
netside[0] = (value >> 24) & 0xFF;
};
ソースオブジェクトのタイプを変更することを許可しない理由(私の場合のように)があり、コードが正しいと確信しており、そのchar配列で行うことを意図したことを行い、警告を避けるために以下を行うことができます:
unsigned int* buf = (unsigned int*)dcc->incoming_buf;
unsigned int received_size = ntohl (*buf);
ポインターを符号なしでキャストしてから、ポインターに戻ります。
unsigned int receed_size = ntohl( *((unsigned *)((unsigned)dcc-> incoming_buf)));