在现代C语言中,可变宽度类型是否已由固定类型取代?


21

今天,在对Code Review的回顾中遇到了一个有趣的观点。@Veedrac在recommened 此答案该可变大小类型(例如intlong)具有固定尺寸类型等来代替uint64_tuint32_t。从该答案的评论中引用:

int和long的大小(以及它们可以容纳的值)取决于平台。另一方面,int32_t始终为32位长。使用int只是意味着您的代码在不同平台上的工作方式不同,这通常不是您想要的。

@supercat 在此处部分解释了标准不固定常见类型的背后原因。与当时通常用于系统编程的汇编相反,C被编写为可跨体系结构移植。

我认为最初的设计意图是,除int之外的每种类型都是可以处理各种大小数字的最小事物,而int是可以处理+/- 32767的最实用的“通用”大小。

对于我来说,我一直在使用int,并不真正担心其他选择。我一直认为这是性能最好的大多数类型,故事结束了。我认为固定宽度唯一有用的地方是在对数据进行编码以进行存储或通过网络传输时。我也很少见过其他人编写的代码中的固定宽度类型。

我是停留在70年代还是int在C99及以后的时代实际上有使用的理由?


1
一部分人只是在模仿别人。我相信大多数固定位类型的代码都是无意识的。没有理由不设置大小。我的代码主要在16位平台(MS-DOS和80年代的Xenix)上制作,现在可以在任何64位平台上编译和运行,并且可以利用新的字长和寻址功能进行编译。也就是说,序列化以导出/导入数据是使其保持可移植性的非常重要的体系结构设计。
卢西亚诺

Answers:


7

有一个普遍而危险的神话,像这样的类型可以uint32_t使程序员不必担心int。如果标准委员会定义一种使用独立于机器的语义声明整数的方法会有所帮助,但无符号类型之类的uint32_t语义过于宽松,以至于无法以干净,可移植的方式编写代码。此外,像这样的int32带符号类型具有语义,这些语义对于许多应用程序而言都是不必要地严格定义的,因此排除了原本会有用的优化。

考虑例如:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int不能容纳4294967295或可以容纳18446744065119617025的计算机上,将为n和的所有值定义第一个函数exponent,并且其行为将不受int; 大小的影响。此外,该标准将不要求它产生不同的行为对机器与任何尺寸int 的一些价值观nexponent,但是,将导致它调用的机器上未定义行为,其中4294967295可表示为一个int,但18446744065119617025不是。

第二个函数将产生不确定的行为对于一些值n,并exponent在一些机器上int不能容纳4611686014132420609,但将产生定义行为的所有值n,并exponent在所有机器上它可以(规格为int32_t暗示在一些机器上它是二进制补码包装行为小于int

从历史上看,即使标准没有说明编译器应如何处理int溢出问题upow,但编译器将始终产生相同的行为,好像int已经足够大而不会溢出。不幸的是,某些较新的编译器可能会通过消除标准未规定的行为来寻求“优化”程序。


3
碰巧想要手动实现的任何人pow,请记住此代码只是一个示例,并不适合exponent=0
Mark Hurd

1
我认为您应该使用前缀减量运算符而不是后缀,当前它在进行1次额外的乘法,例如exponent=1将导致n自身被乘一次,因为减量是在检查之后执行的,如果递增是在检查之前执行的(即--exponent),将不执行乘法运算,并且将返回n本身。
ALXGTV

2
@MarkHurd:该函数的名称很差,因为它实际计算的是N^(2^exponent),但是形式N^(2^exponent)的计算经常用于幂函数的计算,而mod-4294967296幂运算对于诸如计算两个字符串的串联的哈希值之类的事情很有用。哈希是已知的。
超级猫

1
@ALXGTV:该功能旨在说明计算出与功率相关的某些事物。它实际计算的是N ^(2 ^指数),这是有效计算N ^指数的一部分,即使N很小也可能会失败(重复乘以uint32_t31不会产生UB,但是有效计算31 ^ N的方法需要进行31 ^(2 ^ N)的计算,这将
超级猫

我认为这不是一个很好的论点。目的不是要为所有输入定义功能,无论是否明智。以便能够推断出大小和溢出。int32_t有时已经定义了溢出,有时没有定义,这似乎是最不重要的,相对于它让我首先考虑防止溢出的事实。而且,如果确实要定义溢出,则可能是想要将结果取某个固定值取模-因此,无论如何,您都在使用固定宽度类型。
Veedrac 2015年

4

对于与指针(并因此与可寻址内存的数量)紧密相关的值(例如缓冲区大小,数组索引和Windows')lParam,有意义的是使用具有与体系结构相关的大小的整数类型。因此,可变大小类型仍然有用。这就是为什么我们有类型定义size_tptrdiff_tintptr_t等他们是类型定义,因为没有内建C整数类型的需要是指针大小。

所以,真正的问题是是否charshortintlong,和long long仍然有用。

IME,C和C ++程序在int大多数情况下仍然很常见。而且在大多数情况下(例如,当您的数字在±32 767范围内,并且您没有严格的性能要求时),这很好。

但是,如果您需要使用17-32位范围内的数字(例如大城市的人口)怎么办?您可以使用int,但这将对平台依赖项进行硬编码。如果您要严格遵守该标准,则可以使用long,它保证至少为32位。

问题在于C标准没有为整数类型指定任何最大大小。有一些实现long是64位的,这使您的内存使用量增加了一倍。并且,如果这些long恰好是包含数百万个项目的数组的元素,那么您将疯狂地浪费内存。

因此,如果您希望程序既是跨平台的又是内存有效的,那么这里int也不long是合适的类型。输入int_least32_t

  • 您的I16L32编译器为您提供32位long,避免了截断问题int
  • 您的I32L64编译器为您提供32位int,避免了64位的内存浪费long
  • 您的I36L72编译器为您提供了36位 int

OTOH,假设你没有需要巨大的数字或巨大的阵列,但是你有一个需要速度。并且int可能会在所有平台够大,但它不一定是最快的一类:64位系统通常仍然有32位int。但是你可以使用int_fast16_t并获得了“最快”的类型,无论是intlonglong long

因此,存在来自的类型的实际用例<stdint.h>。标准的整数类型不意味着任何东西。特别是long,它可能是32位或64位,并且可能大也可能不足以容纳指针,具体取决于编译器编写者的想法。


uint_least32_t这样的类型存在的问题是,与相比,它们与其他类型的交互的指定能力甚至更弱uint32_t。恕我直言,标准应该定义类似uwrap32_t和的类型unum32_t,其语义是任何定义类型的编译器都uwrap32_t必须将其提升为无符号类型(在本质上与int32位被提升时相同),并且任何定义类型的编译器unum32_t必须确保基本算术促销始终将其转换为能够保留其值的带符号类型。
2015年

此外,该标准还可以定义其存储和别名与intN_t和兼容的类型uintN_t,并且其定义的行为与和一致,但是如果代码在其范围之外分配了值,则可以为编译器提供一定的自由度(允许使用与也许打算这样做,但没有不确定性,例如添加a 和an 是否会产生带符号的或恶意的结果。intN_tuintN_tuint_least32_tuint_least16_tint32_t
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.