在当今的跨平台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

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


有趣的背景链接:


是做什么的C ++标准说明int的大小,长型是什么?
为什么Win64团队选择LLP64模型?

64位编程模型:为什么选择LP64? (有些老化)

long是否保证至少为32位? (这解决了下面的评论讨论。答案。)


评论

您将如何处理对使用long的库的调用?

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

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

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

您缺少DOSBox / Turbo C ++世界,在这个世界中int仍然是16位。我不想这么说,但是如果您要写“当今的跨平台世界”,您就不能忽略整个印度次大陆。

#1 楼

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

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


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

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

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

如果要使用与指针大小相同,应该使用intptr_t(或更好的uintptr_t)。

如果您想要的类型是可以在单个寄存器/指令中处理的最大项,那么我不知道认为该标准提供了一个标准。 size_t在大多数常见平台上都应该正确,但在x32上则不正确。


P.S.

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


P.P.S

使用unsigned long(而不是简单的long)的一个原因是,它被保证具有模数特性。不幸的是,由于C搞砸了促销规则,小于int的无符号类型没有模数行为。

在当今的所有主要平台上,uint32_t的大小相同或大于int,因此具有模数行为。但是,从历史上看,从理论上讲,将来的平台中int是64位的,因此uint32_t不具有模数行为。

我个人认为最好进入强迫行为的习惯。在方程式的开头使用“ 1u *”或“ 0u +”进行模运算,因为它适用于任何大小的无符号类型。

评论


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

–超级猫
16年5月6日在12:56

#2 楼

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

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

输入<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位算术快。

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

因此,是的,应该禁止现代C ++代码使用longintshortlong long也应该如此。

评论


我希望我还有其他五个帐户可以对此进行更多投票。

–Gor机器人
16年5月5日在23:06

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

–约书亚·斯尼德(Joshua Snider)
16年5月6日,0:06

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

–user22815
16年5月6日在0:07



禁止int可能是...过多? (如果代码需要在所有晦涩(而不是那么晦涩)的平台上具有极高的可移植性,我会考虑。将其禁止用于“应用程序代码”可能与我们的开发人员不太合。

–马丁·巴(Martin Ba)
16年5月6日在8:27

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

– underscore_d
16年5月6日在9:04



#3 楼

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

如果您需要一个正好为N位宽的整数,请使用std::intN_t(如果需要std::uintN_t版本,请使用unsigned)。将int视为32位整数并将long long视为64位整数是错误的。在当前平台上,可能碰巧是这种情况,但这依赖于实现定义的行为。

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

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

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

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

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

long通常是可以用单机器指令处理的最宽的类型。这尤其使unsigned long在处理原始数据和各种位处理方面非常有吸引力。例如,我期望在位向量的实现中看到unsigned long。如果代码写得很仔细,则类型的实际宽度没有多大关系(因为代码会自动适应)。在本机字为32位的平台上,使位向量的支持数组为unsigned的数组是最理想的32位整数,因为使用必须通过昂贵的方式加载的64位类型会很愚蠢指令只能再次移位和掩盖不需要的位。另一方面,如果平台的本机字长为64位,则我希望使用该类型的数组,因为这意味着“查找第一组”之类的操作可能会快两倍。因此,您所描述的long数据类型的“问题”因平台而异,实际上是可以充分利用的功能。仅当您将内置类型视为具有一定宽度的类型时,这才成为问题,而它们根本不是。

charintlong是如上所述非常有用的类型。 shortlong long的用处不大,因为它们的语义不太清楚。

评论


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

–丹·盖茨(Dan Getz)
16年5月5日在22:34

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

–超级猫
16年5月5日在23:06

是的,我知道...这就是诅咒的来源。最初的作者只是选择了抵制租约的道路,因为当他们编写代码时,距离32位OS已有十多年了。

–Gor机器人
16年5月5日23:37

@ 5gon12eder可悲的是,超级猫是正确的。所有的精确宽度类型均为“ just typedefs”,并且整数提升规则不予注意,这意味着对uint32_t值的算术将在带int宽于uint32_t的平台上以带符号的int宽度算术进行。 (对于当今的ABI,绝大多数情况下这是uint16_t的问题。)

– zwol
16年5月5日在23:55

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

–马丁·巴(Martin Ba)
16年5月6日在8:24

#4 楼

另一个答案已经详细说明了cstdint类型及其中鲜为人知的变体。
我想补充一点:
使用特定于域的类型名称
,即,不要声明您的参数和变量为uint32_t(不一定是long!),但是诸如channel_id_typeroom_count_type等名称。
关于库
使用long的第三方库可能会很烦人,尤其是用作参考或指向这些指针的指针。
最好的方法是制作包装器。
通常,我的策略是制作一组将使用的类型转换函数。它们被重载为仅接受与相应类型完全匹配的那些类型,以及您需要的任何指针等变体。它们是针对os / compiler / settings定义的。这使您可以删除警告,并确保仅使用“正确的”转换。
channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

特别是,由于产生32位的不同原始类型,您对int32_t的定义方式选择可能与库不匹配调用(例如,在Windows上为int vs long)。
类似cast的函数记录冲突,在与函数参数匹配的结果上进行编译时检查,并在且仅当实际类型时删除任何警告或错误匹配所涉及的实际大小。也就是说,如果我(在Windows上)传递了int*long*并给出了编译时错误,则会超载并定义它。
因此,如果库已更新或有人更改了channel_id_type,则继续被验证。

评论


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

–JDługosz
16年5月7日在9:13

因为此网络上大多数不赞成投票的人都没有评论...

–俄罗斯
16年5月9日在15:09