web-dev-qa-db-ja.com

型パンチされたポインターの逆参照の修正により、厳密なエイリアスが解除されます。

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];

これは、私が調べてきた警告の他の例とは微妙に異なるようです。厳密なエイリアスチェックを無効にするのではなく、問題を修正したいと思います。

ユニオンを使用するための多くの提案がありました-このケースに適したユニオンは何ですか?

24
BlankFrank

まず、エイリアス違反の警告が表示される理由を調べてみましょう。

エイリアスルールオブジェクトにアクセスできるのは、独自の型、符号付き/符号なしのバリアント型、または文字型(charsigned charunsigned 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配列の要素にアクセスする権利しか持たないため(上記のルールの概要を参照してください!)charsigned char、またはunsigned charのエイリアス規則に違反しています。

2番目の犯人にもまったく同じエイリアシングの問題があることに注意してください。

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

outgoing_bufchar要素には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);

編集:

答えを完全に作り直しました。特に、プログラムがコンパイラからエイリアス警告を受け取る理由を説明します。

40
ouah

この問題を解決するには、しゃがんではいけない!型Tを読み取るための唯一の「正しい」方法は、型Tを割り当て、必要に応じてその表現を設定することです。

_uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);
_

つまり、整数が必要な場合は、整数を作成する必要があります。言語に配慮した方法でそれを回避する方法はありません。

(一般的にI/Oの目的で)許可される唯一のポインター変換は、タイプTの-​​既存の変数のアドレスを_char*_として扱うことです。むしろ、サイズsizeof(T)のc​​harsの配列の最初の要素へのポインタとして。

21
Kerrek SB
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.上記のコードを使用する唯一の実用的な理由は、コンパイラの警告を回避することです。

4
Real Name

ソースオブジェクトのタイプを変更できない理由がある場合(私の場合のように)、コードが正しいことと、警告を避けるためにそのchar配列で意図したことを実行することを絶対に確信している場合次のことを実行できます。

unsigned int* buf = (unsigned int*)dcc->incoming_buf;
unsigned int received_size = ntohl (*buf);
0
Oleg Oleg

私の場合、この場合、問題は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;
};
0
Henri Socha