C中的类型安全性

so.*_*red 36 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 argc, char* argv[]) {
    dollar_t amount = 50;
    calc(DOLLAR_2_CENT(amount));  // ok
    calc(amount);                 // raise warning
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

有没有办法让上面的代码至少提高gcc的警告?
我知道我可以使用C-structs来包装unsigneds并获得理想的结果,我只是想知道是否有更优雅的方法来实现它.
可以多一点吗?

Lun*_*din 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;
}
Run Code Online (Sandbox Code Playgroud)

更常规/传统的技巧是使用通用的struct wrapper,在其中使用"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)
  ...
}
Run Code Online (Sandbox Code Playgroud)

优点是它适用于任何C版本.缺点是代码和内存开销,并且它只允许运行时检查.

  • @ so.very.tired你怎么期望对`struct cents {unsigned count;存在内存和代码惩罚?}`?在运行时字段中存储类型的双元素结构可能会受到惩罚; 如果只包含一个无符号值的结构有任何结构,我会感到惊讶. (4认同)

n. *_* m. 10

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

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

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


小智 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
Run Code Online (Sandbox Code Playgroud)

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


Pat*_*ter 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;
}
Run Code Online (Sandbox Code Playgroud)

你甚至比警告更强大.这是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'