voidポインター(void *)が2つのデータ型の1つであるかどうかを確認するにはどうすればよいですか?


10

2 type秒のパラメーターを受け入れたい関数を書いています。

  • A string(char *)
  • A structureにはn個の要素があります。

そして、これを達成するためにvoid *、パラメーターとして単純なタイプを使用することを考えています。しかし、私はパラメータがどちらかのタイプであるかどうかを安全に確認する方法がわかりません。


10
できません!少なくとも、関数に2番目のパラメーターを追加する必要がありますvoid*。これは、ポイントが何を指しているかを示します。
Adrian Mole、

4
...とにかく2番目のパラメーターを追加する必要がある場合は、2つの別個の関数func_strを記述func_structして、コンパイル時に型チェックを取得することもできます。
M Oehm

ええ、だから私はそれが1つの機能だけで可能かどうか考えていました
localhost

1
安全でポータブルな方法ではできません。勇気がある場合は、ヒューリスティックを使用して、メモリの最初のバイトが文字に期待できるものと同じであるかどうかを推測してみることもできますが、安全とは言えません。
Serge Ballesta

文字列関数と構造体関数の共通名だけが必要な場合は、_Genericマクロを使用できます。自己識別型を作成することもできます。たとえば、タグ付きunionsを使用すると、生のchar *文字列を渡すことができません。それはおそらく、それが価値があるよりももっと面倒です。
M Oehm

回答:


12

の翻訳void*は、
「親愛なるコンパイラ、これはポインタであり、これに関する追加情報はありません。」です。

通常、コンパイラーはあなた(プログラマー)よりもよく知っています。なぜなら、彼が以前に入手した情報がまだ記憶されていて、あなたが忘れていた可能性があるからです。
しかし、この特別なケースでは、あなたはもっとよく知っているか、もっとよく知る必要があります。すべての場合においてvoid*、情報は他の方法で利用可能ですが、「知りたがっている」プログラマのみが利用できます。そのため、プログラマーはコンパイラー(または実行中のプログラム)void*に情報を提供する必要があります。これは、ランタイム中に情報が変更される可能性があるためです。
通常、これは、追加のパラメーターを介して、時にはコンテキストを介して、つまりプログラムが「知っている」ことで情報を提供することによって行われます(たとえば、考えられるタイプごとに、個別の関数があり、呼び出される関数はそのタイプを意味します)。

したがって、最後にvoid*はタイプ情報が含まれていません。
多くのプログラマーはこれを「タイプ情報を知る必要がない」と誤解しています。
しかし、その逆は真です。タイプ情報を追跡し、それをプログラム/コンパイラに適切に提供するプログラマの責任がvoid* 増加します。


さらに、コンパイラーは実際に、示されるデータのタイプが何であるかを認識しています。したがって、あるvoid*関数にジャンプして、間違った型にキャストし、データを逆参照すると、あらゆる種類の未定義の動作が呼び出されます。
ランディン

5

void*は一般的なプログラミングでは推奨されていませんが、今日それらを使用する必要がある状況は多くありません。存在しないタイプセーフティにつながるため、危険です。また、お気付きのように、タイプ情報も失われます。enumつまり、と一緒に扱いにくいものをドラッグする必要がありますvoid*

代わりに_Generic、コンパイル時に型をチェックして型の安全性を追加できるC11 を使用する必要があります。例:

#include <stdio.h>

typedef struct
{
  int n;
} s_t; // some struct

void func_str (const char* str)
{
  printf("Doing string stuff: %s\n", str);
}

void func_s (const s_t* s)
{
  printf("Doing struct stuff: %d\n", s->n);
}

#define func(x) _Generic((x),              \
  char*: func_str, const char*: func_str,  \
  s_t*:  func_s,   const s_t*:  func_s)(x) \


int main()
{
  char str[] = "I'm a string";
  s_t s = { .n = 123 };

  func(str);
  func(&s); 
}

constサポートするすべてのタイプの認定()バージョンを提供することを忘れないでください。


呼び出し元が間違った型を渡したときにコンパイラエラーを改善したい場合は、静的アサートを追加できます。

#define type_check(x) _Static_assert(_Generic((x), \
  char*:   1,  const char*: 1,  \
  s_t*:    1,  const s_t*:  1,  \
  default: 0), #x": incorrect type.")

#define func(x) do{ type_check(x); _Generic((x),     \
  char*: func_str, const char*: func_str,            \
  s_t*:  func_s,   const s_t*:  func_s)(x); }while(0) 

次のようなint x; func(x);ことを試みると、コンパイラメッセージが表示されます"x: incorrect type"

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.