C中的类型安全

C中的类型安全,c,type-safety,C,Type Safety,有没有办法让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 ar

有没有办法让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-structs来包装
无符号的
s并实现所需的结果,我只是想知道是否有更优雅的方法来实现它。

它能比这多一点吗?

在C语言中,别名有一个非常特殊的狭义含义,而这不是你想要的。你可能想说“typedefing”

答案是不,你不能。无论如何都不是以优雅的方式。您可以对每种数字类型使用一个结构,并使用一组单独的函数对每种类型进行算术运算。除了乘法运算,你运气不好。为了将英尺乘以磅,你需要第三种类型。您还需要平方英尺、立方英尺、负二次方幂的秒数以及无限多的其他类型的类型


如果这正是您所追求的,那么C语言就不是正确的语言。

问题在于C不会将您的两个typedef视为不同的类型,因为它们都是type
无符号的

有各种各样的技巧可以避免这种情况。一件事是将您的类型更改为枚举。好的编译器会在将某个枚举类型隐式转换为或从某个枚举类型隐式转换为任何其他类型时强制执行更强的类型警告

即使您没有一个好的编译器,使用枚举也可以做到:

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;
}

一个更传统的技巧是使用泛型结构包装器,其中使用“ticket”枚举来标记类型。例如:

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版本一起使用。缺点是代码和内存开销,它只允许运行时检查。

您需要在构建过程中使用静态分析工具来实现这一点

例如,如果在代码上运行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

编辑:这里有一个即使在C89中也可以使用的替代方案,以防您的编译器不支持
\u Generic
选择器(很多编译器都不支持,而且您经常被机器上安装的东西卡住)

您可以使用宏来简化
struct
wrappers的使用

#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: In function ‘main’: Edit1.c:17:10: error: incompatible type for argument 1 of ‘calc’ calc(amount); // raise warning ^ Edit1.c:10:6: note: expected ‘cent_t {aka struct }’ but argument is of type ‘dollar_t {aka struct }’ void calc(cent_t amount);// { $gcc-O3-墙版1.c Edit1.c:在函数“main”中: Edit1.c:17:10:错误:“calc”的参数1的类型不兼容 计算(金额);//提出警告 ^ Edit1.c:10:6:注意:应为'cent_{aka struct}',但参数的类型为'dollar_{aka struct}' 无效计算(分金额);//{ 这里是gcc 3.4的结果

$ gcc -O3 -Wall Edit1.c Edit1.c: In function 'main': Edit1.c:17: error: incompatible type for argument 1 of 'calc' $gcc-O3-墙版1.c Edit1.c:在函数“main”中: Edit1.c:17:错误:“calc”的参数1的类型不兼容
使
cent\u t
dollar\u t
成为一个只有一个成员的结构?C不是真正的类型安全语言。模拟类型安全的最简单方法是使用结构。在使用之前正确声明所有函数(完整的函数参数列表等)。传递结构而不是基本类型(或基本类型的typedef)。更喜欢内联函数而不是宏。在编译器上设置警告级别。我相信你可以检查这一点。可能还有其他编译器或静态分析工具也可以。声明使用未签名类型的钱不是一个好主意。当你超支时,它可能会给你一种虚假的财富感。
FOO分
FOO分吗_美元
仅仅是虚拟值?结构是不可能的(就像你说的,内存和代码会有损失,我负担不起…)@so.very.wear是的,这只是无意义的傻瓜。您可以使用枚举来存储任何整数值,尽管枚举的实际大小取决于编译器。可能必须考虑
enum
hack的一些可移植性问题。如果您尝试将
int
分配给
enum
,某些编译器会发出警告>键入,如
dollar\u t amount=50;
@so.very.retired对于
struct cents{unsigned count;}的内存和代码惩罚有多高
?在运行时字段中存储类型的两元素结构可能会有惩罚;如果只包含一个无符号值的结构有任何惩罚,我会感到惊讶。C89没有指定的初始值设定项,示例的第三行将无法编译。您是否使用
-std=C89
尝试过此操作?是的。它在没有警告的情况下编译。我是否应该我已经试过了
-pedantic
.Is
(分币,100*来自新币(美元))
真的比
((分币){100*(美元).v})更可取吗
?不太可能,但这就是使用
struct
容器在C中进行强类型检查所必须做的。它在实践中有用吗?我不认为有用,但这正是OP想要做的。所以我提出了一个可接受答案的替代方案,仅此而已。一如既往地警告emptor。很好!我会尝试一下。谢谢。有免费alt吗(我接受这个答案,因为经过进一步的研究,这似乎正是我想要的:一个静态分析工具,每当我犯类型错误时,它会警告我/提出错误。