C#中的64位指针算术,检查算术溢出是否改变行为

如何解决C#中的64位指针算术,检查算术溢出是否改变行为

|| 我有一些不安全的C#代码,它在64位计算机上运行的类型为“ 0”的大型内存块上执行指针算术运算。它在大多数时候都能正常工作,但是当事情变大时,我经常会遇到指针不正确的损坏。 奇怪的是,如果我打开“检查算术上溢/下溢”,则一切正常。我没有任何溢出异常。但是由于性能下降,我需要在不使用此选项的情况下运行代码。 是什么造成这种行为差异?     

解决方法

        这是C#编译器错误(在Connect上提交)。 @Grant表明,由C#编译器生成的MSIL将
uint
操作数解释为带符号。根据C#规范,这是错误的,这是相关部分(18.5.6):   18.5.6指针算法      在不安全的上下文中,可以将
+
-
运算符(第7.8.4节和第7.8.5节)应用于除
void*
之外的所有指针类型的值。因此,对于每种指针类型“ 5”,都隐式定义了以下运算符:
T* operator +(T* x,int y);
T* operator +(T* x,uint y);
T* operator +(T* x,long y);
T* operator +(T* x,ulong y);
T* operator +(int x,T* y);
T* operator +(uint x,T* y);
T* operator +(long x,T* y);
T* operator +(ulong x,T* y);
T* operator –(T* x,int y);
T* operator –(T* x,uint y);
T* operator –(T* x,long y);
T* operator –(T* x,ulong y);
long operator –(T* x,T* y);
     给定指针类型为'5'的表达式
P
和类型为
int
uint
long
ulong
的表达式
N
,表达式
P + N
N + P
计算类型
T*
的指针值,该值是将
N * sizeof(T)
加到
P
给出的地址而得到的。同样,表达式“ 19”计算类型“ 5”的指针值,该值是从“ 7”给出的地址中减去“ 17”而得到的。      给定指针类型为“ 5”的两个表达式expression7ѭ和
Q
,表达式
P – Q
计算
P
Q
给出的地址之间的差,然后将该差除以ѭ29then。结果的类型始终为
long
。实际上,
P - Q
被计算为
((long)(P) - (long)(Q)) / sizeof(T)
。      如果指针算术操作溢出了指针类型的域,则结果将以实现定义的方式被截断,但是不会产生异常。 您可以在指针上加上ѭ1,不会进行任何隐式转换。并且该操作不会溢出指针类型的域。因此不允许截断。     ,这里选中和未选中之间的区别实际上是IL中的一个错误,或者仅仅是一些错误的源代码(我不是语言专家,所以我不会评论C#编译器是否为含糊的源代码)。我使用C#编译器的4.0.30319.1版本编译了此测试代码(尽管2.0版本似乎做同样的事情)。我使用的命令行选项为:/ o + /不安全/ debug:pdbonly。 对于未选中的块,我们具有以下IL代码:
//000008:     unchecked
//000009:     {
//000010:         Console.WriteLine(\"{0:x}\",(long)(testPtr + offset));
  IL_000a:  ldstr      \"{0:x}\"
  IL_000f:  ldloc.0
  IL_0010:  ldloc.1
  IL_0011:  add
  IL_0012:  conv.u8
  IL_0013:  box        [mscorlib]System.Int64
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(string,object)
在IL偏移量11处,加法运算得到2个操作数,一个为byte *类型,另一个为uint32类型。根据CLI规范,它们实际上分别被标准化为本地int和int32。根据CLI规范(准确地说是分区III),结果将是native int。因此,必须将secodn操作数提升为native int类型。根据规范,这是通过符号扩展来完成的。因此,uint.MaxValue(在0xFFFFFFFF或-1中为带符号表示法)的符号扩展为0xFFFFFFFFFFFFFFFFFF。然后将2个操作数相加(0x0000000008000000L +(-1L)= 0x0000000007FFFFFFL)。 conv操作码仅出于验证目的而需要,以将本机int转换为int64,在生成的代码中为nop。 现在对于检查的块,我们有以下IL:
//000012:     checked
//000013:     {
//000014:         Console.WriteLine(\"{0:x}\",(long)(testPtr + offset));
  IL_001d:  ldstr      \"{0:x}\"
  IL_0022:  ldloc.0
  IL_0023:  ldloc.1
  IL_0024:  add.ovf.un
  IL_0025:  conv.ovf.i8.un
  IL_0026:  box        [mscorlib]System.Int64
  IL_002b:  call       void [mscorlib]System.Console::WriteLine(string,object)
除了add和conv操作码外,它实际上是相同的。对于添加操作码,我们添加了2个“后缀”。第一个是“ .ovf”后缀,其后缀具有明显的含义:检查溢出,但是还需要“启用”第二个后缀:“。un”。 (即,没有\“ add.un \”,只有\“ add.ovf.un \”)。 \“。un \”具有2种效果。最明显的是,累加和溢出检查都像操作数是无符号整数一样进行。从我们的CS类开始,希望大家都记得,由于二进制补码的双重编码,有符号加法和无符号加法是相同的,所以“ .un”实际上只影响溢出检查,对吗? 错误。 请记住,在IL堆栈上,我们没有2个64位数字,我们有一个int32和一个本机int(在归一化之后)。好吧,“。un”意味着从int32到native的转换被视为“ conv.u \”,而不是像上面的默认“ conv.i \”。因此uint.MaxValue为零,扩展为0x00000000FFFFFFFFL。然后加法正确产生0x0000000107FFFFFFL。转换操作码可确保将无符号操作数表示为有符号的int64(可以)。 您的修复程序仅适用于64位。在IL级别,一个更正确的解决方案是将uint32操作数显式转换为native int或unsigned native int,然后32位和64位的check和unchecked都具有相同的表现。     ,        请仔细检查您的不安全代码。在分配的内存块之外读取或写入内存会导致“损坏”。     ,        我已经解决了这个问题,所以正在回答自己的问题,但是仍然有兴趣阅读有关为什么行为随着ѭ36和ѭ37发生变化的评论。 此代码演示了问题以及解决方法(在添加之前始终将偏移量转换为
long
):
public static unsafe void Main(string[] args)
{
    // Dummy pointer,never dereferenced
    byte* testPtr = (byte*)0x00000008000000L;

    uint offset = uint.MaxValue;

    unchecked
    {
        Console.WriteLine(\"{0:x}\",(long)(testPtr + offset));
    }

    checked
    {
        Console.WriteLine(\"{0:x}\",(long)(testPtr + offset));
    }

    unchecked
    {
        Console.WriteLine(\"{0:x}\",(long)(testPtr + (long)offset));
    }

    checked
    {
        Console.WriteLine(\"{0:x}\",(long)(testPtr + (long)offset));
    }
}
这将返回(在64位计算机上运行时):
7ffffff
107ffffff
107ffffff
107ffffff
(顺便说一句,在我的项目中,我首先将所有代码编写为托管代码,而没有所有这种不安全的指针算术麻烦,但发现它使用了过多的内存。这只是一个爱好项目;如果被炸毁,唯一会受到伤害的是我。)     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-