“长期”禁令有意义吗?


109

在当今的跨平台C ++(或C)世界中,我们拥有

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

今天的意思是,对于任何“公共”(有符号)整数,int在编写C ++应用程序代码时就足够了,并且仍可以用作默认整数类型。出于当前实际目的,它在各个平台上的大小也将保持一致。

如果用例至少需要64位,我们今天就可以使用long long,尽管可能使用一种指定位的类型,或者该__int64类型可能更有意义。

这留long在中间,我们正在考虑彻底禁止在long应用程序代码中使用

这是否有意义,或者是否有long必要在必须跨平台运行的现代C ++(或C)代码中使用?(平台是台式机,移动设备,但不是微控制器,DSP等)。


可能有趣的背景链接:


14
您将如何处理对使用时间长的库的调用?
安赫尔

14
long是保证32位的唯一方法。int可以是16位,因此对于某些应用程序来说还不够。是的,int现代编译器有时为16位。是的,人们确实在微控制器上编写软件。我认为随着iPhone和Android设备的兴起,更多的人在微控制器上编写的软件比PC上的用户更多,更不用说Arduino等的兴起了。– slebetman
2016年

53
为什么不禁止使用char,short,int,long和long long并使用[u] intXX_t类型?
immibis

7
@slebetman我更深入地研究了一点,尽管§3.9.1.3中隐藏了C ++标准,但该要求似乎仍然存在:“有符号和无符号整数类型应满足C标准第5.2节中给出的约束。 4.2.1。” 并且在C标准§5.2.4.2.1中,它声明的最小范围与您编写的完全相同。你是绝对正确的。:)显然拥有C ++标准的副本还不够,还需要找到C标准的副本。
汤米·安德森

11
您缺少DOSBox / Turbo C ++世界,那里int仍然只有16位。我不想这么说,但是如果您要写有关“当今的跨平台世界”的文章,您将不能忽略整个印度次大陆。
Lightness Races in Orbit

Answers:


17

long今天要使用的唯一原因是在调用或实现一个使用它的外部接口时。

正如您在帖子中所说的,short和int在当今所有主要台式机/服务器/移动平台上都具有相当稳定的特征,我认为在可预见的将来没有任何改变的理由。因此,我几乎没有理由避免使用它们。

long另一方面是一团糟。我知道在所有32位系统上,它都具有以下特征。

  1. 它的大小恰好是32位。
  2. 它的大小与内存地址相同。
  3. 它与可以保存在普通寄存器中并使用一条指令进行处理的最大数据单元大小相同。

基于这些特征中的一个或多个,编写了大量代码。但是,随着迁移到64位,不可能保留所有这些。类似于Unix的平台使用以特征1为代价保留特征2和3的LP64。Win64用以特征2和3为代价保留特征1的LLP64。而且IMO几乎没有理由使用long

如果您想要一个大小恰好为32位的类型,则应使用int32_t

如果您想要一个与指针大小相同的类型,则应该使用intptr_t(或更好uintptr_t)。

如果您希望一种类型可以在单个寄存器/指令中处理的最大项目,那么不幸的是,我认为该标准不提供该类型。size_t应该适用于大多数常见平台,但不适用于x32


聚苯乙烯

我不会理会“快速”或“最小”类型。“最小”类型仅在您关心可移植性以真正模糊其中的地方时才重要CHAR_BIT != 8。实际上,“快速”类型的大小似乎很随意。Linux似乎使它们的大小至少与指针相同,这在具有快速32位支持的64位平台(如x86-64和arm64)上是愚蠢的。IIRC iOS使它们尽可能小。我不确定其他系统还能做什么。


PPS

使用unsigned long(但不是简单long)的原因之一是因为它具有模数特性。不幸的是,由于C搞砸了升级规则,未签名类型小于int没有取模行为的无符号类型。

今天,在所有主要平台上uint32_t,其大小均与int相同或更大,因此具有模数行为。但是,从历史上看,从理论上讲,将来的平台可能int是64位的,因此uint32_t没有模数特性。

我个人会说,最好在方程式开头使用“ 1u *”或“ 0u +”来加入模数行为的习惯,因为这适用于任何大小的无符号类型。


1
如果所有“指定大小”类型都可以指定与内置类型不同的语义,则它们将更加有用。例如,具有一个将使用mod-65536算术而不管“ int”大小如何的类型,以及一个能够容纳数字0到65535但可以任意地且不一定一致地能够使用的类型将是有用的。持有的数字大于该数字。在大多数机器上哪种大小类型最快将取决于上下文,因此能够让编译器任意选择对于速度而言是最佳的。
超级猫

204

正如您在问题中提到的那样,现代软件就是有关Internet上的平台和系统之间进行互操作的。C和C ++标准给出了整数类型大小的范围,而不是特定的大小范围(与Java和C#之类的语言相反)。

为确保在不同平台上编译的软件以相同的方式使用相同的数据,确保其他软件可以使用相同的大小与您的软件进行交互,您应使用固定大小的整数。

Enter <cstdint>,它提供了确切的信息,并且是所有编译器和标准库平台都必须提供的标准标头。注意:从C ++ 11开始,才需要此标头,但是无论如何,许多较早的库实现都提供了此标头。

想要64位无符号整数吗?使用uint64_t。带符号的32位整数?使用int32_t。尽管标头中的类型是可选的,但现代平台应支持该标头中定义的所有类型。

有时,例如在用于与其他系统通信的数据结构中,需要特定的位宽。有时不是。对于不太严格的情况,请<cstdint>提供最小宽度的类型。

至少变体:int_leastXX_t将最小XX位的整类型。它将使用提供XX位的最小类型,但允许该类型大于指定的位数。实际上,这些类型通常与上述给出确切位数的类型相同。

也有快速变体:int_fastXX_t至少为XX位,但应使用在特定平台上可快速执行的类型。在此上下文中,“快速”的定义未指定。但是,实际上,这通常意味着小于CPU寄存器大小的类型可能会别名为CPU寄存器大小的类型。例如,Visual C ++ 2015的标头指定int_fast16_t为32位整数,因为x86上的32位算术总体上比16位算术更快。

这一点非常重要,因为无论平台如何,都应该能够使用可以保存程序执行的计算结果的类型。如果一个程序在一个平台上产生正确的结果,但由于整数溢出的差异而在另一个平台上产生不正确的结果,那就不好了。通过使用标准整数类型,您可以保证在不同平台上使用的整数大小的结果相同(当然,除了整数宽度以外,平台之间还可能存在其他差异)。

因此,是的,long应禁止使用现代C ++代码。所以应该intshortlong long


20
我希望我还有其他五个帐户可以对此进行更多投票。
史蒂芬·本纳普

4
+1,我处理了一些奇怪的内存错误,这些错误仅在结构的大小取决于您所编译的计算机时才会发生。
约书亚·

9
@Wildcard是C头,它也是C ++的一部分:请参见其上的“ c”前缀。在C ++编译单元中d时,还有一些方法可以将typedef放在std名称空间#include中,但是我链接的文档没有提及它,Visual Studio似乎不在乎如何访问它们。

11
禁止int可能...过多?(如果代码需要在所有晦涩的(和不那么晦涩)平台便携至极禁止它“的应用程序代码,”我认为它可能不符合我们的开发者坐得非常好。
马丁巴

5
@Snowman #include <cstdint>需要把类型std::和(遗憾)可选允许也把他们在全局命名空间。#include <stdint.h>恰恰相反。其他任何一对C头文件也是如此。请参阅:stackoverflow.com/a/13643019/2757035我希望标准仅要求每个对象影响其各自所需的名称空间-而不是看似屈服于某些实现所建立的不良约定-但哦,好了,我们在这里。
underscore_d

38

不,禁止内置整数类型将是荒谬的。但是,也不应滥用它们。

如果您需要一个正好为 N位宽的整数,请使用(或者如果您需要一个版本)。认为是32位整数和64位整数是错误的。在您当前的平台上可能恰好是这样,但这依赖于实现定义的行为。std::intN_tstd::uintN_tunsignedintlong long

使用固定宽度整数类型对于与其他技术进行互操作也很有用。例如,如果应用程序的某些部分是用Java编写的,而其他部分是用C ++编写的,则可能需要匹配整数类型,以便获得一致的结果。(仍然要注意,Java中的溢出具有明确定义的语义,而signedC ++中的溢出是未定义的行为,因此一致性是一个很高的目标。)在不同计算主机之间交换数据时,它们也将具有无价的价值。

如果您不需要N位,而只是一个足够宽的类型,请考虑使用(针对空间进行了优化)或(针对速度进行了优化)。同样,两个家庭也有对应的家庭。std::int_leastN_tstd::int_fastN_tunsigned

那么,什么时候使用内置类型?好吧,由于该标准并未精确指定其宽度,因此,当您不在乎实际位宽在乎其他特征时,请使用它们。

A char是硬件可寻址的最小整数。该语言实际上强迫您使用它来别名任意内存。它也是表示(窄)字符串的唯一可行类型。

An int通常是机器可以处理的最快类型。它足够宽,可以用一条指令加载和存储(而不必屏蔽或移位位),也可以窄到可以用(最有效的)硬件指令进行操作。因此,int在不考虑溢出的情况下,它是传递数据和进行算术运算的理想选择。例如,枚举的默认基础类型为int。不要仅仅因为可以就将其更改为32位整数。另外,如果您的值只能是–1、0和1,则int是一个完美的选择,除非您要存储大量的数组,在这种情况下,您可能希望使用更紧凑的数据类型,但必须付出更高的代价才能访问单个元素。更有效的缓存可能会为您带来回报。还根据定义了许多操作系统功能int。来回转换他们的论点和结果将是愚蠢的。所有可能要做的就是引入溢出错误。

long通常是可以用单机器指令处理的最宽的类型。这unsigned long对于处理原始数据和各种位处理东西特别有吸引力。例如,我希望能unsigned long在位向量的实现中看到它。如果代码写得很仔细,则类型的实际宽度没有多大关系(因为代码会自动适应)。在本机字为32位的平台上,使位向量的后备数组为unsigned最希望使用32位整数,因为使用必须通过昂贵的指令加载的64位类型只是愚蠢的,以使其无论如何都再次移位并屏蔽掉不需要的位。另一方面,如果平台的本机字长为64位,则我希望使用该类型的数组,因为这意味着“查找第一组”之类的操作可能会快两倍。因此,在的“问题” long是你所描述的,它的大小因平台不同的数据类型,实际上是一个功能,可以很好地加以使用。仅当您将内置类型视为具有一定位宽的类型(它们根本不是)时,这才成为问题。

charintlong与上述非常有用的类型。shortlong long没有那么有用,因为它们的语义不太清楚。


4
OP特别指出了longWindows和Unix之间的大小差异。我可能会误会,但是您对long比较“特征”而不是“问题” 的大小差异的描述对我来说比较32和64位数据模型有意义,但对于这种特殊的比较却不可行。在特定情况下,这个问题被问到,这真的是一项功能吗?还是在其他情况下(通常)具有此功能,并且在这种情况下无害?
丹·盖茨

3
@ 5gon12eder:问题是创建uint32_t之类的类型是为了使代码的行为与“ int”的大小无关,但是缺少一种含义是“像uint32_t一样的类型”可用于32位位系统”使得编写行为与“ int”的大小无关的代码要比编写几乎正确的代码困难得多。
超级猫

3
是的,我知道...这就是诅咒的来源。最初的作者只是选择了抗拒租约之路,因为当他们编写代码时,距离32位OS已有十多年了。
史蒂芬·本纳普

8
@ 5gon12eder可悲的是,超级猫是正确的。所有确切的宽度类型的“只是类型定义”和整数提升规则拿不出他们的通知,这意味着算术uint32_t值会进行签约int-width算术一个平台,让上intuint32_t。(对于当今的ABI,这在绝大多数情况下是一个问题uint16_t。)
zwol

9
首先,感谢您的详细回答。但是:亲爱的。您的长段文字:“ long通常是可以用单机器指令处理的最宽的类型。...”-这是完全错误的。查看Windows数据模型。恕我直言,下面的整个示例都无法理解,因为在x64 Windows上,long仍然是3​​2位。
马丁·巴

6

另一个答案已经详细说明了cstdint类型及其中鲜为人知的变体。

我想补充一点:

使用特定于域的类型名称

也就是说,不申报您的参数和变量是uint32_t(当然不是long!),但是名字例如channel_id_typeroom_count_type等等。

关于图书馆

使用long或不使用它们的第三方库可能会很烦人,尤其是用作它们的引用或指针时。

最好的事情就是让包装。

通常,我的策略是制作一组将要使用的类似cast的函数。它们被重载为仅接受与相应类型完全匹配的那些类型,以及您需要的任何指针等变体。它们是特定于os / compiler / settings定义的。这样,您就可以删除警告,同时确保仅使用“正确的”转换。

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

特别是,由于不同的原始类型会产生32位,因此您对int32_t定义方式的选择可能与库调用不匹配(例如,在Windows上为int vs long)。

类转换函数记录了冲突,提供了与函数参数匹配的结果的编译时检查,并且仅当实际类型与所涉及的实际大小匹配时,才删除任何警告或错误。也就是说,如果我(在Windows上)传递int*a或a,long*并且它给出了编译时错误,它将被重载并定义。

因此,如果库已更新或有人更改了内容,则将channel_id_type继续进行验证。


为什么要下票(无评论)?
JDugugz

因为该网络上大多数
Ruslan
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.