繁体   English   中英

使用Buffer.BlockCopy C#奇怪的数据损坏

[英]Strange data corruption with Buffer.BlockCopy C#

背景

我正在编写一个C#程序,该程序通过数据采集来收集一些信息。 它非常复杂,因此我在这里不做详细介绍,但是数据采集是在不断进行的,然后在异步线程上,我的程序定期访问采集缓冲区并从中获取100个样本。 然后,我在100个样本中查找感兴趣的触发条件。如果看到触发条件,则从前触发缓冲区中收集一堆样本,从后触发缓冲区中收集一堆样本,然后组装所有这些样本一起组成一个200个元素的数组

在我的异步线程中,我使用Buffer.BlockCopy方法组装了200个元素的数组(类型为double)。 我选择使用此方法的唯一特定原因是,我需要注意异步线程中要进行多少数据处理。 如果我做的太多,最终可能会导致获取缓冲区过满,因为我没有经常访问它。 由于Buffer.BlockCopy在将数据从源数组推送到目标数组方面比大的“ for循环”要有效得多,所以这就是我决定使用它的唯一原因。

问题

当我调用Buffer.BlockCopy方法时,我这样做:

Buffer.BlockCopy(newData, 0, myPulse, numSamplesfromPreTrigBuf, (trigLocation * sizeof(double));

哪里;

newData是一个double []数组,其中包含新数据(100个元素)(典型数据,如0.0034、6.4342等,范围从0到7)。

myPulse是目标数组。 用200个元素实例化。

numSamplesfromPreTrigBuf是我要在副本的此特定实例中应用的偏移量

trigLocation是我要在此特定实例中复制的元素数。

复制发生时没有错误,但是写入myPulse的数据已被破坏; 数字,例如-2.05E-289和5.72E + 250。 不管是微小的数字还是大量的数字。 这些数字不会出现在我的源数组中。

我已经简单地通过使用Array.Copy()解决了这个问题,除了无需通过乘以sizeof(double)来计算要复制的元素数之外,没有其他源代码修改。 但是我确实花了两个小时尝试调试Buffer.BlockCopy()方法,但完全不知道为什么副本是垃圾。

从我对Buffer.BlockCopy的示例用法(我认为这是正确的用法)中,会有任何人有一个主意,如何跨垃圾数据复制?

我认为您的偏移量是错误的-这也是一个字节偏移量,因此您需要将其乘以sizeof(double) ,就像长度一样。

使用BlockCopy和类似方法时要小心-您会失去.NET的一些安全性。 与完全unsafe方法不同,它确实检查数组范围,但是您仍然可以产生一些非常奇怪的结果( 并且我假设您可能会产生无效的引用-一个大问题 EDIT :幸运的是, BlockCopy仅适用于原始类型的数组)。

另外, BlockCopy是线程安全的,因此,如果一次要从多个线程访问共享缓冲区,则希望同步对共享缓冲区的访问。

实际上,只要每个元素类型都是原始类型, Buffer.BlockCopy允许源Array和目标Array具有不同的元素类型。 无论哪种方式,如您从../vm/comutilnative.cppmscorlib.dll源代码中所../vm/comutilnative.cpp ,该副本只是一种直接映像操作,它永远不会以任何方式(例如,将其作为“逻辑”或“数字”)。 它基本上被称为C语言经典之作memmove 所以不要期望这样:

var rgb = new byte[] { 1, 2, 3 };
var rgl = new long[3];

Buffer.BlockCopy(rgb, 0, rgl, 0, 3); // likely ERROR: integers never widened or narrowed

// INTENTION?: rgl = { 1, 2, 3 }
// RESULT:     rgl = { 0x0000000000030201, 0, 0 }

现在, Buffer.BlockCopy仅接受一个count参数,允许大小不同的元素类型就引入了基本的语义歧义,即该count参数是否将对术语或源元素或目标元素中的总字节进行计数。 解决方案可能包括:

  1. 添加第二个count属性,以便srcdst每个都有一个; (没有...)
  2. 任意选择srcdst来表示count -并记录选择; (没有...)
  3. 始终以字节为单位表示count ,因为元素大小“ 1”是(唯一的)公分母,适用于任意不同大小的元素类型。 (是)

由于(1.)很复杂(可能会增加更多的混乱),并且(2.)的任意对称破缺很难记录,因此此处选择的是(3.),这意味着必须始终指定count参数以字节为单位

因为srcOffsetdstOffset参数的情况并不那么关键(由于每个“偏移”都有独立的参数,因此每个c̲o̲u̲l̲d̲都相对于其各自的Array进行了索引;扰流器警报:...不是) ,但很少有人提到, 这些参数总是以bytes表示 从文档(添加重点):

Buffer.BlockCopy
https://docs.microsoft.com/en-us/dotnet/api/system.buffer.blockcopy

  参数\n   src Array源缓冲区。\n   into src. srcOffset Int32 src中从零开始的\n   dst Array目标缓冲区。\n   into dst. dstOffset Int32从dst开始的从零开始的\n   count Int32要复制的字节数。\n

srcOffsetdstOffset字节偏移量这一事实导致在本页上讨论的奇怪情况。 一方面,它需要第一个和/或最后一个元素的副本可以是部分副本:

var rgb = new byte[] { 0xFF, 1, 2, 3, 4, 5, 6, 7, 8, 0xFF };
var rgl = new long[10];

Buffer.BlockCopy(rgb, 1, rgl, 1, 8);    // likely ERROR: does not target rgl[1], but
                                        // rather *parts of* both rgl[0] and rgl[1]

// INTENTION? (see above)     rgl = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 0L } ✘
// INTENTION? (little-endian) rgl = { 0L, 0x0807060504030201L, 0L, ... }       ✘        
// INTENTION? (big-endian)    rgl = { 0L, 0x0102030405060708L, 0L, ... }       ✘
// ACTUAL RESULT:             rgl = { 0x0706050403020100L, 8L, 0L, 0L, ... }   ?
//                                                    ^^-- this byte not copied (see text)

在这里,我们看到的不是代替(也许)将某些内容复制到索引 1的元素rgl[1] ,然后(也许)从那里继续,而是复制了第一个元素rgl[0]目标字节偏移量 1,并导致该元素的部分复制(当然是意外复制)。 具体来说,不会复制rgl[0]字节0(little-endian long最低有效字节)。

继续该示例,索引1的long值未完全写入,这一次将值“ 8”存储到其最低有效字节中,特别是不影响其其他(高)7个字节。

因为我没有手艺我的例子不够好,明确地表现出来,让我讲清楚这最后一点:这些部分复制的long值, 复制的部分不归零的可能通常从一个适当的预期long存储一个byte值。 因此,对于Buffer.BlockCopy的讨论,“部分复制”是指任何多字节原始值(例如, long )值的未复制字节在操作前均保持不变,从而“合并”为新值以某种依赖于字节序的方式(因此可能(并希望)是无意的)。

要“修复”示例代码,必须为每个Array提供的偏移量预乘以其各自的元素大小,才能将其转换为字节偏移量。 这会将上面的代码Buffer.​BlockCopy更正''为唯一明智的操作Buffer.​BlockCopy在这里可能会合理地执行,即(一个或多个)源和(一个或多个)目标元素之间的小尾数复制,请注意确保没有根据其大小,部分复制或部分复制不完整。

Buffer.BlockCopy(rgb, 1 * sizeof(byte), rgl, 1 * sizeof(long), 8); // CORRECTED (?)

// CORRECT RESULT:            rgl = { 0L, 0x0807060504030201L, 0L, ... }       ✔

// repaired code shows a proper little-endian store of eight consecutive bytes from a
// byte[] into exactly one complete element of a long[].

在固定示例中,将8个完整的byte元素复制到1个完整的long元素。 为简单起见,这是“多对一”的副本,但是您也可以想象更复杂的场景(未显示)。 实际上,对于从源到目标的元素计数,对Buffer.BlockCopy的单个调用可以部署以下五个操作模式中的任何一个:{ nop1-to- 1,1-to-many多对1多对多 }。

该代码还说明了Buffer.BlockCopy接受大小不同的元素的数组是如何牵涉到字节序的 确实,修复后的示例似乎需要代码现在固有地包含了(正确性)对运行它的CPU的字节顺序的依赖性。 将这一点与现实用例显得稀少或晦涩的事实结合起来,尤其要记住上面讨论的非常真实且容易出错的部分复制危害。

考虑到这些要点将建议避免在API允许的情况下,在一次对Buffer.BlockCopy调用中混合使用源/目标元素大小的技术。 无论如何,都应谨慎使用混合尺寸的元件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM