簡體   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