d算术混淆大论战
隐式转换
易错
问题是如何说服沃尔特
我想要输出隐式转换
的警告的编译时选项
我更希望,移植C
到D
时,可报错
,而不是保留地雷
代码.应该是这样的-vimplicit-conversions
隐式开关.
C系外系统编程语言
倾向于用显式转换
.
第1个示例,在D
中修复了.乘法
示例有意思,其他在速度
相关时,意义不大.
奇怪的是,没有更多signed -> unsigned
间的转换,那才是个大坑
.
对包装和非包装
(溢出陷阱)算术运算,现代编程语言倾向于有单独的运算符
或内置操作符
.
抓此类错误与检查
数组边界一样有用.处理器早晚要加缺失指令
来加快速度.
C++
已用-Wconversion
修复了.
当然,如果用模板库
,可能会收到不想要警告.因此,好语法消除内联警告,鼓励打开最大严格性,并在方便时可选的禁用它.内联静默警告是ts
比js
好的地方.
写模板
时要注意,可基于有无符号
限制模板参数.
有符号模运算
有点麻烦,但我自己重载了.
C++
中unsigned int(a)
有模运算
.但人们按值区间
类型用它,而不是模运算
用它.如果命名为模整
,则比正 整
好得多.
C++
仍可启用符号溢出
检查,而D
中整操作都是模运算
的.
C++
中的模运算,问题在优化
不够.因为优化映射
到圆上比到直线上
的计算要难得多.这是D应该解决的问题.
问题在,复杂表达式中,它避免了优化.
理论上,可按单独表达式计算溢出
,并推测计算,溢出时切换
到慢速路径
,但这是高级方法
而不是系统级方法.在低级编程中,程序员希望代码映射
到机器指令
,而不会膨胀
.你希望透明映射
.
要使溢出检查真正强制
转换,需要带约束的更高级
的类型系统
,这样编译器可知道从堆中提取的整数
可有哪些值.
模算术
处理器支持数组
中高效模运算索引
,并为此提供特殊指令,dsp
也可以.
假定a[i%a.length]与a[i]
速度一样,则可按模运算
定义数组索引
.则无越界
.完美实现内存安全
正
基本上是从size_t
来.
至少在32
位系统上,内存缓冲区
在技术上可能跨越
一半以上的地址空间
.
处理内存缓冲区
的API
函数必须处理size_t
这种难题.而64
位则不存在.缓冲大小
可为整
,而不必丧失功能
.
模算术数学特性.
a+b-c
可转为a-c+b
.但如果处理潜在的整数溢出陷阱
,那么就不能安全重排
表达式.a+b
上溢?a-c
下溢?
很早前就失去了透明度.允许编译器
优化大部分表达式
.乘法和移位
替换了整除常数.函数是内联
的,循环是展开
和/或矢量化
的.
现有编程已能抓算术溢出
.
正->整
似乎是为了优化.多个整
导致模板的多实例
,这无意义.sizeof
返回的是正的整(size_t)
.
现代容器不必返回正
.
也许:重大更改并提供编译器标志来获取旧行为吗?然后是另一个用于捕获溢出
的编译器标志.
源代码或编译标志
中的提示来控制优化.
更快一致的优化代码,与性能不均匀的
代码生成之间存在很大差异.
硬件在一致
性能方面也很糟糕,如浮点值
接近零(非正规数)的计算,这在实时/音频
程序员中非常不受欢迎.
在面向高级
编程语言中一直抓溢出
.C
等落后了.
对低级编程,处理器速度和分支预测
有吸引力.
现代语言最佳解决方案可能是:改进类型系统,来证明表达式
不会溢出.默认检查正
溢出,提供内联
禁止选项.
不必优化所有路径,关键性能一般只在一小组
函数中.
D
中正=整
转换太痛苦,
auto var = arr.length
,然后做减法,突然就溢出
,在64
位,不必用正
了,C++
现在也对整大小
提供cont.ssize()
.
我不信.D
和Phobos
的设计可大量防止与内存安全
无关的错误,有时甚至以效率
代价.例如,@safe
不需要默认初化整数
,初化是不必要
的且速度较慢
.必要时覆盖默认值
.与默认检查边界
一样,这是最好的设计
.
用-fwrapv|clang -fwrapv
可提升性能.
高级模板
代码中,如果条件总是假
,如果不删它们,那么将会增加
代码大小.好的优化器
鼓励你写更高级的代码
.
这样,
if (x < x + 1) { ... }
行不?
C++20
提供std::ssize(容器)
,返回整
类型.
这样,可编写假定普通
算术且也适用于旧容器
的模板.小语言
适合重大更改.
这是内部循环
问题.与缓存一样,一旦达到了循环
中推出CPU
管道循环缓冲区的阈值,然后就很重要了.
当程序员不断受到打击时,就是个问题.
可删除许多影响小的单独优化
,但删除的每个优化都会降低竞争力
.
目前,大多数C/C++
代码库都不是在性能关键函数
中按高级方式
编写的,但编译器变得"更智能
",硬件更多样化,因而向性能代码
中更高
级别编程迈进.拥有硬件
越多样化,高质量优化
就越有价值,调整代码
成本就越高.
在D中,用模算术
,且不限制整数.会得到额外
膨胀.
在重要地方缺少优化
是有影响的.此时,我指出这在内部循环
中影响力最大,但当前C/C++
代码库往往不会在性能敏感
函数中用高级编程
.
问题在于:如果可避免,人们不想手动调整
内部循环.
如果从相同输入中获得相同的输出/响应
,那么未偏离规范.
因此,如果在整数算法上检查
溢出,则:
for(int i=1; i<99999; i++){
int x = next_monotonically_increasing_int_with_no_sideffect();
if (x < x+i){...}
}
与下面相同:
int x;
for(int i=1; i<99999; i++){
x = next_monotonically_increasing_int_with_no_sideffect();
...
}
assert(x <= maximum_integer_value - 99998);
良好语言规范应只指定可观察行为
的要求(包括内存和接口
要求).
测试假条件,如果计算可推导出x
的上个值,则可完全删除循环,仅保留最后断定.
如果性能很重要,可手动优化它.无论如何,手动优化
是获得高性能代码
的必要条件.反面是在@safe
中有未定义行为
.同样,有检查边界,在性能重要时,可关闭它.
与一般警告一样多的缺点,我们应该解决它.这些转换
可能太常见,而无法彻底弃用
它们.尽管如此,旧代码
仍会继续编译,但对新代码
,语言要明确支持
显式转换.
我们甚至不应警告
整提升.到处都是显式转换
的代码,非常难看.但可警告无符号/有符号
转换.隐式转换为相同符号的更大整数
不是反模式,可以保留他们.
它还*导致*
错误.重构代码
且类型变化时,强制转换
可能不会做预期事情
,比如意外截断
整数值.
D相对C
的进步之一(因为运行良好,很大程度上是隐藏
的)是只有在不丢失位
时,才会自动转换
整数为更小整数的值区间
传播.
实际使用中,D比C差.使用byte
和short
类型时,这是不断烦恼根源.
值区间
传播仅适用于单个表达式
,过于保守,无法在实际代码
中提供太多帮助.
int i;
byte b = i & 0xFF;
//上面byte=>ubyte
ubyte a, b, c;
a = b | c;
都编译过了.
未定义行为,C++
选择了性能
,你也可选择其他.
编译器拒绝了a=b+c
.或许该模环绕算法
?我知道b/c
可能区间,因而不会溢出
?编译器需要显式转换
,为何碍事呢.
如果,改为uint
,又可以了,不要求转为ulong
,这不一致.整提升/32位特殊
.
但如编译阶段
抓错误,加两个正字节
与正
没啥区别,都可溢出.
其他现代编程语言
可运行时抓
算术溢出.并允许在代码性能关键
部分选择退出
这些检查.
b+c
可能创建不适合正字节
的值.
因为有隐式
截断为字节
的C漏洞
.
与C
的整提升
规则一致.我们尽量做好.
运行时
抓溢出有其他问题.VRP
可以安全
的隐式转换
为字节
.
int i;
ubyte _tmp = i & 0xFF;
byte b = _tmp;
这很有趣,允许
它.
int a, b, c;
a = b + c;
这里同样,会产生不合适值,编译器接受它.
问题是不一致
.整数类型行为取决于宽度,使语言难以学习,并使通用代码为窄整数
添加特例,就像std.math.abs
中:
static if (is(immutable Num == immutable short) || is(immutable Num == immutable byte))
return x >= 0 ? x : cast(Num) -int(x);
else
return x >= 0 ? x : -x;
即使编译器
提供了比语言规范
更多保证,仍应尽可能
避免未定义行为
.
模算术没用,它使情况更糟.正确删除条件
比错误的反转
它要好.
未定义行为
并不比不想要
的定义行为更差.
我不同意.至少通过溢出,可清楚推断
正在发生
的事情.
fun(aLongArray[x]);
x *= 0x10000;
如果数组够长,按你提倡编译器的语义
可能会:
x
不能溢出,所以乘前,最多为0x7FFF
.
我知道aLongArr
更长,所以可省略检查边界.
与上面
相比,溢出
问题要小得多.
我主张捕捉溢出
,除非明确禁用它.还主张同时拥有模运算符
和钳(紧固)运算符
抓溢出,类似GCC
的-ftrapv
,整溢出
会崩溃.
不能在@safe
代码中允许未定义行为
,@safe
中不能有溢出
时未定义
行为.
断言未溢出就可以了.用-release
开关,与c++
的int(整,非正)
类似.但反面不是.下面是可行的:
import core.checkedint;
bool check;
auto x = mulu(a,b,check);
assert(!check);
不确定编译器
是否会在发布
模式下利用溢出
是未定义行为
.
@safe
代码应允许未定义行为
.让它实现定义.要求代码
保证内存安全
.
可由语言标准
留给编译器,但编译器仍强加通用内存安全
要求.
我用-O3
测试了溢出,它并未删除"边界检查
".因此,编译器可内存安全
的调整优化
.
实现定义的解决方案,存在任何更改都可能破坏内存安全
的问题.
其他一些函数
的内存安全性
可能取决于具有溢出整数
的@safe
函数的正确行为.
你的意思是@信任
代码,但要更具体.它实际上是溢出,包装也可以这样说.可能是@信任
代码未考虑负数
.
如果计算x时溢出
,则限制x为位宽
,而不是任意
位,是有意义的.需要时,可进一步
限制它.
当然,这仅与禁用捕获溢出
的@safe
代码相关.
实现定义
即供应商必须记录语义.
"未定义行为"即供应商不需要/但要鼓励
记录行为.这是在解决硬件
有未定义行为
时,C语言规范
中引入的.C++
编译器之间竞争使他们利用这一点来搞最硬核优化.
这并不难,大约两三句话.只要理解二进制补码
算术.
必须努力理解二进制补码
.一些崇高的尝试:
Java
:禁止所有无符号
类型.最终不得不
按黑客方式将重新添加它.
Python
:数字可以增长且不损失精度
.但是,代码变慢.
Javascript
:一切都是双精
浮点值!导致各种问题.浮点更难.
添加abs(short)
和abs(byte)
是个巨大
错误.这些函数不在C语言
中是有道理的.
试图隐藏计算机整数运算
及整提升
的工作原理,会导致无尽的失望
和不可避免的失败
.
不,VRP
会发出错误.
int i;
byte b = i.to!byte;
i = -129;
b = i.to!byte; // std.conv.ConvOverflowException
安全溢出,应该比下面的好
byte b = cast(byte)i;
运行时检查
溢出来抓漏洞
.好的优化编译器
在i
区间大致值已知道时,可消除漏洞
.如:
void foobar(byte[] a)
{
foreach (i ; 0 .. a.length)
a[i] = (i % 37).to!byte;
}
命令:
$ gdc-12.0.1 -O3 -fno-weak-templates -c test.d && objdump -d test.o
乘法和移位
代替了慢除法
,条件分支仅用于比较i与数组长度
..to!byte
部分无成本,通过mov %al,(%rsi,%rcx,1)
指令直接把字节写入目标数组
.
理解二进制
补码很烦,有摩擦.
D
还可改进:
使64
位整数(非正)
"默认
"地全面检查
.
对用内部"假设"指令
来用限制信息
提供编译器的受限整数
提供库类型.此类型将选择合适
的限制
整数的存储类型
.
高速要求
时,加些干净语法
来禁用运行时检查
.
如此,加ARC
加上本地GC
,可有竞争力.
D应改进高级
编程,及从高级到系统级
的转换能力.
细节主要用于非常低级
的技巧
和易出错
的位操作
.有个好标准库
,这不应是经常需要的.此外,由于SIMD
的可用性,我发现位技巧
不实用.在SIMD
前,我有时会用正位技巧
来模拟SIMD
(用于处理图像),但这是神秘
的.我只想在创建高精度相量
(振荡器)或按位向量对待浮点数
的极少数情况下这样.大多数程序员不需要这些知识,他们只需要个好库.
无论如何,要求类似C
的熟练程度是糟糕的策略,因为这使D程序员
更容易过渡到C++
!
D需要朝着简单
的方向发展,这是它相对于C++
和Rust
可以获得的主要优势
.
不,你可以丢弃它
struct Thing {
short a;
}
// 不同.
Thing calculate(int a, int b) {
return Thing(a + b);
}
当前规则要求,在构造函数调用
中显式转换.然后,稍后,重构Thing
为int
.它仍会编译,仍然
存在显式转换,现在砍掉位
.
显式转换问题,一旦写入它们,很难撤销
.cast
代表有问题.
short a;
short b = a + 1;
你可能就需要一个了.是的,可能会截断进位.
另一方面,如果在某些类型的通用代码
中存在整数
,则可能会丢失
精度.
合理折衷是允许隐式转换
输入的最大类型.在字面
上可应用VRP
.即:
short a;
short b = a + 1;
时,检查输入
a = type short
1 = VRP下转为`字节/极(甚至)`.
最大类型?短.所以可隐式转换
为short
.然后跑VRP
来进一步变小它:
byte c=(a&0x7e)+1;//好的,VRP可知道它仍适合那里,所以它变得更小了.
但由于最大的原始输入
适合"short"
,即使可能会丢失
一个进位,它允许输出
变为"short"
.
另一方面:
ushort b = a + 65535 + 3;
不,编译器可常折叠
该字面,VRP
根据其值调整大小
为"int"
,因此需要显式转换
来确保不会丢失*实际*输入
精度.
short a;
short b;
short c = a * b;
我会允许的.输入是a和b
,它们都是短,所以让输出也隐式截断回
短.就像int
一样,是的,乘法产生一个高字
,但它可能不适合,我不希望编译器烦我.
折衷方案会平衡合法
的安全问题与意外丢失或重构更改
(如重构为整数
,现在输入类型
增长,并且编译器可再次发出错误
)与几乎无处不在
的烦人转换
.
移除大部分转换
,使剩下转换
更加突出,它们是潜在的问题
.
d
干C
的整提升
和转换
的成功一样,零惊奇.
但也有区别.
C可隐式转换为更短的整数,D
没有,翻译时必须用cast()
.
但此时,不知道强制转换类型
的D代码
是否比C代码
更脆弱,因为更改类型时,C和D
都收不到警告.
因此,此时检测整数问题
的最佳方法是:
1
.VRP
不转换.
2
.公平
:D
带强制转换
,或C带隐式强制转换
.
可"克服"
D整数会很好,没有一劳永逸
,今天人们仍然一直从C转换为D
.这是目前状态,兼容C语义
很有用.
甚至C++
也不断引入新基本类型
,以便为该语言的每个新版本
提供更好的类型安全
.例如,在C++
中std::byte
不是算术类型.
C是在PDP-11
上开发的,并且由于-11
指令工作方式,而产生了整提升
规则.float=>double
提升规则同样如此.
多少人实际
使用(并且需要
)正
?如果99%
的用户不需要它们,归类为库类型
就很好.
因为C是在-11
上开发的.这已经延续
到现代CPU
中,考虑:
void tests(short* a, short* b, short* c) { *c = *a * *b; }
0F B7 07 movzx EAX,word ptr [RDI]
66 0F AF 06 imul AX,[RSI]
66 89 02 mov [RDX],AX
C3 ret
void testi(int* a, int* b, int* c) { *c = *a * *b; }
8B 07 mov EAX,[RDI]
0F AF 06 imul EAX,[RSI]
89 02 mov [RDX],EAX
C3 ret
你为使用短
算术而不是整
算术支付了3字节
.它也比较慢.
一般,int
应用于大多数计算,short
和byte
用于存储.
(现代CPU
长期以来一直刻意优化和调整C语义
.)
现代机器*肯定*
了解C的工作原理
.
正
在只期望正值的API
中非常有用,标记为uint
表明预期,处理位掩码时,也很有用.对系统编程语言,也很重要.更反映现实
.
需要库类型来操作位掩码
会使D成为系统编程语言
的一个完全笑话
.
因而,尽量用整
.
用户并不关心编译器
如何执行指令.他们关心结果,如果赋值为字节
,他们可能不在乎失去额外精度
,不然,为何不用整
?
不再,除
在小整
上可能较快.
1
.你非常小心地演示了短算术
,而不是与x86
上的整
算术大小相同的字节
算术.
2
.循环
计数(或字节
计数)不是明智的语言设计
方法.可能与语言实现
有关;整个程序性能可能与语言设计
有关;但变化是微不足道的,不应
妨碍正确的语义.
3
.你代码示例完全按你建议一样,用短算术
存储.此时,用短
算术而不是整
算术,会产生相同结果及更小代码.
4
.(上接3
)在更大,更有趣
表达式中,不管语言语义
,编译器一般都会自由地使用整
作为临时变量.
较大代码大小
肯定会给指令缓存
带来更大压力,但减速不大.利用SIMD
指令的自动向量
代码看起来有点不同.
处理大型数组
时,性能确实提高了
很多.并且16
位版本大约比32
位版本快两倍
(因为每个128
位XMM
寄存器代表8个短
或4个整
数).
如果希望D语言对SIMD
友好,那么可以鼓励局部变量
使用short
和byte
.
int a = int.max;
long b = a + 1;
writeln(b > 0); // 假
是的,我希望编译器确定是否需要
溢出,并基于此生成
适当指令.
对于int->long
不经常出现原因是:因为a)
不常转换int
到long
,且b)
很少见整溢出
.
正如我之前观察到的,没有解决
方案.这是不同
问题.最好坚持使用已充分理解
问题,并且最适合常见CPU
架构的机制
.
代码大小的损失仍然存在
字节算术
的代价是缺少
寄存器.通用方案中,短与字节
没啥区别.
如果客户期望有性能
系统编程语言,就不行了.
还记得最近处理x87
,dmd
保持额外精度,来避免双精圆整
问题?我传播给dmc
,它让我赢得了设计胜利.客户在"浮点"
算术上,基准测试,并宣布dmc
慢了10%
.双精圆整
问题,他不感兴趣.
加载指令
仍用额外操作数大小
来覆盖字节.
加载短
,会产生额外字节
.
:不管语义,编译器
用整
作临时.
根据优化表达式方式,你会得到其他截断
问题.x87
则更慢.
我在这呆了40年了,没有神奇的解决方式
,整提升
是最实用的解决方案.最好花些时间学习它们
,会没事的.
SIMD
是它自己的世界,为什么D将向量类型
作为核心语言特性?我不相信自动矢量化
.
有趣的是,当不可用有符号除法
指令时,除有符号
是:保存
操作数的符号,取反为无符号
,除无符号
,再取反.
无符号
操作是CPU
工作方式的核心,有符号
依赖它.
重申:
C的规则:整提升
,允许隐式向小
转换.
D的规则:整提升
,除非VRP
通过,否则禁止隐式向小转换.
我提出规则:整提升
,除非VRP
通过或请求转换与最大输入类型
相同(除非值明显
越界,排除字面
).禁止隐式向小
转换.
实际计算不变.只是放宽D当前严格的隐式转换
规则到更接近C许可
标准.
codegen
基本不变.中间值不变.类似C
,但仅允许隐式转换回输入
.
64
位上,字节
与字寄存器
一样多.(从技术上讲,但应不惜一切代价避免使用高半
寄存器.)
如果客户想要生成整,则生成整
.
如何不用操作数大小覆盖前缀
存储短?
我指的是乘法
.可加载第二个
寄存器,执行32
位乘法,然后存储截断结果
.在不同环境,这可能是值得的.
ubyte x,y,z,w; w = x + y + z.
(((x+y)%2^32%2^8)+z)%2^32%2^8
//上下是一样的.
(((x+y)%2^32)+z)%2^32%2^8
2^32
在32
位寄存器上隐式用的.2^8
是隐式截断.
前者,有两个显式截断
,可重写为后者,去掉中间截断
,得到与提升
完全相同结果.
访问这些字节寄存器
需要额外的REX
字节.
他们需要为子表达式
插入强制转换到整
中.这不好.
考虑比加载和存储
更复杂的表达式.
考虑:
byte a, b;
int d = a + b;
你的提议
会得到令人惊讶
的结果.
其他提议
都没有更好的理解.
我们考虑了这一点并选择不那样,理由是我们试图尽量减少
不可见的截断.
另外,作为务实
的程序员,除了在数据结构
中节省些空间
之外,我发现短
几乎无用.用短
代表有问题.
作为具有手动编码汇编优化
经验且熟悉SIMD
编译器内在函数的务实程序员
,在C代码中用短
作为临时代码
实际上非常适合于原型设计/测试单个16位通道的行为
.作为奖励,编译器中自动向量化
器也可能会有所收获.但是大量的强制类型转换
是有问题的.
我也不太信任自动矢量化
质量,但该功能是GCC
和LLVM
后端免费提供的.现在,过度偏执字节/短
变量错误,会迫使用户
选择以下两种没有吸引力
的选项:1,用丑陋的转换
减小代码,2更改临时变量
类型为整数
,并浪费一些向量化
机会.
当信噪比
不好时,用户很自然就开始忽略错误消息
.初学者经过有效培训
,可应用强制转换
,而不会思考关闭烦人
的编译器,导致这样
VRP
只是创可贴,帮助不大,并会带来很多不便
.
我建议:
实现与Rust
类似的wrapping_add,wrapping_sub,wrapping_mul
内置函数,这很容易且无成本.
在其中一个D编译器(很可能是GDC
或LDC
)中实现实验-ftrapv
选项,以在运行时
捕获有符号和无符号溢出
.或者,可添加函数属性
来更细粒度
控制该功能.是的,我知道这违反了当前的需要
二进制补码解决所有
的的D语言规范
,但对花哨的实验选项
来说并不重要.
用-ftrapv
运行些测试并在Phobos
中检查实际触发
了多少算术溢出
.如果包装行为
是预期的,则用内置
函数替换受影响
的算术运算符.
从长远来看,请考虑更新语言规范
.
好处:即使-ftrapv
开销很大,也是在应用中测试算术溢出安全性
的有用工具.有总比没有好
.
我知道D是如何工作的.我知道为什么.见鬼,是我在dmd
中实现了部分VRP
代码,并向许多新用户解释了它.它实际上作用不大.
强制显式转换
很少能防止真正的错误
,代价是,使语言更难使用,并在未来产生了自己的问题.
放宽规则会减少大量误报
的负担,强制有害
转换,且保持精神规则
.它不仅是不可见的截断,它还是有漏洞的
不可见的截断.
良好代码会用检查漏洞的窄转换
,但类型本身无趣,返回类型
上重载会更好.但如果检查溢出,最好是默认
.
byte x = narrow(expression);
//如果是默认,如下取消检查
byte x = uncheck(expression);
我每天都用D
,我没遇见问题,我在src/dmd/*.d
中:
grep -w cast *.d
未发现你提到的强制转换
类别的short/ushort
转换.当然,也许编码风格不同.
在phobos/std/*.d
上同样查找,我写得很少,强制转换为short/ushort
的实例为零.
“很少”,这类错误
确实很少见但也很重要.这正是我们想要抓
的东西.
通常应用向量
类型,而不是依赖自动向量化
.自动矢量化问题之一是,一些小改动可能会意外
的阻止矢量化.
当然,允许隐式转换整数
为短是*方便*
.但你就没有安全的整数数学
了.
正如我反复提到的,没有快速,方便且不隐藏错误
的解决方案.
写个dip
吧.
有趣的是,字节
版代码更小.
0000000000000000 <_D4main5testbFPhQcQeZv>:
0: 8a 02 mov (%rdx),%al
2: 41 f6 20 mulb (%r8)
5: 88 01 mov %al,(%rcx)
7: c3 ret
0000000000000000 <_D4main5testiFPiQcQeZv>:
0: 8b 02 mov (%rdx),%eax
2: 41 0f af 00 imul (%r8),%eax
6: 89 01 mov %eax,(%rcx)
8: c3 ret
此外,ARM64
的大小没有区别:
testb:
ldrb w0, [x0]
ldrb w1, [x1]
mul w0, w0, w1
strb w0, [x2]
ret
tests:
ldrh w0, [x0]
ldrh w1, [x1]
mul w0, w0, w1
strh w0, [x2]
ret
testi:
ldr w0, [x0]
ldr w1, [x1]
mul w0, w0, w1
str w0, [x2]
ret
我不认为成为库类型
是耻辱.根据语言的不同,它们可能与内置类型
一样有用且方便.本线程中提到了C++
的std::byte
,它就是库类型.
感谢支持,GDC
已在C/C++
语义上支持-ftrapv
选项(捕捉有符号溢出
,无符号溢出
,小于int
由于整体提升而在监控下的类型).现在我需要一些试验,检查如何与Phobos
和其他D代码
交互.修补GCC
源代码来测试是否可抓无符号溢出
也会很有趣.
但总之,这很有前途.它可保护一些32
位和64
位计算的算术溢出
错误.在大型复杂
软件中解决此类算术溢出
问题,是我对D语言
的主要关注点
之一.
尽管会降低速度,DMD
现在用x87
圆整浮计算.
如果CPU
上有SIMD
浮点指令,与其他编译器一样,则用它而不是x87
.
浮字面应圆整至他们的精度.
除了C和C++
标准外,还有IEEE754
和通用实践,特别是32/64
位IEEE754
.编译器至少用一组合适
的标志,实现了多个标准
,并明确记录每组标志的保证
.
//linux中
void main(){
import std.stdio;
assert(42*6==252);
//常折叠,用扩展精度,由于双精圆整,整体结果不太准
assert(cast(int)(4.2*60)==251);
//无常折叠,用双精精度,更准确
double x=4.2;
assert(cast(int)(x*60)==252);
}
4.2
和60
是命名常量
,结果为251
或252
不重要,程序可正常工作,但是,由于结果有时是251
,有时是252
,导致难以追踪且不一致
.在编译完全相同的表达式
时,我甚至在Windows
与linux
结果不同.虽然这是LDC
,但不确定DMD
也有该问题.
注意,这是最近才发生的,因而我强烈反对"增强"
精度.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现