38

Cの初心者として、ポインタをキャストしても実際に問題がないかどうかについて混乱しています。

私が理解しているように、ほとんどすべてのポインター型を他の型にキャストすることができ、コンパイラーはそれを可能にします。例えば:

int a = 5;
int* intPtr = &a;
char* charPtr = (char*) intPtr; 

ただし、一般に、これは未定義の動作を引き起こします(ただし、多くのプラットフォームで機能します)。とはいえ、いくつかの例外があるようです。

  • void*自由に行き来できます(?)
  • char*自由に行き来できます(?)

(少なくとも私はそれをコードで見ました...)。

では、ポインタ型間のキャストはどれがCの未定義動作ではないのでしょうか?

編集:

C標準( http://c0x.coding-guidelines.com/6.3.2.3.htmlのセクション「6.3.2.3ポインター」)を調べてみましたが、についてのビットを除いて、実際には理解していませんでしたvoid*

Edit2:

明確にするために:私は明示的に「通常の」ポインターについてのみ質問しています。つまり、関数ポインターについては質問していません。関数ポインタをキャストするためのルールは非常に制限されていることに気づきました。実際のところ、私はすでにそれについて質問しました:-):関数ポインターをキャストし、パラメーターの数を変更するとどうなりますか?

4

4 に答える 4

35

基本的に:

  • aT *は自由にaに変換し、void *また元に戻すことができ(T *関数ポインターではない場合)、元のポインターを取得します。
  • aT *は自由にaに変換され、U *また元に戻されます(ここでT *、およびU *は関数ポインターではありません)。配置要件が同じである場合は、元のポインターを取得します。そうでない場合、動作は未定義です。
  • 関数ポインタは、他の関数ポインタ型に自由に変換して元に戻すことができ、元のポインタを取得できます。

注:( T *非関数ポインターの場合)は、常に。のアライメント要件を満たしますchar *

重要:これらのルールはいずれも、たとえばaをaに変換してから、それを逆参照しようとするとどうなるかについては何も述べていません。これは、標準のまったく異なる領域です。T *U *

于 2011-01-26T21:52:55.857 に答える
9

Oli Charlesworthの優れた回答には、ポインターを別のタイプのポインターにキャストすると、明確に定義された結果が得られるすべてのケースがリストされています。

さらに、ポインターをキャストすると実装定義の結果が得られる場合が4つあります。

  • 十分に大きい(!)整数型へのポインターをキャストできます。C99にはオプションのタイプがintptr_tありuintptr_t、この目的のためにあります。結果は実装定義です。連続したバイトストリームとしてメモリをアドレス指定するプラットフォーム(「線形メモリモデル」、最近のほとんどのプラットフォームで使用)では、通常、ポインタが指すメモリアドレスの数値、つまり単純にバイトカウントを返します。ただし、すべてのプラットフォームが線形メモリモデルを使用しているわけではないため、これは実装定義です:-)。
  • 逆に、整数をポインタにキャストできます。整数の型がintptr_toruintptr_tに十分な大きさであり、ポインタをキャストすることによって作成された場合、同じポインタ型にキャストして戻すと、そのポインタが返されます(ただし、有効ではなくなる可能性があります)。それ以外の場合、結果は実装定義になります。(値を読み取るだけではなく)実際にポインターを逆参照することは、依然としてUBである可能性があることに注意してください。
  • 任意のオブジェクトへのポインタをにキャストできますchar*。次に、結果はオブジェクトのアドレス指定された最下位バイトを指し、ポインタをオブジェクトのサイズまでインクリメントすることで、オブジェクトの残りのバイトを読み取ることができます。もちろん、実際に取得する値は、やはり実装によって定義されます...
  • nullポインターは自由にキャストでき、ポインターの種類に関係なく常にnullポインターのままになります:-)。

出典:C99標準、セクション6.3.2.3「ポインター」および7.18.1.4「オブジェクトポインターを保持できる整数型」。

私が知る限り、異なるタイプのポインターへのポインターの他のすべてのキャストは未定義の動作です。特に、char整数型または十分に大きい整数型にキャストしていない場合は、間接参照しなくても、別のポインター型にポインターをキャストするのは常にUBである可能性があります。

これは、型の配置が異なる可能性があり、異なる型に互換性のある配置があることを確認するための一般的で移植可能な方法がないためです(符号付き/符号なし整数型のペアなどの特殊な場合を除く)。

于 2011-01-27T15:59:48.667 に答える
5

一般に、最近のように、ポインター自体が同じ配置プロパティを持っている場合、問題はキャスト自体ではなく、ポインターを介してデータにアクセスできるかどうかにあります。

T*任意の型を前後にキャストするvoid*ことは、すべてのオブジェクト型に対して保証されていますT。これにより、まったく同じポインタが返されることが保証されます。void*キャッチオールオブジェクトポインタ型です。

オブジェクトタイプ間の他のキャストの場合、保証はありません。そのようなポインタを介してオブジェクトにアクセスすると、アライメント(バスエラー)、整数のトラップ表現など、あらゆる種類の問題が発生する可能性があります。異なるポインタタイプが同じ幅を持つことさえ保証されていないため、理論的には情報が失われる可能性があります。

ただし、常に機能するはずのキャストの1つは(unsigned char*)。このようなポインタを介して、オブジェクトの個々のバイトを調査できます。

于 2011-01-26T22:04:57.117 に答える
0

規格の作成者は、次の理由から、そのようなサポートに費用がかかるプラットフォームで、ポインタータイプのほとんどの組み合わせ間で変換をサポートすることのコストと利点を比較検討しようとはしませんでした。

  1. このような変換に費用がかかるほとんどのプラットフォームは、標準の作成者が知らなかったものである可能性があります。

  2. そのようなプラットフォームを使用する人々は、そのようなサポートのコストと利点を備えた標準の作成者よりも適切に配置されます。

ある特定のプラットフォームがとの異なる表現を使用する場合、標準では、たとえば、からへのラウンドドリップ変換は一貫して機能するが、への変換とへの変換は失敗する 可能性を意図的に考慮しているint*double*思います。double*int*double*int*double*int*

規格の作成者は、そのような変換に費用がかからないプラットフォームでそのような操作が失敗する可能性があることを意図していないと思います。彼らは、憲章と理論的根拠の文書で、「プログラマーが必要なことをするのを妨げない(または不必要に妨害しない)」という原則を含むものとして、Cの精神を説明しました。その原則を考えると、実装がコストをかけない場合にプログラマーが必要なことを達成するのに役立つ方法で実装がアクションを処理することを標準が義務付ける必要はありません。スピリットオブCは、マンデートの有無にかかわらず、そのように動作します。

于 2018-12-03T16:36:57.827 に答える