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およびoutgoing_bufは次のように定義されます。
char incoming_buf[LIBIRC_DCC_BUFFER_SIZE];
char outgoing_buf[LIBIRC_DCC_BUFFER_SIZE];
これは、私が調べてきた警告の他の例とは微妙に異なるようです。厳密なエイリアスチェックを無効にするのではなく、問題を修正したいと思います。
ユニオンを使用するための多くの提案がありました-このケースに適したユニオンは何ですか?
まず、エイリアス違反の警告が表示される理由を調べてみましょう。
エイリアスルールオブジェクトにアクセスできるのは、独自の型、符号付き/符号なしのバリアント型、または文字型(char
、signed char
、unsigned char
)のみです。
Cは、エイリアシングルールに違反すると未定義の動作を呼び出すと述べています(so do n't!)。
プログラムの次の行で:
unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));
incoming_buf
配列の要素はchar
型ですが、unsigned int
としてアクセスしています。実際、式*((unsigned int*)dcc->incoming_buf)
の間接参照演算子の結果はunsigned int
型です。
これは、incoming_buf
配列の要素にアクセスする権利しか持たないため(上記のルールの概要を参照してください!)char
、signed char
、またはunsigned char
のエイリアス規則に違反しています。
2番目の犯人にもまったく同じエイリアシングの問題があることに注意してください。
*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);
outgoing_buf
のchar
要素には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
が32ビットであると想定している場合はunsigned int
の使用を検討する必要があります)。
このように、タイプchar
を介して要素にアクセスすることにより、エイリアスルールに違反することなく、配列にunsigned int
オブジェクトを格納できます。
*((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)
のcharsの配列の最初の要素へのポインタとして。
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 ++標準では、データを自分で調整しようとする必要があると記載されています。 2.アーキテクチャ/システムとコード内のデータのアライメントを完全に理解している場合にのみ試してください(たとえば、上記のコードはIntel 32/64で確実です;アライメント1; Win/Linux/Bsd/Mac) 3.上記のコードを使用する唯一の実用的な理由は、コンパイラの警告を回避することです。
ソースオブジェクトのタイプを変更できない理由がある場合(私の場合のように)、コードが正しいことと、警告を避けるためにそのchar配列で意図したことを実行することを絶対に確信している場合次のことを実行できます。
unsigned int* buf = (unsigned int*)dcc->incoming_buf;
unsigned int received_size = ntohl (*buf);
私の場合、この場合、問題は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;
};