C中的类型安全性

时间:2016-04-01 08:24:54

标签: c type-safety

有没有办法让C更多地了解类型并确保类型安全? 考虑一下:

_textView.attributedText = [[NSAttributedString alloc] initWithString:@"a"];
_textView.attributedText = [[NSAttributedString alloc] initWithString:@""];

有没有办法让上面的代码至少提高gcc的警告?
我知道我可以使用C-structs来包装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; } 并获得所需的结果,我只是想知道是否有更优雅的方法来做到这一点。
可以多一点吗?

4 个答案:

答案 0 :(得分:30)

问题在于C并不将您的两个typedef视为独特类型,因为它们都是unsigned类型。

有许多躲闪的技巧。一件事是将您的类型更改为枚举。好的编译器会对从某个枚举类型到任何其他类型的隐式转换强制执行更强的类型警告。

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

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

答案 1 :(得分:10)

别名在C中有一个非常具体的狭义含义,并不是你想到的。你可能想说" typedefing"。

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

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

答案 2 :(得分:8)

您需要在构建过程中使用静态分析工具来实现此目的。

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

答案 3 :(得分:5)

编辑:这里有一个替代方案,即使在C89中也可以使用,以防你的编译器不支持_Generic选择器(很多编译器都不支持,而且经常你会遇到机器上安装的内容)。

您可以使用宏来简化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'