プログラム自体のメカニズムを介して、または(おそらく)コンパイラのパスを使用する事前コンパイルスクリプトを介して、Cで変数のタイプを自動的に検出できる方法はありますか?変数を解析し、それらにタイプを割り当てましたか?私はこれについての一般的な提案を探しています。以下は、私が必要としているものとその理由についてのより多くの背景です。
OpenMP削減句のセマンティクスを変更したいと思います。この時点で、ソースコード内の句を(スクリプトを介して)関数の呼び出しに置き換えるのが最も簡単なようです。次に、必要な削減セマンティクスを実装するように関数を定義できます。たとえば、私のスクリプトはこれを変換します
#pragma omp parallel for reduction(+:x)
これに:
my_reduction(PLUS, &x, sizeof(x));
#pragma omp parallel for
以前、私は(言う)
enum reduction_op {PLUS, MINUS, TIMES, AND,
OR, BIT_AND, BIT_OR, BIT_XOR, /* ... */};
そしてmy_reduction
署名があります
void my_reduction(enum reduction_op op, void * var, size_t size);
とりわけ、 my_reduction
プログラマーが当初意図したように、加算演算を縮小変数に適用する必要があります。しかし、私の関数はこれを正しく行う方法を知ることができません。特に、操作の種類(PLUS
)、元の変数の場所(var
)、および変数の型のサイズは知っていますが、変数の型自体は知りません。 。特に、var
が整数型か浮動小数点型かはわかりません。低レベルのPOVとは、これら2つのクラスのタイプの加算操作は完全に異なります。
GCCがサポートする非標準の演算子typeof
だけが、sizeofの動作と同じように機能し、ある種の型変数を返す場合、この問題を簡単に解決できます。しかし、typeofは実際にはsizeofとは異なります。明らかに、l値宣言でのみ使用できます。
これで、コンパイラは、実行可能コードの生成が完了する前に、明らかにxのタイプを認識します。これにより、GCCのパーサーを利用して、x
の型を取得し、それをスクリプトに渡してから、GCCを最後まで実行して、変更したソースコードをコンパイルできるかどうか疑問に思います。そうすれば、宣言するのに十分簡単になります
enum var_type { INT8, UINT8, INT16, UINT16, /* ,..., */ FLOAT, DOUBLE};
void my_reduction(enum reduction_op op, void * var, enum var_type vtype);
そしてmy_reduction
演算子を逆参照して適用する前に、適切にキャストできます。
ご覧のとおり、私はCで一種の「ディスパッチ」メカニズムを作成しようとしています。C++のオーバーロードを使用しないのはなぜですか。私のプロジェクトでは、Cで記述されたレガシーソースコードを使用する必要があるためです。スクリプトを使用してコードを自動的に変更することはできますが、別の言語に書き直すことはできません。
ありがとう!
C11 _Generic
直接的な解決策ではありませんが、次のようにすべてのタイプをコーディングすることに忍耐強い場合は、目的の結果を達成できます。
#include <assert.h>
#include <string.h>
#define typename(x) _Generic((x), \
int: "int", \
float: "float", \
default: "other")
int main(void) {
int i;
float f;
void* v;
assert(strcmp(typename(i), "int") == 0);
assert(strcmp(typename(f), "float") == 0);
assert(strcmp(typename(v), "other") == 0);
}
コンパイルして実行する:
gcc -std=c11 a.c
./a.out
たくさんのタイプの良い出発点を見つけることができます この答えで 。
Ubuntu 17.10、GCC7.2.0でテスト済み。 GCCは4.9でのみサポートを追加しました。
大量のマクロを記述しない限り、Cにはコンパイル前にこれを実行する方法が実際にはありません。私はnotマクロの洪水アプローチをお勧めします、それは基本的に次のようになります:
_void int_reduction (enum reduction_op op, void * var, size_t size);
#define reduction(type,op,var,size) type##_reduction(op, var, size)
...
reduction(int, PLUS, &x, sizeof(x)); // function call
_
これは非常に悪い習慣であり、それでも、不十分に記述されたレガシーコードを維持する場合の最後の手段としてのみ使用する必要があることに注意してください。このアプローチでは、型安全性やその他のそのような保証はありません。
より安全なアプローチは、呼び出し元からint_reduction()
を明示的に呼び出すか、実行時に型を決定するジェネリック関数を呼び出すことです。
_void reduction (enum type, enum reduction_op op, void * var, size_t size)
{
switch(type)
{
case INT_TYPE:
int_reduction(op, var, size);
break;
...
}
}
_
Int_reductionがインライン化され、他のさまざまな最適化が行われる場合、このランタイム評価は、難読化されたマクロよりも必ずしもそれほど遅くはありませんが、はるかに安全です。
Sizeof関数を使用して型を判別し、不明な型の変数をvarとします。その後
if(sizeof(var)==sizeof(char))
printf("char");
else if(sizeof(var)==sizeof(int))
printf("int");
else if(sizeof(var)==sizeof(double))
printf("double");
2つ以上のプライマリタイプが同じサイズである可能性がある場合、問題が発生します。
GCCは typeof 拡張機能を提供します。これは標準ではありませんが、十分に一般的です(clang/llvmなどの他のいくつかのコンパイラーにあります)。
目的に合わせて [〜#〜] melt [〜#〜] (GCCを拡張するためのドメイン固有言語)で拡張することにより、GCCをカスタマイズすることを検討できます。
必要に応じて、プラグインまたは [〜#〜] melt [〜#〜] 拡張機能を使用してGCCをカスタマイズすることも検討できます。ただし、これには、複雑なGCC内部表現(Gimple、Tree)のいくつかを理解する必要があります(したがって、少なくとも数日かかるでしょう)。
しかし、型はCではコンパイルのみのものです。それらは具体化されていません。