web-dev-qa-db-ja.com

Cでのポインタの型キャスト

ある型へのポインターが別の型のポインターに変換される可能性があることを知っています。 3つの質問があります。

  1. ポインタを型キャストするとき、何に注意すべきですか?
  2. 結果として生じるポインタにはどのような例外/エラーが発生する可能性がありますか?
  3. 例外/エラーを回避するためのベストプラクティスは何ですか?
16
user1871762

よく書かれたプログラムは通常、ポインタ型キャストをあまり使用しません。たとえば、mallocにptr型キャストを使用する必要がある場合があります(_(void *)malloc(...)_と宣言されています)。 。

_  int *p = malloc(sizeof(int)); // no need of (int *)malloc(...)
_

ただし、システムアプリケーションでは、バイナリまたは特定の操作を実行するためにトリックを使用したい場合があります。マシン構造に近い言語であるCは、そのために便利です。たとえば、(IEEE 754の実装に従う)doubleのバイナリ構造を分析し、バイナリ要素の操作がより簡単であると宣言したとします。

_  typedef unsigned char byte;
  double d = 0.9;
  byte *p = (byte *)&d;
  int i;
  for (i=0 ; i<sizeof(double) ; i++) { ... work with b ... }
_

unionを使用することもできます。これは例です。

より複雑な使用法は C++ polymorphism のシミュレーションである可能性があります。これは、「クラス」(構造)階層をどこかに格納し、何が何であるかを覚えて、ポインタ型キャストを実行して、たとえば、派生クラスを指す親「クラス」ポインタ変数(C++リンクも参照)

_  CRectangle rect;
  CPolygon *p = (CPolygon *)&rect;
  p->whatami = POLY_RECTANGLE; // a way to simulate polymorphism ...
  process_poly ( p );
_

ただし、この場合は、C++を直接使用する方がよいでしょう。

ポインタ型キャストは、プログラム分析の一部である決定された状況(開発が始まる前)に対して慎重に使用されます。

ポインタ型キャストの潜在的な危険性

  • 必要のないときに使用してください-エラーが発生しやすく、プログラムが複雑になります
  • アクセスオーバーフローを引き起こす可能性のある異なるサイズのオブジェクトを指している、間違った結果...
  • s1 *p = (s1 *)&s2;のような2つの異なる構造へのポインタ:サイズと配置に依存するとエラーが発生する可能性があります

(公平を期すために、熟練したCプログラマは上記の間違いを犯しません...)

ベストプラクティス

  • それらが必要な場合にのみ使用し、なぜそれが必要なのかを説明する部分にコメントしてください
  • あなたが何をしているのかを知ってください-熟練したプログラマーは間違いなく大量のポインター型キャストを使用する可能性があります。
12
Ring Ø

プレーンCでは、任意のポインター型を他のポインター型にキャストできます。互換性のない型との間でポインタをキャストし、メモリを誤って書き込むと、アプリケーションからセグメンテーション違反または予期しない結果が発生する可能性があります。

以下は、構造ポインタをキャストするサンプルコードです。

struct Entity { 
  int type;
}

struct DetailedEntity1 {
  int type;
  short val1;
}

struct DetailedEntity2 {
  int type;
  long val;
  long val2;
}

// random code:
struct Entity* ent = (struct Entity*)ptr;

//bad:
struct DetailedEntity1* ent1 = (struct DetailedEntity1*)ent;
int a = ent->val; // may be an error here, invalid read
ent->val = 117; // possible invali write

//OK:
if (ent->type == DETAILED_ENTITY_1) {
  ((struct DetailedEntity1*)ent)->val1;
} else if (ent->type == DETAILED_ENTITY_2) {
  ((struct DetailedEntity2*)ent)->val2;
} 

関数ポインタについては-宣言に完全に適合する関数を常に使用する必要があります。そうしないと、予期しない結果やセグメンテーション違反が発生する可能性があります。

ポインターからポインター(構造かどうか)にキャストする場合は、メモリが aligned であることを確認する必要があります。構造全体をキャストする場合は、最初に同じ変数の同じ順序を使用し、「共通ヘッダー」の後にのみ構造を区別することが最善の方法です。また、メモリの配置はマシンごとに異なる可能性があるため、構造体ポインタをバイト配列として送信し、それをバイト配列として受信することはできません。予期しない動作やsegfaultが発生する場合があります。

小さい変数ポインターを大きい変数ポインターにキャストするときは、非常に注意する必要があります。このコードを考えてみましょう:

char* ptr = malloc (16);
ptr++;
uint64_t* uintPtr = ptr; // may cause an error, memory is not properly aligned

また、従う必要がある strict aliasing ルールもあります。

3
Dariusz

C-faqは、Steve Summit(以前はニュースグループ。これは、当時多くの優秀なプログラマーが読んだり更新したりすることを意味します。時には、言語自体の概念化者もいます)。

要約版もあります。これはおそらくより口当たりがよく、それでも非常に、非常に、非常に、非常に便利です。 Cを使用する場合、全体を要約して読むことは必須だと思います。

0
Olivier Dulac