在C系列语言中使用显式大小的类型的缺点或权衡

时间:2012-04-19 04:01:53

标签: c++ objective-c c performance portability

我正在开发几个需要在多个桌面和移动平台上移植的C和C ++项目。我知道在读取和写入磁盘数据时使用显式大小的类型u32_t i64_等很重要。

使用所有整数类型的显式大小类型以确保一致执行是否是个好主意?我听说显式大小的类型会产生性能影响,因为处理器针对其预期的int类型进行了优化等。我还读到一个好的策略是在内部为类数据成员使用显式大小的类型,但不在接口中使用。

在数据成员和接口上是否有关于显式大小类型的最佳实践? (我假设在这些情况下C或C ++之间不会有巨大的差异,但请告诉我是否存在)

4 个答案:

答案 0 :(得分:10)

基本的“int”类型的好处在于它几乎总是最快的整数类型,适用于您当前正在编译的任何平台。

另一方面,使用int32_t(而不仅仅是int)的优点是你的代码可以指望int32_t总是32位宽,无论它编译在什么平台上,这意味着你可以安全地比int更多地假设值的行为。对于固定大小的类型,如果您的代码在新平台Y上完全编译,那么它的行为可能与在旧平台X上完全相同。

int32_t的(理论上)缺点是新平台X可能不支持32位整数(在这种情况下,你的代码根本不会在该平台上编译),或者它可能支持它们但处理它们的速度比它慢会处理普通的in。

上面的例子有点人为,因为几乎所有现代硬件都全速处理32位整数,但是存在(并且确实存在)操纵int64_ts比操作int更慢的平台,因为(a)CPU具有32位寄存器,因此必须将每个操作分成多个步骤,当然(b)64位整数占用的内存是32位整数的两倍,这会给缓存带来额外的压力。

但是:请记住,对于人们编写的99%的软件,这个问题不会对性能产生任何可观察到的影响,因为99%的软件现在没有CPU限制,并且即使对于代码来说,整数宽度也不太可能是性能问题。那么真正归结为,你希望你的整数数学表现如何

  • 如果您希望编译器保证您的整数值总是占用32位RAM,并且总是在2 ^ 31处“环绕”(或者对于无符号,则为2 ^ 32) ),无论你正在编译什么平台,都要使用int32_t(等)。

  • 如果你真的不关心包装行为(因为你知道你的整数永远不会包装,由于它们存储的数据的性质),你想让代码更便携对于奇数/异常编译目标,至少理论上更快(尽管可能不是现实生活中),那么你可以坚持使用普通的旧/短/ int / long。

我个人默认使用固定大小的类型(int32_t等),除非有非常明确的理由不这样做,因为我希望最小化跨平台的变体行为的数量。例如,此代码:

for (uint32_t i=0; i<4000000000; i++) foo();

...将始终调用foo()恰好4000000000次,而此代码:

for (unsigned int i=0; i<4000000000; i++) foo();

可能调用foo()4000000000次,或可能进入无限循环,具体取决于(sizeof(int)&gt; = 4)。当然可以手动验证第二个片段在任何给定平台上那样做,但考虑到两种样式之间可能为零的性能差异,我更喜欢第一种方法,因为预测其行为是明智之举。我认为char / short / int / long方法在C的早期阶段更有用,当时计算机体系结构更加多样化,并且CPU足够慢以至于实现完全本机性能比安全编码更重要。

答案 1 :(得分:6)

使用inttypes.hstdint.h。它是ANSI-C,因此它将支持任何旨在符合ANSI标准的工具链。

此外,它仍然可以为您节省重新设计轮子的工作。

你唯一必须做的是

#include <inttypes.h>

uint32_t integer_32bits_nosign;
  • 关于便携性的另一个问题: 因为数据宽度数据结束非常重要。 您必须使用标准宏检查目标字节序:

    struct {
    #if defined( __BIG_ENDIAN__ ) || defined( _BIG_ENDIAN )
        // Data disposition for Big Endian   
    #else
        // Data disposition for Little Endian   
    #endif
    };
    

如果使用位字段,则特别敏感。


编辑:

当然,如果您计划在仅使用C ++的代码上使用<csdtint>,则可以使用{{1}}。

答案 2 :(得分:1)

具有固定大小类型的相当令人讨厌的“陷阱”是,虽然它们给人的印象是代码不依赖于“int”的大小,但这实际上是一种错觉。一段代码如:

uint32_t mul(uint16_t a, uint16_t b)
{ return a*b; }
对于所有平台上的“a”和“b”的所有值,

将具有已定义的含义 其中“int”是40位或更大,并且还将定义所有值的含义 在“int”为16位的所有平台上的“a”和“b”,尽管是含义 当算术产品是65535时会有所不同 C89标准注意到虽然没有要求这样做,但大部分都是如此 那个时代的实现定义了它们的整数数学行为 签署的操作 - 除了一些特殊的例外 - 行为相同 即使结果在INT_MAX + 1之间,也可以使用未签名的对应 和UINT_MAX - 因此在那些编译器上所有“a”值的行为 并且“b”将匹配具有较大“int”类型的机器上的行为。它有 然而,对于32位编译器来说,生成代码会变得时髦 由于标准不禁止它们,因此打破值大于INT_MAX 这样做。

答案 3 :(得分:0)

stdint.h中有fast_type大小的整数,编译器将在平台中选择所需大小的最快整数,例如(从stdint.h中提取)

typedef signed char     int_fast8_t;
#if __WORDSIZE == 64
typedef long int        int_fast16_t;
typedef long int        int_fast32_t;
typedef long int        int_fast64_t;
#else
typedef int         int_fast16_t;
typedef int         int_fast32_t;
__extension__
typedef long long int       int_fast64_t;
#endif