[英]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.cpp
的mscorlib.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
参数是否将对术语或源元素或目标元素中的总字节进行计数。 解决方案可能包括:
count
属性,以便src和dst每个都有一个; (没有...) count
-并记录选择; (没有...) count
,因为元素大小“ 1”是(唯一的)公分母,适用于任意不同大小的元素类型。 (是) 由于(1.)很复杂(可能会增加更多的混乱),并且(2.)的任意对称破缺很难记录,因此此处选择的是(3.),这意味着必须始终指定count
参数以字节为单位 。
因为srcOffset
和dstOffset
参数的情况并不那么关键(由于每个“偏移”都有独立的参数,因此每个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
srcOffset
和dstOffset
是字节偏移量这一事实导致在本页上讨论的奇怪情况。 一方面,它需要第一个和/或最后一个元素的副本可以是部分副本:
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
的单个调用可以部署以下五个操作模式中的任何一个:{ nop , 1-to- 1,1-to-many , 多对1 , 多对多 }。
该代码还说明了Buffer.BlockCopy
接受大小不同的元素的数组是如何牵涉到字节序的 。 确实,修复后的示例似乎需要代码现在固有地包含了(正确性)对运行它的CPU的字节顺序的依赖性。 将这一点与现实用例显得稀少或晦涩的事实结合起来,尤其要记住上面讨论的非常真实且容易出错的部分复制危害。
考虑到这些要点将建议避免在API允许的情况下,在一次对Buffer.BlockCopy
调用中混合使用源/目标元素大小的技术。 无论如何,都应谨慎使用混合尺寸的元件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.