这个问题在这里已有答案:
许多样式指南(例如Google)建议在索引数组时使用 int
作为默认整数 . 随着64位平台的兴起,大多数时候 int
只有32位,这不是平台的自然宽度 . 因此,除了简单的相同之外,我认为没有理由保持这种选择 . 我们清楚地看到编译以下代码的位置:
double get(const double* p, int k) {
return p[k];
}
被编译成
movslq %esi, %rsi
vmovsd (%rdi,%rsi,8), %xmm0
ret
其中第一条指令将32位整数提升为64位整数 .
如果代码转换成
double get(const double* p, std::ptrdiff_t k) {
return p[k];
}
现在生成的程序集
vmovsd (%rdi,%rsi,8), %xmm0
ret
这清楚地表明,CPU使用 std::ptrdiff_t
比在 int
感觉更有家的感觉 . 许多C用户已经转移到 std::size_t
,但我不想使用无符号整数,除非我真的需要模数 2^n
行为 .
在大多数情况下,使用 int
不会损害性能,因为未定义的行为或有符号整数溢出允许编译器在内部将 int
推广到处理索引的 std::ptrdiff_t
循环中,但我们从上面清楚地看到编译器没有感觉到回家 int
. 此外,在64位平台上使用 std::ptrdiff_t
会使溢出不太可能发生,因为当我们看到越来越多的人被 int
溢出困住时,他们必须处理大于 2^31 - 1
的整数,这些日子变得非常普遍 .
从我所看到的,唯一让 int
分开的事情似乎是 5
等文字是 int
这一事实,但如果我们将 std::ptrdiff_t
作为默认整数移动,我看不出它会导致任何问题 .
我即将 std::ptrdiff_t
作为我小公司编写的所有代码的事实上的标准整数 . 有什么理由可能是一个糟糕的选择吗?
PS:我同意这个名字 std::ptrdiff_t
是丑陋的事实,这就是为什么我把它定义为 il::int_t
这看起来好一点的原因 .
PS:据我所知,很多人会建议我使用 std::size_t
作为默认整数,我真的想说清楚我不想使用无符号整数作为我的默认整数 . 在STL中使用 std::size_t
作为默认整数是Bjarne Stroustrup和视频Interactive Panel: Ask Us Anything时间42:38和1:02:50的标准委员会所承认的错误 .
PS:就性能而言,在我所知道的任何64位平台上, +
, -
和 *
以相同的方式编译 int
和 std::ptrdiff_t
. 所以速度没有区别 . 如果除以编译时常量,速度是相同的 . 只有在你对 b
一无所知时才划分 a/b
,在64位平台上使用32位整数会给你带来轻微的性能优势 . 但是这种情况非常罕见,因为我认为不要选择离开 std::ptrdiff_t
. 当我们处理矢量化代码时,这里有一个明显的区别,而且越小越好,但这是一个不同的故事,并且没有理由坚持 int
. 在这些情况下,我建议使用固定大小的C类型 .
10 回答
有关C核心指南的讨论使用了什么:
https://github.com/isocpp/CppCoreGuidelines/pull/1115
Herb Sutter写道,将添加
gsl::index
(将来可能std::index
),其定义为ptrdiff_t
.编辑:Herb Sutter的更多理由
我从一个旧计时器(前C)的角度来看这个... ...当天回想起
int
是该平台的本土词,并且可能提供最佳性能 .如果你需要更大的东西,那么你就可以使用它并在性能上付出代价 . 如果你需要更小的东西(有限的内存,或特定需要固定大小),同样的事情..否则使用
int
. 是的,如果你的值在一个目标平台上的int可以容纳它的范围内,并且另一个目标平台上的int不能...那么我们就有了我们编译时特定的特定定义(在它们成为标准化之前我们自己创建) .但是现在,现在,处理器和编译器要复杂得多,而且这些规则并不那么容易应用 . 更难以预测您所选择的性能对某些未知的未来平台或编译器的影响......我们如何才能真正知道uint64_t在任何特定的未来目标上的表现会比uint32_t更好还是更差?除非你是处理器/编译器大师,否则你不......
所以...也许它是老式的,但除非我为像Arduino等受限制的环境编写代码,我仍然使用
int
作为通用值,我知道在我正在编写的应用程序的所有合理目标上都将在int
范围内 . 并且编译器从那里获取它...这些天通常意味着32位签名 . 即使假设16位是最小整数大小,它也涵盖了大多数用例..并且大于这个数字的用例很容易识别并用适当的类型处理 .大多数程序不会在几个CPU周期的边缘生存和死亡,并且
int
非常容易编写 . 但是,如果您对性能敏感,我建议使用<cstdint>
中定义的固定宽度整数类型,例如int32_t
或uint64_t
. 它们的好处在于它们在签名或未签名方面的预期行为,以及它们在内存中的大小 . 此标头还包括快速变体,例如int_fast32_t
,它们至少是规定的大小,但如果它有助于提高性能,则可能更多 .没有使用
int
的正式理由 . 它不符合标准的任何理智 . 对于索引,您几乎总是需要签名指针大小的整数 .那就是说,输入
int
感觉就像你刚刚对Ritchie说的那样,输入std::ptrdiff_t
感觉就像Stroustrup刚踢你的屁股 . 编码人员也是人,不要给他们的生活带来太多的丑陋 . 我更喜欢使用long或一些容易打字的typedef,如index
而不是std::ptrdiff_t
.这有点基于意见,但唉,唉问题也有点乞求它 .
首先,你谈论的是整数和指数,就好像它们是同一个东西,事实并非如此 . 对于任何像"integer of sorts, not sure what size"这样的东西,简单地使用
int
当然是大部分时间,仍然合适 . 对于大多数应用程序而言,这在大多数情况下都可以正常工作,并且编译器对此很满意 . 默认情况下,没关系 .对于数组索引,这是一个不同的故事 .
到目前为止,还有一个正式的正确的东西,那就是
std::size_t
. 在将来,可能会有一个std::index_t
,这使得意图在源级别更清晰,但到目前为止还没有 .std::ptrdiff_t
作为索引"works"但与int
一样不正确,因为它允许负索引 .是的,这就是萨特先生认为正确的事情,但我不同意 . 是的,在汇编语言指令级别上,这支持很好,但我仍然反对 . 标准说:
数组预订是指
E1
的E2
成员 . 没有像数组的负数元素这样的东西 . 但更重要的是,带有负加法表达式的指针算法会调用未定义的行为 .换句话说:任何大小的签名索引都是错误的选择 . 指数未签名 . 是的,签署的指数有效,但它们仍然是错误的 .
现在,虽然根据定义
size_t
是正确的选择(一个足够大的无符号整数类型,可以包含任何对象的大小),但对于普通情况或者默认情况下它是否是真正的好选择可能是有争议的 .说实话,你最后一次创建一个包含1019个元素的数组是什么时候?
我个人使用
unsigned int
作为默认值,因为这允许的40亿个元素足够(几乎)每个应用程序,并且它已经推动普通用户的计算机而非接近其限制(如果仅仅订阅整数数组,那个假定分配了16GB的连续内存) . 我个人认为默认为64位索引是荒谬的 .如果您正在编写关系数据库或文件系统,那么是的,您将需要64位索引 . 但对于平均_1132426_程序,32位索引就足够了,它们只消耗了一半的存储空间 .
当保留大量的索引时,如果我能负担得起(因为数组不超过64k元素),我甚至会去
uint16_t
. 不,我不是在开玩笑 .存储真的是这样的问题吗?贪婪地保存两四个字节是荒谬的,不是吗!好吧,不......
大小可能是指针的问题,所以肯定它也可以用于索引 . x32 ABI不存在无缘无故 . 你不会注意到不必要的大索引的开销,如果你总共只有少数几个(就像指针一样,无论如何它们都会在寄存器中,没有人会注意到它们的大小是4还是8字节) .
但是想想一个槽映射的例子,你可以在其中存储每个元素的索引(取决于实现,每个元素有两个索引) . 哎呀,无论你是每次都打L2,还是每次访问都有一个缓存未命中,它肯定会让你感到有点不同!更大并不总是更好 .
在一天结束时,你必须问问自己你付出的代价,以及你得到的回报 . 考虑到这一点,我的风格建议是:
如果它花费你"nothing",因为你只有...一个指针和一些指数要保持,然后只使用's formally correct (that' d
size_t
) . 正式的正确是好的,正确的总是有效的,它是可读的和可理解的,正确的是......永远不会错 .但是,如果它确实花了你(你可能有几百或者几千或一万个指数),你得到的东西是没有 Value 的(因为你甚至不能存储220个元素,所以无论你是否订阅232或264都不会差异),你应该三思而后行太浪费 .
在大多数现代64位体系结构中,
int
是4个字节,ptrdiff_t
是8个字节 . 如果你的程序使用了很多整数,使用ptrdiff_t
而不是int
可能会使程序的内存需求翻倍 .还要考虑现代CPU经常因内存性能而受到瓶颈 . 使用8字节整数也意味着你的CPU缓存现在拥有的元素数量是以前的一半,所以现在它必须更频繁地等待缓慢的主存储器(这可能很容易需要几百个周期) .
在许多情况下,执行“32到64位转换”操作的成本与内存性能完全相形见绌 .
所以这是一个实际的原因
int
在64位机器上仍然很受欢迎 .我给你的建议是不要过多地考虑汇编语言输出,不要过分担心每个变量的确切大小,而不是说“编译器在家里感觉” . (我真的不知道你最后一个是什么意思 . )
对于花园种类的整数,大多数程序都充满了,普通
int
应该是一个很好用的类型 . 它应该是高效使用的,既不浪费不必要的内存,也不会在内存和计算寄存器之间移动时引入大量额外的转换 .现在,确实有很多更专业的用途,普通的
int
不再合适 . 特别是,对象的大小,元素的数量和数组的索引几乎总是size_t
. 但这并不意味着所有整数都应该是size_t
!有符号和无符号类型的混合以及不同大小类型的混合也可能导致问题 . 但是现代编译器中的大多数都很好地处理了它们以及它们为不安全组合发出的警告 . 因此,只要您使用现代编译器并注意其警告,您就不需要选择不自然的类型,只是为了避免类型不匹配问题 .
我没有't think that there' s real 使用
int
的原因 .如何选择整数类型?
如果是用于位操作,则可以使用无符号类型,否则使用带符号类型
如果是与内存相关的东西(索引,容器大小等),你不知道上限,请使用
std::ptrdiff_t
(唯一的问题是当大小大于PTRDIFF_MAX
时,这在实践中很少见)否则使用
intXX_t
或int(_least)/(_fast)XX_t
.这些规则涵盖了
int
的所有可能用法,它们提供了更好的解决方案:int
不适合存储与内存相关的东西,因为它的范围可能比索引小(这不是理论上的东西:对于64位机器,int
通常是32位,所以使用int
,你只能处理20亿元素)int
不适合存储"general"整数,因为它的范围可能小于所需的范围(如果范围不够则会发生未定义的行为),或相反,它的范围可能比需要的大得多(因此内存浪费)唯一的原因是人们可以使用
int
,如果进行计算,并且知道范围适合[-32767; 32767](标准只能保证这个范围 . 请注意,这些实现可以自由地提供更大的int
,他们通常会这样做 . 目前int
在很多平台上都是32位的) .由于上面提到的
std
类型有点单调乏味,所以可以将它们缩短(我使用s8
/u8
/.../s64
/u64
和spt
/upt
("(un)signed pointer sized type")作为ptrdiff_t
/size_t
. 我've been using these typedefs for 15 years, and I' ve从来没有写过int
,因为...) .亲
我想,更容易打字?但你总能
typedef
.许多API使用int,包括标准库的一部分 . 这在历史上导致了问题,例如在转换到64位文件大小期间 .
由于默认类型提升规则,比int更窄的类型可以扩展为int或unsigned int,除非您在很多地方添加显式转换,并且在某些实现的某些地方,许多不同的类型可能比int窄 . 所以,如果你关心可移植性,这是一个小问题 .
Con
大部分时间我也使用
ptrdiff_t
作为索引 . (我同意Google的观点,即无符号索引是一个错误的吸引子 . )对于其他类型的数学,有int_fast64_t
.int_fast32_t
,等等,也将与int
一样好或更好 . 几乎没有真实世界的系统,除了上个世纪的一些已经废弃的Unices之外,使用ILP64,但是有很多CPU需要64位数学 . 从标准来看,技术上允许编译器在int
大于32,767时破坏程序 .也就是说,任何值得盐的C编译器都会在很多代码上进行测试,这些代码会在内部循环中向指针添加
int
. 所以它不能做任何太愚蠢的事情 . 目前硬件的最坏情况是它需要额外的指令来将32位带符号值签名扩展为64位 . 但是,如果你真正想要的是最快的指针数学,对于幅度在32 kibi和2 gibi之间的值的最快数学,或者最少浪费的memoey,你应该说出你的意思,而不是让编译器猜测 .我猜99%的病例没有理由使用
int
(或其他大小的有符号整数) . 但是,仍有情况,使用int
是一个不错的选择 .一场表演:
int
和size_t
之间的一个区别是i++
可能是int
的未定义行为 - 如果i
是MAX_INT
. 这实际上可能是一件好事,因为编译器可以使用这种未定义的行为来加快速度 .例如,在这个question中,差异在于利用未定义的行为和使用禁止此漏洞的编译器标志-fwrapv之间的因子2 .
如果使用
int
我的工作循环速度提高了两倍 - 我肯定会使用它B)不易出错的代码
使用
size_t
的反转for循环看起来很奇怪并且是错误的来源(我希望我做对了):通过使用
你应该得到经验不足的C程序员的感激,他们将来必须管理你的代码 .
C)使用签名索引进行设计
通过使用
int
作为索引,您可以用负值表示错误值/超出范围,这样可以派上用场并且可以使代码更清晰 .如果元素不存在,
-1
. 要检测此"error",您不必知道数组的大小 .如果element在数组中,
-index
用于元素将插入数组的位置(并且不在数组中) .显然,相同的信息可以使用正索引值进行编码,但代码变得不那么直观 .
显然,还有理由选择
int
而不是std::ptrdiff_t
- 其中一个是内存带宽 . 有许多内存限制算法,对于它们来说,减少从RAM转移到缓存的内存量非常重要 .如果你知道,所有数字都小于
2^31
那么使用int
将是一个优势,因为否则一半的内存传输只会写入你已经知道的0
,它们就在那里 .一个例子是压缩的稀疏行(crs)矩阵 - 它们的索引存储为
ints
而不是long long
. 因为许多具有稀疏矩阵的操作都是内存绑定的,所以使用32位或64位之间确实存在差异 .