web-dev-qa-db-ja.com

なぜこれが型参照されたポインタ参照警告の逆参照をコンパイラ固有のものと主張するのですか

私は読んだ variouspostsonStack Overflow RE:逆参照型でパンされたポインタエラー。私の理解では、エラーは本質的に、異なる型のポインターを介してオブジェクトにアクセスする危険性に関するコンパイラーの警告である(ただし、_char*_には例外が発生しているように見えます)が、理解可能で合理的な警告です。

私の質問は、以下のコードに固有です:ポインタのアドレスを_void**_にキャストすると、この警告の対象になります(_-Werror_によってエラーに昇格)?

さらに、このコードは複数のターゲットアーキテクチャ用にコンパイルされており、そのうちの1つだけが警告/エラーを生成します-これは合法的にコンパイラのバージョン固有の欠陥であることを意味しますか?

_// main.c
#include <stdlib.h>

typedef struct Foo
{
  int i;
} Foo;

void freeFunc( void** obj )
{
  if ( obj && * obj )
  {
    free( *obj );
    *obj = NULL;
  }
}

int main( int argc, char* argv[] )
{
  Foo* f = calloc( 1, sizeof( Foo ) );
  freeFunc( (void**)(&f) );

  return 0;
}
_

上記の私の理解が正しい場合、_void**_はまだ単なるポインタなので、これは安全にキャストできます。

このコンパイラ固有の警告/エラーを緩和する回避策lvaluesを使用しないはありますか?つまり私はそれが問題を解決する理由を理解していますが、freeFunc()[〜#〜] null [〜#〜]を利用したいので、このアプローチは避けたいと思います。 =意図された出力引数:

_void* tmp = f;
freeFunc( &tmp );
f = NULL;
_

問題のコンパイラ(1つ):

_user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-fc3-linux-gnu-gcc (GCC) 3.4.5
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.c: In function `main':
./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

user@8d63f499ed92:/build$
_

不満のないコンパイラ(多くの1つ):

_user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-rh73-linux-gnu-gcc (GCC) 3.2.3
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

user@8d63f499ed92:/build$
_

更新:さらに、_-O2_を使用してコンパイルした場合に警告が生成されるように見えることを発見しました(ただし、「問題のあるコンパイラ」のみが表示されます)

38
StoneThrow

タイプ_void**_の値は、タイプ_void*_のオブジェクトへのポインターです。タイプ_Foo*_のオブジェクトはタイプ_void*_のオブジェクトではありません。

タイプ_Foo*_のvaluesと_void*_の間には暗黙的な変換があります。この変換により、値の表現が変わる場合があります。同様に、_int n = 3; double x = n;_と書くと、xを値_3.0_に設定する明確な動作がありますが、double *p = (double*)&n;には未定義の動作があります(実際には一般的なアーキテクチャでは、pを「_3.0_へのポインタ」に設定しません)。

オブジェクトへの異なるタイプのポインターが異なる表現を持つアーキテクチャーは、今日ではまれですが、C標準では許可されています。 Wordポインタがメモリ内のWordのアドレスであり、バイトポインタがこのWordのバイトオフセットとともにWordのアドレスである(まれな)古いマシンがあります。そのようなアーキテクチャーでは、_Foo*_はWordポインターであり、_void*_はバイトポインターです。 ファットポインターの(まれな)マシンがあり、オブジェクトのアドレスだけでなく、そのタイプ、サイズ、およびアクセス制御リストに関する情報も含まれています。明確な型へのポインタは、実行時に追加の型情報を必要とする_void*_とは異なる表現を持つ場合があります。

このようなマシンはまれですが、C標準で許可されています。また、一部のCコンパイラは、コードを最適化するために、タイプ制限されたポインタを別個のものとして扱う許可を利用します。ポインターのエイリアシングのリスクは、コードを最適化するコンパイラーの機能に対する主要な制限であるため、コンパイラーはそのような許可を利用する傾向があります。

コンパイラは、あなたが何か間違ったことをしているとあなたに告げたり、あなたが望んでいないことを静かにしたり、あなたが望んでいたことを静かにしたりすることは自由です。未定義の動作は、これらのいずれかを許可します。

freefuncをマクロにすることができます:

_#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)
_

これには、マクロの通常の制限が伴います。型の安全性の欠如、pは2回評価されます。これは、pが解放されたオブジェクトへの単一のポインターである場合に、ダングリングポインターを残さないという安全性を与えるだけであることに注意してください。

void *は不完全な型を参照するため、C標準で特別に扱われます。この処理はnotvoid **に拡張しますdoesは完全な型、具体的にはvoid *を指します。

厳密なエイリアシングルールでは、ある型のポインターを別の型のポインターに変換し、その後そのポインターを逆参照することはできないとしています。唯一の例外は、オブジェクトの表現を読み取ることができる文字タイプに変換する場合です。

この制限を回避するには、関数の代わりに関数のようなマクロを使用します。

#define freeFunc(obj) (free(obj), (obj) = NULL)

あなたはこのように呼び出すことができます:

freeFunc(f);

ただし、上記のマクロはobjを2回評価するため、これには制限があります。 GCCを使用している場合、これはいくつかの拡張、特にtypeofキーワードとステートメント式で回避できます。

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })
12
dbush

タイプでパンされたポインターの逆参照はUBであり、何が起こるかを期待することはできません。

コンパイラが異なれば警告も異なります。このため、同じコンパイラの異なるバージョンを異なるコンパイラと見なすことができます。これは、アーキテクチャへの依存よりも、分散のより良い説明のようです。

この場合の型パンニングが悪い理由を理解するのに役立つケースは、関数がsizeof(Foo*) != sizeof(void*)のアーキテクチャで機能しないことです。これは標準で承認されていますが、これが真実である現在のバージョンは知りません。

回避策は、関数の代わりにマクロを使用することです。

freeはnullポインターを受け入れることに注意してください。

9
AProgrammer

このコードはC標準に従って無効であるため、場合によっては機能する可能性がありますが、必ずしも移植可能ではありません。

別のポインター型にキャストされたポインターを介して値にアクセスするための「厳密なエイリアス規則」は、6.5段落7にあります。

オブジェクトは、次のいずれかのタイプの左辺値式によってのみアクセスされる格納された値を持つ必要があります。

  • オブジェクトの有効なタイプと互換性のあるタイプ

  • オブジェクトの有効な型と互換性のある型の修飾バージョン

  • オブジェクトの有効な型に対応する符号付きまたは符号なしの型である型

  • オブジェクトの有効な型の修飾バージョンに対応する符号付きまたは符号なしの型である型

  • メンバーの中に前述のタイプの1つを含む集約またはユニオンタイプ(再帰的に、サブアグリゲートまたは含まれるユニオンのメンバーを含む)、または

  • 文字タイプ。

*obj = NULL;ステートメントでは、オブジェクトは有効なタイプFoo*を持っていますが、タイプ*objの左辺値式void*によってアクセスされます。

6.7.5.1パラグラフ2では、

2つのポインター型が互換性を持つためには、どちらも同じように修飾され、どちらも互換型へのポインターでなければなりません。

したがって、void*Foo*は互換性のある型または修飾子が追加された互換性のある型ではなく、厳密なエイリアシングルールの他のオプションには適合しません。

コードが無効であるという技術的な理由ではありませんが、セクション6.2.5のパラグラフ26の注記にも関連しています。

voidへのポインターは、文字型へのポインターと同じ表現と位置合わせの要件を持っている必要があります。同様に、互換性のある型の修飾されたバージョンまたは修飾されていないバージョンへのポインターは、同じ表現と配置の要件を持たなければなりません。構造体型へのすべてのポインタは、互いに同じ表現と配置要件を持たなければなりません。共用体型へのすべてのポインタは、互いに同じ表現と整列の要件を持たなければなりません。他の型へのポインターは、同じ表現または配置要件を持つ必要はありません。

警告の違いについては、これは標準が診断メッセージを必要とする場合ではないので、コンパイラーまたはそのバージョンが潜在的な問題に気づき、それらを役立つ方法で指摘するのにどれだけ優れているかだけの問題です。最適化の設定が違いを生むことに気づきました。これは多くの場合、実際にはプログラムのさまざまな部分が実際にどのように組み合わされるかについて、より多くの情報が内部で生成されるため、その追加情報は警告チェックにも利用できます。

4
aschepler

Cはすべてのポインターに同じ表現を使用するマシン用に設計されましたが、規格の作成者は、異なるタイプのオブジェクトへのポインターに異なる表現を使用するマシンで言語を使用できるようにしたいと考えました。したがって、さまざまな種類のポインターに異なるポインター表現を使用するマシンは、「あらゆる種類のポインターへのポインター」タイプをサポートする必要はありませんでした。

標準が作成される前は、すべてのポインタ型に同じ表現を使用するプラットフォームの実装では、満場一致でvoid**少なくとも適切なキャストで、「任意のポインターへのポインター」として使用されます。標準の作成者は、これがそれをサポートするプラットフォームで役立つことをほぼ確実に認識しましたが、それは普遍的にサポートすることができなかったので、それを義務付けることを拒否しました。その代わりに、彼らは、理にかなっているのが理にかなっている場合に、理論的根拠が「人気のある拡張」として説明するような構造を質の高い実装が処理することを期待していました。

2
supercat

他の回答が述べたことに加えて、これはCでのクラシックなアンチパターンであり、火で燃やされるべきものです。それはに現れます:

  1. あなたが警告を見つけたようなフリーアンドヌルアウト機能。
  2. void *を返す標準のCイディオムを回避する割り当て関数(これにはtype punningではなくvalue conversionが含まれるため、この問題は発生しません)、代わりに、エラーフラグを返し、ポインタツーポインタを介して結果を保存します。

(1)の別の例として、ffmpeg/libavcodecのav_free関数に長年にわたって悪名高い事例がありました。最終的にはマクロやその他のトリックで修正されたと思いますが、よくわかりません。

(2)の場合、 cudaMallocposix_memalignの両方が例です。

どちらの場合も、インターフェイスは本質的にrequire無効な使用法ですが、それを強く推奨し、フリーの目的を無効にするvoid *タイプの追加の一時オブジェクトでのみ正しい使用法を認めます-null-out機能、および割り当てが厄介になります。