C 中的类型安全

Type-safety in C

有没有办法让 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 结构来包装 unsigneds 并达到预期的结果,我只是想知道是否有更优雅的方法来做到这一点。
能再多一点吗?

别名在 C 语言中具有非常具体的狭义含义,这不是您所想的。你可能想说"typedefing"。

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

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

问题是 C 不会将您的两个 typedef 视为不同的类型,因为它们都是 unsigned.

类型

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

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

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

一个更 conventional/traditional 的技巧是使用通用结构包装器,其中您使用 "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

http://www.gimpel.com/html/strong.htm

编辑:如果您的编译器不支持 _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: 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 3.4 的结果

$ gcc -O3  -Wall Edit1.c
Edit1.c: In function 'main':
Edit1.c:17: error: incompatible type for argument 1 of 'calc'