还有理由在C++代码中使用`int`吗?

Ins*_*oop 181 c++

许多样式指南(例如Google建议使用int索引数组时作为默认整数使用).随着64位平台的兴起,大多数时候a int只有32位,这不是平台的自然宽度.因此,除了简单的说法,我认为没有理由保持这种选择.我们清楚地看到编译以下代码的位置:

double get(const double* p, int k) {
  return p[k];
}
Run Code Online (Sandbox Code Playgroud)

被编译成

movslq %esi, %rsi
vmovsd (%rdi,%rsi,8), %xmm0
ret
Run Code Online (Sandbox Code Playgroud)

其中第一条指令将32位整数提升为64位整数.

如果代码转换成

double get(const double* p, std::ptrdiff_t k) {
  return p[k];
}
Run Code Online (Sandbox Code Playgroud)

生成的程序集现在

vmovsd (%rdi,%rsi,8), %xmm0
ret
Run Code Online (Sandbox Code Playgroud)

这清楚地表明,CPU感觉更在家std::ptrdiff_t比使用int.许多C++用户已经迁移到了std::size_t,但我不想使用无符号整数,除非我真的需要模数2^n行为.

在大多数情况下,使用int不会损害性能,因为未定义的行为或有符号整数溢出允许编译器在内部将任何内容int提升为std::ptrdiff_t处理索引的in循环,但我们从上面清楚地看到编译器不会感到宾至如归int.此外,std::ptrdiff_t在64位平台上使用会使溢出不太可能发生,因为当我们看到越来越多的人被int溢出困住时,他们必须处理大于2^31 - 1现在变得非常普遍的整数.

从我所看到的,这使得唯一int脱颖而出似乎是文字,如事实5int,但我不认为它会引起任何问题,如果我们移动到std::ptrdiff_t一个默认的整数.

我即将成为std::ptrdiff_t我小公司编写的所有代码的事实上的标准整数.这有什么理由可能是一个糟糕的选择吗?

PS:我同意这个名字std::ptrdiff_t是丑陋的事实,这就是为什么我把它改成了il::int_t看起来好看的原因.

PS:据我所知,很多人会建议我使用std::size_t默认整数,我真的想明确表示我不想使用无符号整数作为我的默认整数.std::size_t在STL中使用默认整数是Bjarne Stroustrup和视频交互式面板中的标准委员会所承认的错误:在时间42:38和1:02:50 向我们提问.

PS:在性能方面,任何64位平台,我知道的,+,-*被编译为通过相同的方式intstd::ptrdiff_t.所以速度没有区别.如果除以编译时常量,速度是相同的.只有当你在64位平台上使用32位整数a/b时才知道这一点时才会划分,b这会给你带来轻微的性能优势.但是这种情况非常罕见,因为我认为不会选择离开std::ptrdiff_t.当我们处理矢量化代码时,这里有一个明显的区别,越小越好,但这是一个不同的故事,没有理由坚持int.在这些情况下,我建议使用固定大小的C++类型.

Rob*_*juk 105

有关C++核心指南的讨论使用了什么:

https://github.com/isocpp/CppCoreGuidelines/pull/1115

Herb Sutter写道,gsl::index将被添加(将来可能std::index),将被定义为ptrdiff_t.

hsutter于2017年12月26日发表评论•

(感谢许多WG21专家对本说明的评论和反馈.)

将以下typedef添加到GSL

namespace gsl { using index = ptrdiff_t; }

并推荐gsl::index所有容器索引/下标/大小.

合理

指南建议对下标/索引使用签名类型.见ES.100至ES.107.C++已经使用有符号整数作为数组下标.

我们希望能够教会人们在高警告级别编写简单,自然,无警告的"新清洁现代代码",并且不会让我们写出关于简单代码的"陷阱"脚注.

如果我们没有一个短字可采用类似index是有竞争力的intauto,人们仍然会使用intauto并得到他们的错误.例如,他们会在广泛使用的平台上编写for(int i=0; i<v.size(); ++i)或者 for(auto i=0; i<v.size(); ++i)有32位大小的错误,而for(auto i=v.size()-1; i>=0; ++i)这些错误根本不起作用.我认为我们不能for(ptrdiff_t i = ...直面教学,或者人们会接受它.

如果我们有一个饱和的算术类型,我们可能会使用它.否则,最好的选择是ptrdiff_t具有饱和算术无符号类型的几乎所有优点,除了这ptrdiff_t仍然使得普遍的循环样式for(ptrdiff_t i=0; i<v.size(); ++i) 在今天的STL容器上发出签名/未签名的不匹配i<v.size()(并且类似地 i!=v.size()).(如果未来的STL将其size_type更改为要签名,那么即使最后一个缺点也会消失.)

然而,试图教人们经常写作是毫无希望的(也是令人尴尬的)for (ptrdiff_t i = ... ; ... ; ...).(甚至指南目前只在一个地方使用它,这是一个与索引无关的"坏"示例.)

因此,我们应该提供gsl::index(以后可以考虑作为考虑因素std::index)作为一种类型ptrdiff_t,因此我们希望(而不是尴尬地)教人们定期写作 (index i = ... ; ... ; ...).

为什么不告诉别人写ptrdiff_t因为我们相信告诉别人你在C++中必须做的事情会很尴尬,即使我们做了,人们也不会这样做.写作ptrdiff_t相比,是太丑陋和unadoptable autoint.添加名称的目的index是使使用正确大小的签名类型尽可能简单和有吸引力.

编辑:Herb Sutter的更多理由

ptrdiff_t够大吗?是.标准容器已经被要求不具有可以表示的元素ptrdiff_t,因为减去两个迭代器必须符合difference_type.

但是ptrdiff_t真的够大,如果我有一个内置数组charbyte大于内存地址空间大小的一半,那么有ptrdiff_t多少元素可以表示?是.C++已经使用有符号整数作为数组下标.因此index,请将其用作绝大多数用途的默认选项,包括所有内置数组.(如果遇到极其罕见的数组或类数组类型,大于地址空间的一半及其元素sizeof(1),并且您要小心避免截断问题,请继续使用size_tfor索引这种非常特殊的容器.这种野兽在实践中是非常罕见的,当它们出现时通常不会被用户代码直接索引.例如,它们通常出现在一个内存管理器中,它接管系统分配并包裹个体较小的用户使用的分配,或提供自己的接口的MPEG或类似的分配;在这两种情况下,size_t只应在内存管理器或MPEG类实现内部进行.)

  • 将`gsl :: index`定义为`ptrdiff_t`将使其成为负面索引不适合的上下文中的即时反模式.它将隐式标记在诸如"垃圾质量代码"之类的上下文中使用`gsl :: index`的任何代码.适当的类型确实是`std :: size_t`,毫无疑问必须是*unsigned*.每个人都知道*signed*indexing在某些情况下是必要的,但是默认情况下让人们使用签名索引*不是一种选择.在不同的上下文中对不同类型的需求正是我们没有"默认"索引类型的原因.这种类型不存在. (12认同)
  • 回应引用的文字; 使用带符号的索引在访问大小超过"SIZE_MAX/2"的容器时会出现导致整数溢出的明显问题.我希望还有其他更改来解决这个问题(例如,使对象的最大大小实际上是`SIZE_MAX/2`而不是`SIZE_MAX`). (5认同)
  • 我不明白如何使用`ptrdiff_t`*尴尬*.如果Sutter不喜欢它,那就好了,但这不是一个很好的论据. (3认同)
  • "因为我们相信告诉别人你在C++中必须做的事情会很尴尬" - 事实上,C++已经足够令人尴尬了.虽然不是一个合理的理由.我同意@ user694733. (2认同)

lit*_*die 36

我从一个旧计时器(前C++)的角度来看待它...当天被理解int为该平台的本土词并且可能提供最佳性能.

如果你需要更大的东西,那么你就可以使用它并在性能上付出代价.如果你需要更小的东西(有限的内存,或特定需要固定大小),同样的事情..否则使用int.是的,如果你的值在一个目标平台上的int可以容纳它的范围内,而另一个目标平台上的int不能......那么我们就有了我们编译时特定的特定定义(在它们成为标准化之前我们自己创建).

但是现在,现在,处理器和编译器要复杂得多,而且这些规则并不那么容易应用.更难以预测您选择的性能对未知的未来平台或编译器的影响......我们如何才能真正知道uint64_t在任何特定的未来目标上的表现会比uint32_t更好或更差?除非你是处理器/编译器大师,否则你不......

所以...也许它是老式的,但除非我为Arduino等受限环境编写代码,否则我仍然使用int通用值,我知道在int我编写的应用程序的所有合理目标的大小范围内.并且编译器从那里获取它...这些天通常意味着32位签名.即使假设16位是最小整数大小,它也涵盖了大多数用例..并且大于这个数字的用例很容易识别并用适当的类型处理.

  • 更重要的是,它不仅仅是'int`是平台的本土词.由于64位已经成为常态,遗留的考虑因素已经让"int"落后了.当然这就是为什么首先存在类似`size_t`的类型 - 因为你的系统最了解. (10认同)
  • 据推测,一个2*31 + 1元素的数组不仅在一天的代码中随机出现而没有知识.这是一个刻意的设计决定,在这种情况下使用适当的类型.如果它是一个小数组,你还明确地将索引值键入unint8_t吗? (9认同)
  • 有时大型数组*会出现在以前在小型数组上运行的代码中*.例如,当您购买新相机时会发生这种情况 - 然后您是否检查了所有源代码以检查每个"width*height*bpp"的分配? (4认同)
  • 在给定最小大小约束的情况下,stdint.h中`int_fastN_t`类型的整个点是"给出最佳性能".尽管如此,标准对于*需要快速进行整数运算才是模糊的. (3认同)

Eya*_* K. 18

大多数程序不会在几个CPU周期的边缘生存和死亡,并且int很容易编写.但是,如果您对性能敏感,我建议使用定义的固定宽度整数类型<cstdint>,例如int32_tuint64_t.它们的优点在于它们在签名或未签名方面的预期行为,以及它们在内存中的大小.此标题还包括快速变体,例如int_fast32_t,至少是规定的大小,但如果它有助于提高性能,则可能更多.

  • 对索引使用固定的64位整数类型会导致32位系统的性能下降.在这方面,`size_t`或`ptrdiff_t`要好得多. (10认同)
  • @Joshua:在32位系统上,64位的任何东西如何比32位更快?什么是_you_吸烟?! (7认同)
  • @Joshua:我致力于高性能计算,我可以告诉你,"nwellnhof"是对的:你不想使用大小超过计算机字大小的整数,除非你真的需要这样的范围.它只会杀死性能. (4认同)
  • 您正在混合代码大小和效率.像`int32_t`这样的固定大小类型通常会显着降低*效率,因为通过强制确切的大小,它们会限制编译器不使用可能更自然或计算效率更高的大小. (3认同)
  • 此外,精确宽度类型是可选的,不需要存在,所以至少纯粹从保持代码理论上可移植性的角度来看,我不认为在没有特定固定宽度要求的情况下使用它们是好的. (2认同)

Upr*_*ted 15

没有正式理由使用int.它不符合标准的任何理智.对于索引,您几乎总是需要签名指针大小的整数.

这就是说,打字int感觉就像你刚刚对Ritchie说的那样,打字std::ptrdiff_t感觉就像Stroustrup只是踢你的屁股.编码人员也是人,不要给他们的生活带来太多的丑陋.我宁愿使用long一些容易打字的typedef index而不是std::ptrdiff_t.

  • @InsideLoop可悲的是,你是对的,但这是由于工具供应商的判断力差.在N位平台上,`int`应该是其定义**的N位**.我们需要的是真正意味着什么是"int"的意思(在64位工具供应商破坏它之前). (7认同)
  • @Sjoerd`int`已签名但在大多数64位平台上为32位. (5认同)
  • C++委员会的成员同意ptrdiff_t是丑陋的.所以他们建议使用"索引". (2认同)
  • "对于索引,你几乎总是想要签名的机器字长型." 哪个匹配"int"的定义! (2认同)
  • YMMD 与关于 Ritchie 和 Stroustrup 的那条线 :-) (2认同)

Dam*_*mon 13

这有点基于意见,但唉,这个问题也有点乞求.

首先,你谈论的是整数和指数,就好像它们是同一个东西,事实并非如此.对于任何类似"整数,不确定大小"这样的东西,简单地使用int当然,大部分时间,仍然是合适的.对于大多数应用程序而言,这在大多数情况下都可以正常工作,编译器对此很满意.默认情况下,没关系.

对于数组索引,这是一个不同的故事.

到目前为止,还有一个正式的正确的事情,那就是std::size_t.在将来,可能有一个std::index_t使得意图在源级别更清晰,但到目前为止还没有.
std::ptrdiff_t作为一个索引"工作",但同样不正确,int因为它允许负指数.
是的,这发生在萨特先生认为正确的事情上,但我不同意.是的,在汇编语言指令级别,这支持很好,但我仍然反对.标准说:

8.3.4/6:E1[E2]*((E1)+(E2))[...] 相同由于适用的转换规则+,if E1是一个数组和E2一个整数,然后E1[E2]引用的是E2-th成员E1.
5.7/5:[...]如果指针操作数和结果都指向同一个数组对象的元素,或者一个超过数组对象的最后一个元素[...],则行为是未定义的.

数组预订是指E2-th成员E1.没有像数组的负数元素这样的东西.但更重要的是,带有负加法表达式的指针算法会调用未定义的行为.

换句话说:任何大小的签名索引都是错误的选择.指数未签名.是的,签署的指数有效,但它们仍然是错误的.

现在,虽然size_t根据定义是正确的选择(一个足够大的无符号整数类型,可以包含任何对象的大小),但对于普通情况或默认情况下它是否是真正的好选择可能是有争议的.

说实话,你最后一次创建一个包含10 19个元素的数组是什么时候?

我个人使用unsigned int默认,因为这允许的40亿个元素对于(几乎)每个应用程序都足够了,并且它已经推动普通用户的计算机非常接近其限制(如果仅仅订阅整数数组,则假设分配了16GB的连续内存).我个人认为默认为64位索引是荒谬的.

如果您正在编写关系数据库或文件系统,那么是的,您将需要 64位索引.但对于普通的"普通"程序,32位索引就足够了,它们只消耗了一半的存储空间.

当保留大量的指数时,如果我能负担得起(因为数组不超过64k元素),我甚至会去uint16_t.不,我不是在开玩笑.

存储真的是这样的问题吗?贪婪地保存两四个字节是荒谬的,不是吗!好吧,不......

大小可能是指针的问题,所以肯定它也可以用于索引.x32 ABI不存在无缘无故.你不会注意到不必要的大索引的开销,如果你总共只有少数几个(就像指针一样,无论如何它们都会在寄存器中,没有人会注意到它们的大小是4还是8字节).

但是想想一个槽映射的例子,你可以在其中存储每个元素的索引(取决于实现,每个元素有两个索引).哎呀,无论你是每次都打L2,还是每次访问都有一个缓存未命中,它确实会让你感到有些不同!更大并不总是更好.

在一天结束时,你必须问问自己你付出了什么,以及你获得了什么回报.考虑到这一点,我的风格建议是:

如果它花费你"没有",因为你只有一个指针和一些指数可以保持,那么只需使用正式的正确(那就是size_t).正式是正确的,正确的总是有效的,它是可读的和可理解的,正确的是...... 从来没有错.

但是,如果它确实花了你(你可能有几百或者几千或一万个指数),你得到的东西是没有价值的(因为你甚至不能存储2 20个元素,所以你是否可以订阅2 32或2 64没有区别),你应该三思而后行太浪费.

  • 很抱歉,您不同意索引应该是无符号整数的事实.正如C++社区中的许多人一样,你错了.不仅Herb Sutter而且Bjarne Stroustrup和Chandler Carruth也同意这一点,并认为STL做出了错误的选择.是指数是非负整数.所以呢?整数除以0意味着未定义的行为,没有人觉得需要创建一个不包含0的类型.除此之外,如果p是一个指针并且q = p + n,则证明q - p是标准的,一个`ptrdiff_t`."n"这种类型听起来很自然. (2认同)

jic*_*ick 11

在大多数现代64位体系结构中,int是4个字节,ptrdiff_t是8个字节.如果你的程序使用了大量的整数,使用ptrdiff_t替代int翻番程序的内存需求.

还要考虑现代CPU经常受到内存性能的瓶颈.使用8字节整数也意味着你的CPU缓存现在拥有的元素数量是以前的一半,所以现在它必须更频繁地等待缓慢的主存储器(这可能很容易需要几百个周期).

在许多情况下,执行"32到64位转换"操作的成本与内存性能完全相形见绌.

所以这是一个int在64位机器上仍然流行的实用原因.

  • 现在你可能会争论二十几个不同的整数类型,可移植性和标准委员会以及所有内容,但事实是,对于那里写的很多C++程序,他们正在思考一个"规范"的架构,这通常是唯一的他们一直关注的建筑.(如果您正在为Windows游戏编写3D图形例程,那么您肯定它不会在IBM大型机上运行.)因此,对于他们来说,问题归结为:"我是否需要4字节整数或这是一个8字节的?"


Ste*_*mit 5

我的建议是不要过多地考虑汇编语言输出,不要过分担心每个变量的确切大小,而不是说"编译器在家里感觉".(我真的不知道你最后一个是什么意思.)

对于花园种类的整数,大多数程序都充满了,平原int应该是一个很好的类型.它应该是机器的自然字大小.它应该是高效的使用,既不浪费不必要的内存,也不会在内存和计算寄存器之间移动时引入大量额外的转换.

现在,确实有很多更专业的用途,普通int的不再合适.特别是,对象的大小,元素的数量和数组的索引几乎总是如此size_t.但这并不意味着所有整数都应该是size_t!

有符号和无符号类型的混合以及不同大小类型的混合也可能导致问题.但是现代编译器很好地处理了大部分问题以及它们为不安全组合发出的警告.因此,只要您使用现代编译器并注意其警告,您就不需要选择不自然的类型,只是为了避免类型不匹配问题.