【问题标题】:Type-safety in CC 中的类型安全
【发布时间】:2016-04-01 08:24:54
【问题描述】:

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

【问题讨论】:

  • 使cent_tdollar_t 成为一个结构,只有一个成员?
  • C 并不是真正的类型安全语言。 模拟类型安全的最简单方法是使用结构。
  • 在使用前正确声明所有函数(函数参数的完整列表等)。传递结构而不是基本类型(或基本类型的 typedef)。比宏更喜欢内联函数。在编译器上调高警告级别。
  • 我相信splint 可以检查这一点。可能还有其他编译器或静态分析工具也可以。
  • 声明钱使用无符号类型不是一个好主意。当你超支时,它可能会给你一种虚假的财富感。

标签: c type-safety


【解决方案1】:

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

【讨论】:

  • FOO_CENTFOO_DOLLAR 只是虚拟值吗?
  • 结构是不可能的(就像你说的,有内存和代码的惩罚,我负担不起......)
  • @so.very.tired 是的,这只是无稽之谈。您可以使用枚举来存储任何整数值,但枚举的实际大小取决于编译器。
  • enum hack 可能需要考虑一些可移植性问题。如果您尝试将int 分配给enum 类型,某些编译器会抛出警告,例如dollar_t amount = 50;
  • @so.very.tired 您对struct cents { unsigned count; } 的内存和代码损失究竟有多少期望?将类型存储在运行时字段中的二元素结构可能会受到惩罚;如果只包含一个无符号值的结构有任何值,我会感到惊讶。
【解决方案2】:

别名在 C 中具有非常具体的狭义含义,这不是您的想法。您可能想说“typedefing”。

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 好!我会试一试。谢谢。
    • 有免费的替代品吗? :(
    • 我接受这个答案,因为经过进一步研究,这似乎正是我所寻找的:一个静态分析工具,每当我犯类型错误时都会警告我/引发错误。跨度>
    【解决方案4】:

    编辑:这里有一个替代方案,即使在 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:在函数“main”中: Edit1.c:17:10: error: 'calc' 的参数 1 的类型不兼容 计算(金额); // 发出警告 ^ Edit1.c:10:6: note: 预期为“cent_t {aka struct }”,但参数类型为“dollar_t {aka struct }” void calc(cent_t amount);// {

    这里是 gcc 3.4 的结果

    $ gcc -O3 -Wall Edit1.c Edit1.c:在函数'main'中: Edit1.c:17:错误:'calc' 的参数 1 的类型不兼容

    【讨论】:

    • C89 没有指定的初始化程序,您的示例的第三行无法编译。
    • 你用-std=c89试试这个
    • 是的。它在没有警告的情况下编译。也许我应该尝试-pedantic
    • (TO_NT (cent_t, 100 * FROM_NT (dollar))) 真的比( (cent_t) { 100 * (dollar).v } ) 更可取吗?
    • 并非如此,但这就是使用 struct 容器在 C 中进行强类型检查所必须做的事情。它在实践中有用吗?我认为不是,但这就是 OP 想要做的。所以我提出了一个替代接受的答案,仅此而已。与往常一样买主告诫
    猜你喜欢
    • 2012-03-24
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 2021-01-27
    • 1970-01-01
    • 1970-01-01
    • 2017-07-31
    • 1970-01-01
    相关资源
    最近更新 更多