Cに型をもう少し認識させ、型の安全性を保証する方法はありますか?
このことを考慮:
typedef unsigned cent_t;
typedef unsigned dollar_t;
#define DOLLAR_2_CENT(dollar) ((cent_t)(100*(dollar)))
void calc(cent_t amount) {
// expecting 'amount' to semantically represents cents...
}
int main(int argc, char* argv[]) {
dollar_t amount = 50;
calc(DOLLAR_2_CENT(amount)); // ok
calc(amount); // raise warning
return 0;
}
上記のコードを少なくともgccで警告を発生させる方法はありますか?
C構造体を使用してunsigned
sをラップし、目的の結果を得ることができることを知っています。もっとエレガントな方法があるかどうか疑問に思っていました。
それだけでは足りませんか?
これを実現するには、ビルドプロセスで静的分析ツールを使用する必要があります。
たとえば、コードでPCLintを実行すると、次の出力が得られます。
[Warning 632] Assignment to strong type 'cent_t' in context: arg. no. 1
[Warning 633] Assignment from a strong type 'dollar_t' in context: arg. no. 1
問題は、2つのtypedefがどちらもunsigned
型であるため、Cが2つのtypedefを区別できる型として扱わないことです。
これを回避するためのさまざまなトリックがあります。 1つは、型を列挙型に変更することです。優れたコンパイラーは、特定の列挙型と他の型の間の暗黙の変換に対して、より強い型付け警告を強制します。
あなたが良いコンパイラを持っていなくても、列挙型でこれを行うことができます:
typedef enum { FOO_CENT } cent_t;
typedef enum { FOO_DOLLAR} dollar_t;
#define DOLLAR_2_CENT(dollar) ((cent_t)(100*(dollar)))
void calc(cent_t amount) {
// expecting 'amount' to semantically represents cents...
}
#define type_safe_calc(amount) _Generic(amount, cent_t: calc(amount))
int main(int argc, char* argv[]) {
dollar_t amount = 50;
type_safe_calc(DOLLAR_2_CENT(amount)); // ok
type_safe_calc(amount); // raise warning
return 0;
}
より一般的/伝統的なトリックは、型をマークするために「チケット」列挙型を使用する、汎用の構造体ラッパーを使用することです。例:
typedef struct
{
type_t type;
void* data;
} wrapper_t;
...
cent_t my_2_cents;
wrapper_t wrapper = {CENT_T, &my_2_cents};
...
switch(wrapper.type)
{
case CENT_T: calc(wrapper.data)
...
}
利点は、どのCバージョンでも動作することです。欠点は、コードとメモリのオーバーヘッドであり、実行時のチェックしかできないことです。
エイリアシングは、Cで非常に特定の狭い意味を持ち、それはあなたが考えているものではありません。 「typedefing」と言いたいかもしれません。
そして答えはノーです、あなたはできません。とにかくエレガントな方法ではありません。数値型ごとに構造体を使用し、個別の関数セットを使用してそれぞれの数値を計算できます。掛け算に関しては、運が悪い。フィートをポンドで乗算するには、3番目のタイプが必要です。また、フィートの2乗、フィートの3乗、秒のマイナス2の累乗、および無限の他のタイプのタイプも必要です。
これがあなたの望んでいることなら、Cは適切な言語ではありません。
編集:コンパイラが_Generic
セレクターをサポートしていない場合に備えて、C89でも機能する代替案(多くのコンパイラーはサポートしておらず、多くの場合、マシンにインストールされているもので立ち往生しています)。
マクロを使用すると、struct
ラッパーの使用を簡略化できます。
#define NEWTYPE(nty,oty) typedef struct { oty v; } nty
#define FROM_NT(ntv) ((ntv).v)
#define TO_NT(nty,val) ((nty){(val)}) /* or better ((nty){ .v=(val)}) if C99 */
NEWTYPE(cent_t, unsigned);
NEWTYPE(dollar_t, unsigned);
#define DOLLAR_2_CENT(dollar) (TO_NT(cent_t, 100*FROM_NT(dollar)))
void calc(cent_t amount) {
// expecting 'amount' to semantically represents cents...
}
int main(int argc, char* argv[]) {
dollar_t amount = TO_NT(dollar_t, 50); // or alternatively {50};
calc(DOLLAR_2_CENT(amount)); // ok
calc(amount); // raise warning
return 0;
}
警告よりも強くなります。これはgcc 5.1でのコンパイル結果です
$ gcc -O3 -Wall Edit1.c Edit1.c:関数「main」内: Edit1.c:17:10: エラー: ‘calc’ calc(amount);の引数1に互換性のないタイプ。 //警告を出します ^ Edit1.c:10:6: 注意: ‘cent_t {aka struct}’が必要ですが、引数のタイプは ‘dollar_t {aka struct}’です void calc(cent_t amount); // {
そしてここにgcc 3.4での結果
$ gcc -O3 -Wall Edit1.c Edit1.c:In function 'main': Edit1.c:17:error:incompatible type for argument 1 of 'calc '