(我是新手,所以我说的话可能不准确)
在我目前的心理模型中,溢出是一种算术现象(当我们执行算术运算时发生),而隐式转换是一种赋值(初始化与否)现象(当我们进行赋值时,右手的值不适合左手的值)。
然而,我经常看到'overflow'和'implicit conversion'这两个概念交替使用,这与我所期望的不同。例如,learncpp团队的这句话,讨论了带符号int的overflow和'bit insufficiency':
当我们试图存储一个超出类型范围的值时,就会发生整数溢出(通常简称为溢出)。本质上,我们试图存储的数字需要比对象可用的位数更多的位来表示。在这种情况下,数据会丢失,因为对象没有足够的内存来存储所有内容[1]。
这是关于无符号整型溢出的:
如果我们尝试将数字280(需要9位来表示)存储在1字节(8位)无符号整数中,会发生什么情况?答案是overflow [2]*
特别是这个使用“模 Package ”的人:
这里有另一种方法来考虑同样的事情。任何大于类型所能表示的最大数的数都可以简单地“环绕”(有时称为“模回绕”)。255在1字节整数的范围内,因此255是合适的。然而,256在该范围之外,因此它会绕回值0。257绕回值1。280绕回值24 [2]。
在这种情况下,有人说,超过左手极限的赋值会导致溢出,但我希望在这种情况下,术语“隐式转换”。
我看到溢出这个术语也被用于算术表达式,它的结果超出了左手的限制。
1隐式转换和上溢/下溢之间有什么技术上的区别吗?
我想是的。在参考文献[3]中的“数值转换-积分转换”一节中,对于无符号整数:
[...]得到的值是最小的无符号值,等于源值模2^n,其中n是用于表示目标类型[3]的位数。
对于签名(粗体我):
如果目标类型是已签名的,如果源整数可以在目标类型中表示,则该值不会更改。否则,结果由实现定义(直到C20)目标类型的唯一值等于源值模2n,其中n是用于表示目标类型的位数。(C20起).(注意,这与未定义的有符号整数算术溢出不同)[3]。
如果我们转到引用的部分(溢出),我们会发现(粗体我的):
无符号整数算术总是以2n为模执行,其中n是该特定整数的位数。[..]
当有符号整数算术运算溢出(结果不适合结果类型)时,行为未定义[4]。
对我来说,显然溢出是一种算术现象,而隐式转换是不适合的赋值中的现象。我的解释准确吗?
2隐式转换和溢出在位级(cpu)上有什么区别吗?
我也这么认为。我对c远远不擅长,对汇编更是如此,但是作为一个实验,如果我们用MSVC(flag /std:c20)和MASM(宏汇编)检查下面代码的输出,特别是检查标志寄存器,如果是算术运算或赋值(“隐式转换”),会出现不同的现象。
(我检查了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
可在https://godbolt.org/z/6j6G69bTP处检查
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语句既不设置溢出标志,也不设置进位标志。
c++中k的增量对应于MASM中的增量:
++k;
mov rax, QWORD PTR k$[rsp]
inc rax
mov QWORD PTR k$[rsp], rax
执行inc语句时,溢出标志(有符号溢出)设置为1.
对我来说,虽然可以用不同的方式实现(mov)转换,但使用mov变体和算术溢出的转换之间有明显的区别:算术设置标志。我的解释准确吗?
备注
- 显然,有一个关于unsigned的术语溢出的讨论,但这不是我要讨论的
参考文献
[1][4]第一次世界大战后,中国的经济迅速发展,人民生活水平不断提高,人民生活水平不断提高https://en.cppreference.com/w/cpp/language/operator_arithmetic#Overflows
1条答案
按热度按时间qqrboqgw1#
让我们试着把它分解一下。我们必须从更多的术语开始。
理想算术
执行
进位输出也用于多字运算中,在组成结果的字之间进位1。
溢出
在二进制补码计算机上,当一个加法得进位输出不等于最后一位得进位时,整数溢出.在有标志得体系结构中,这通常会导致溢出标志置位.当用有符号数进行计算时,溢出得出现表明结果不适合输出寄存器,因此不能代表理想得算术结果.
至于“结果不匹配”,它就像是有符号算术的进位。但是,当使用有符号数的多字算术时,仍然需要使用普通的进位将一个字进位到下一个字。
一些作者称执行为“无符号溢出”,溢出为“有符号溢出”。这里的意思是,在这样的术语中,溢出是指任何操作结果不可表示的情况。其他类型的溢出包括浮点溢出,在IEEE-754机器上通过饱和到+-无穷大来处理。
转换
转换是指将一个数据类型表示的值转换为另一个数据类型。当涉及的数据类型是整数类型时,通常通过 * 扩展、*截断、 或 * 饱和 * 或 * 重新解释 * 来完成转换
当试图从一种类型或另一种类型进行转换而结果不可表示时,一些作者也将这种情况称为“溢出”,就像“有符号溢出”和“无符号溢出”一样。然而,这是由位宽变化引起的不同现象,而不是算术结果。因此,是的,你的解释是准确的。这是两个独立的现象,通过“结果值不符合类型”的共同概念联系在一起。
要了解这两者是如何相互联系的,您也可以将两个 n 位数的加法解释为临时产生一个 n + 1位数,这样加法总是理想的。然后,将结果 * 截断 * 为 n 位,并存储在结果寄存器中。如果结果不可表示,则发生 * 进位 * 或 * 溢出 *。进位输出位则正好是临时结果的最高有效位,该临时结果然后被丢弃以到达最终结果。
问题2
对我来说,虽然可以用不同的方式实现(mov)转换,但使用mov变体和算术溢出的转换之间有一个明显的区别:算术设置标志。我的解释准确吗?
解释不正确,标志的存在是一个红鲱鱼。有两种架构,其中数据移动设置标志(如ARMv 6-M)和算术不设置标志(如x86当使用
lea
指令来执行它)或甚至没有标志(如RISC-V)的架构。还请注意,(隐含的或非隐含的)并不一定会产生指令。符号扩展和饱和通常会产生指令,但是零扩展通常是通过确保寄存器的顶部是空的来实现的,这是CPU可能能够作为你想要执行的其他操作的副作用来实现的。截断可以通过忽略寄存器的顶部来实现。当然,一般来说,重新解释本质上也不会产生任何代码。
至于carry out和overflow,它们的发生取决于你用什么值来执行算术运算。这些事情只是发生了,除非你想检测它们的发生,否则不需要代码。这只是默认的事情。