[英]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.