overflow与implicit conversion的技术及CPU寄存器层面差异求证
(我是新手,表述可能存在不准确之处)
在我当前的认知模型中,overflow(溢出)是一种算术现象(发生在执行算术操作时),而implicit conversion(隐式转换)是一种赋值(无论是否为初始化)现象(发生在右值无法放入左值的赋值操作中)。
但我常看到人们将overflow和implicit conversion这两个概念混用,与我的预期不符。例如learncpp团队的以下表述:
Integer overflow(常简称为overflow)发生在尝试存储超出类型范围的值时。本质上,我们要存储的数值所需的比特数超过了对象可用的比特数,此时会因对象内存不足导致数据丢失[1]。
以及关于无符号整数overflow的表述:
如果尝试将280(需要9比特表示)存储到1字节(8比特)的无符号整数中,结果就是overflow[2]*
尤其是这段使用"modulo wrapping"的表述:
换个角度理解:任何大于类型可表示最大值的数都会直接“环绕”(有时称为“modulo wrapping”)。255在1字节整数的范围内,所以没问题;256超出范围,会环绕到0;257环绕到1;280环绕到24[2]。
在这类场景中,左值超出限制的赋值被称为overflow,但我认为此处应使用implicit conversion这个术语。我也看到overflow被用于算术表达式结果超出左值限制的场景。
我认为存在差异。在参考文献[3]的“Numeric conversions - Integral conversions”章节中,针对无符号整数有如下表述:
[...] 结果值是与源值模2^n相等的最小无符号值,其中n是表示目标类型所用的比特数[3]。
针对有符号整数(加粗部分为我标注):
如果目标类型是有符号类型,且源整数可在目标类型中表示,则值不变;否则结果在C20之前是实现定义的,在C20之后是与源值模2^n相等的目标类型唯一值,其中n是表示目标类型所用的比特数。(注意这与有符号整数算术overflow不同,后者是未定义行为)[3]。
在参考文献[4]的Overflow章节中,我们看到(加粗部分为我标注):
无符号整数算术始终以2^n为模执行,其中n是该整数类型的比特数。[...]
当有符号整数算术操作overflow(结果无法放入结果类型)时,行为是未定义的[4]。
在我看来,overflow显然是算术现象,而implicit conversion是赋值超限现象。我的这个理解是否准确?
我认为也存在差异。我对C并不精通,对汇编更是陌生,但通过一个实验,使用MSVC(编译标志/std:c20)和MASM(宏汇编)查看以下代码的输出,尤其是标志寄存器(flag register),可以发现算术操作和赋值(即implicit conversion)会产生不同的现象。
(我在Visual Studio 2022调试器中检查了标志寄存器,以下汇编代码与调试结果基本一致)
#include <iostream> #include <limits> int main(void) { long long x = std::numeric_limits<long long>::max(); int y = x; // // long long k = std::numeric_limits<long long>::max(); ++k; }
输出的汇编代码为:
y$ = 32 k$ = 40 x$ = 48 main PROC $LN3: sub rsp, 72 ; 00000048H call static __int64 std::numeric_limits<__int64>::max(void) ; std::numeric_limits<__int64>::max mov QWORD PTR x$[rsp], rax mov eax, DWORD PTR x$[rsp] mov DWORD PTR y$[rsp], eax call static __int64 std::numeric_limits<__int64>::max(void) ; std::numeric_limits<__int64>::max mov QWORD PTR k$[rsp], rax mov rax, QWORD PTR k$[rsp] inc rax mov QWORD PTR k$[rsp], rax xor eax, eax add rsp, 72 ; 00000048H ret 0 main ENDP
C++中y的拷贝初始化对应MASM中的代码:
int y = x; mov eax, DWORD PTR x$[rsp] mov DWORD PTR y$[rsp], eax
mov指令直接忽略x的64比特,仅截取其32比特,通过dword ptr操作符进行转换并存储到32位eax寄存器中。mov指令不会设置overflow或carry标志位。
C++中k的自增操作对应MASM中的代码:
++k; mov rax, QWORD PTR k$[rsp] inc rax mov QWORD PTR k$[rsp], rax
执行inc指令时,overflow标志位(有符号溢出)会被设置为1。
在我看来,尽管转换的实现方式(如mov)可能不同,但使用mov类指令的转换与算术overflow存在明显差异:算术操作会设置标志位。我的这个理解是否准确?
说明
- 关于无符号整数的overflow术语存在争议,但这并非我讨论的重点
参考文献
[1] learncpp 有符号整数教程
[2] learncpp 无符号整数教程
[3] cppreference 隐式转换章节
[4] cppreference 算术运算符溢出章节
内容的提问来源于stack exchange,提问作者Manuel

