什么时候应该只使用“int”而不是更多符号或特定于大小的types?

我有一个用C 语言实现的编程语言的虚拟机 。它支持在32位和64位体系结构以及C和C ++下编译。

我试图尽可能多的启用警告,使其编译干净。 当我打开CLANG_WARN_IMPLICIT_SIGN_CONVERSION ,我得到了CLANG_WARN_IMPLICIT_SIGN_CONVERSION的新警告。

我想有一个很好的策略,何时使用int与明确无符号types,和/或明确大小的。 到目前为止,我很难决定这个策略应该是什么。

把它们混合在一起(大部分使用int来表示局部variables和参数,而对结构中的字段使用较窄的types),这确实是真的,会导致大量的隐式转换问题。

我喜欢使用结构字段更具体的大小types,因为我喜欢明确控制堆中的对象的内存使用的想法。 而且,对于散列表,我依靠散列时的无符号溢出,所以如果散列表的大小存储为uint32_t ,那就好了。

但是,如果我尝试在所有地方使用更具体的types,我发现自己到处都是曲折的迷宫。

其他C项目做什么?

只要在任何地方使用int都可能看起来很诱人,因为它最大限度地减less了投射的需要,但是您应该注意到几个潜在的缺陷:

  • 一个int可能比你想象的要短。 尽pipe在大多数桌面平台上, int通常是32位, 但C标准只能保证16位的最小长度 。 你的代码是否需要大于2 16 -1 = 32,767的数字,即使是临时值? 如果是这样,不要使用int 。 (您可能需要使用longlong至less为32位)

  • 即使是long也许并不总是足够长的。 特别是,不能保证一个数组的长度(或者一个string,它是一个char数组) long适合。 请使用size_t (或ptrdiff_t ,如果您需要签名的差异)。

    特别是, size_t被定义为足够大以容纳任何有效的数组索引 ,而int或甚至long可能不是。 因此,例如,当遍历一个数组时,你的循环计数器(和它的初始值/最终值)一般应该是size_t ,至less除非你确定数组足够短,以便更小的types工作。 (但是在向后迭代时要小心: size_t是无符号的,所以for(size_t i = n-1; i >= 0; i--)是一个无限循环!使用i != SIZE_MAXi != (size_t) -1应该工作,虽然;或使用do / while循环,但要小心的情况下n == 0 !)

  • 一个int被签名。 特别是,这意味着int overflow是未定义的行为。 如果有任何风险,你的价值可能合法溢出,不要使用int ; 使用一个unsigned int (或一个unsigned long unsigned int ,或者uint NN _t )来代替。

  • 有时候,你只需要一个固定的位长。 如果您正在与ABI进行接口连接,或者需要读取/写入文件格式(需要使用特定长度的整数),则需要使用这个长度。 (当然,就是这样的情况,你也可能需要担心像字节序这样的东西,所以有时候不得不求助于手动将字节逐个字节地打包)。

总而言之,也有理由避免一直使用固定长度的types:不仅int32_t总是打字很笨拙,而且迫使编译器总是使用32位整数并不总是最佳的,特别是在平台上本地int大小可能是,比如64位。 你可以使用C99 int_fast32_t ,但是input更加尴尬。


因此,以下是我个人对最大安全性和便携性的build议:

  • 定义您自己的整数types,以便在常用的头文件中随意使用 ,如下所示:

     #include <limits.h> typedef int i16; typedef unsigned int u16; #if UINT_MAX >= 4294967295U typedef int i32; typedef unsigned int u32; #else typedef long i32; typedef unsigned long i32; #endif 

    只要尺寸足够大,就可以将这些types用于任何types的确切尺寸无关紧要的事物。 我所build议的types名称既简短又自我logging,所以它们在需要的地方可以很容易地使用,并且可以最大限度地减less因使用过于狭窄的types而导致的错误风险。

    方便的是,上面定义的u32u16types保证至less与unsigned int一样宽,因此可以安全地使用,而不必担心它们被提升为int并导致未定义的溢出行为。

  • 对所有数组大小和索引使用size_t ,但在它和其他整数types之间进行转换时要小心。 或者,如果您不想input如此多的下划线,请为其键入一个更方便的别名。

  • 对于假设在特定位数上溢出的计算,可以使用uint NN _t ,或者只使用上面定义的u16 / u32和使用&明确的位掩码。 如果你select使用uint NN _t ,一定要保护自己免于意外的提升到int ; 一个办法是用一个像这样的macros:

     #define u(x) (0U + (x)) 

    这应该让你安全地写,例如:

     uint32_t a = foo(), b = bar(); uint32_t c = u(a) * u(b); /* this is always unsigned multiply */ 
  • 对于需要特定整数长度的外部ABI,再次定义一个特定的types,例如:

     typedef int32_t fooint32; /* foo ABI needs 32-bit ints */ 

    再次,这个types的名字是自我logging,关于它的大小和目的。

    如果ABI可能实际上需要16或64位整数,取决于平台和/或编译时间选项,您可以更改types定义以匹配(并将types重命名为简单) – 但是你真的需要小心,只要你从这种types投入任何东西,因为它可能会意外溢出。

  • 如果你的代码有自己的结构或文件格式需要特定的比特长度,可以考虑定义这些types的自定义types,就好像它是一个外部的ABI一样。 或者你可以使用uint NN _t来代替,但是这样你会失去一点自我文档。

  • 对于所有这些types,不要忘记也要定义相应的_MIN_MAX常量,以便进行边界检查。 这可能听起来像很多工作,但是在单个头文件中实际上只是几行。

最后,记住要小心整数math,特别是溢出。 例如,请记住,两个n位有符号整数的差别可能不适合n位整数。 (如果你知道它是非负的,它将适合n无符号整数;但是记住你需要将input转换为无符号types, 然后才能避免未定义的行为!)同样,要find两个整数(例如二进制search)不使用avg = (lo + hi) / 2 ,而是使用avg = lo + (hi + 0U - lo) / 2 ; 如果总和溢出,前者会中断。

你看起来知道你在做什么,根据链接的源代码,我看了一下。

你自己说过 – 使用“特定”types可以让你有更多的表演。 这不是一条最佳的路线。 尽可能多地使用int ,对于那些不需要更专门的types的东西。

int的美妙之处在于,它被抽象为你所说的types。 在所有不需要将构造暴露给不知道int的系统的情况下,它是最优的。 这是你自己的抽象程序平台的工具。 它也可能让你的速度,尺寸和alignment优势,取决于。

在所有其他情况下,例如,如果你想故意靠近机器规格, int可以,有时应该放弃。 典型的情况包括数据通过networking传输的networking协议,以及互操作性设施 – C语言与其他语言之间的桥梁,访问C结构的内核汇编例程。 但是不要忘记,有时候甚至在这种情况下你也会想要使用int ,因为它遵循平台自己的“本地”或首选的字大小,你可能想要依靠那个属性。

使用像uint32_t这样的平台types,内核可能希望在它们的数据结构中使用这些types(尽pipe它可能不需要),因为后者无法访问int作为types。

综上所述,在任何可能需要的情况下,尽可能多地使用int ,并且从更抽象的types转换到“机器”types(字节/八位字节,单词等)。

至于size_t和其他“使用暗示”types – 只要语法遵循这种types固有的语义 – 比方说使用size_t以及所有types的大小值 – 我就不会竞争。 但是,我不会宽泛地把它应用到任何东西上,因为它保证是最大的types(不pipe它是否真实)。 这是一个你不想在后面踩的水下石头。 在可能的程度上,代码必须是不言自明的,我想说 – 有一个没有自然期望的大小,会有一个很好的理由。 尺寸使用size_t 。 使用offset_t作为偏移量。 用[u]intN_t表示八位字节,单词等等。 等等。

这是关于将特定Ctypes固有的语义应用于您的源代码以及关于正在运行的程序的含义。

此外,正如其他人所说的,不要回避typedef ,因为它使您有效地定义自己的types,我个人珍视的抽象工具。 一个好的程序源代码可能甚至不会公开一个int ,但依赖于多个用户定义types的int 。 我不打算在这里覆盖typedef ,其他答案希望会。

保留用于访问数组成员的大量数据,或者以size_t控制缓冲区。

有关使用size_t的项目示例,请参阅GNU的dd.c,第155行 。

这是我做的一些事情。 不知道他们是为了每个人,但他们为我工作。

  1. 切勿直接使用intunsigned int 。 总是似乎有一个更适当的命名types的工作。
  2. 如果一个variables需要是一个特定的宽度(例如,一个硬件寄存器或协议匹配)使用宽度特定types(例如uint32_t )。
  3. 对于数组迭代器,我想要访问数组元素0到n,这也应该是无符号的(没有理由访问任何小于0的索引),我使用其中一个快速types(例如uint_fast16_t ),select基于访问所有数组元素所需的最小大小。 例如,如果我有一个for循环,最多可以遍历24个元素,那么我将使用uint_fast8_t并让编译器(或者stdint.h,取决于我们想要得到的迂腐程度)决定哪个是该操作的最快types。
  4. 总是使用无符号variables,除非有特定的原因需要签名。
  5. 如果您的无符号variables和签名variables需要一起播放,请使用明确的转换,并注意其后果。 (幸运的是,如果您避免使用带符号的variables(绝对必要的地方除外),这将最小化。)

如果你不同意这些或推荐的替代品,请在评论中告诉我们! 这就是软件开发人员的生活……我们不断学习,或变得无关紧要。

总是。

除非您有特定的原因使用更具体的types,包括您在16位平台上,并且需要大于32767的整数,否则您需要确保正确的字节顺序和标志,以便通过networking或文件进行数据交换除非你受到资源限制,否则考虑以“纯文本”传输数据,如果你愿意的话,也就是ASCII或UTF8)。

我的经验表明,“使用”int“”是一个很好的格言,可以使每一次快速工作,易于维护和正确的代码生效。 但是你的具体情况可能会有所不同,所以请采取一些值得推荐的审议。