簡體   English   中英

將字節數組轉換為POD

[英]Cast array of bytes to POD

比方說,我有一組無符號字符代表一堆POD對象(例如從套接字或通過mmap讀取)。 它們代表哪些類型以及在運行時確定的位置,但我們假設每個類型已經正確對齊。

將這些字節“轉換”為相應的POD類型的最佳方法是什么?

解決方案應該符合c ++標准(比方說> = c ++ 11)或者至少可以保證使用g ++> = 4.9,clang ++> = 3.5和MSVC> = 2015U3。 編輯:在Linux,Windows上運行x86 / x64或32/64位臂。

理想情況下,我想做這樣的事情:

uint8_t buffer[100]; //filled e.g. from network

switch(buffer[0]) {
    case 0: process(*reinterpret_cast<Pod1*>(&buffer[4]); break;
    case 1: process(*reinterpret_cast<Pod2*>(&buffer[8+buffer[1]*4]); break;
    //...
}

要么

switch(buffer[0]) {
    case 0: {
         auto* ptr = new(&buffer[4]) Pod1; 
         process(*ptr); 
    }break;
    case 1: {
         auto* ptr = new(&buffer[8+buffer[1]*4]) Pod2; 
         process(*ptr); 
    }break;
    //...
}

兩者似乎都有效,但兩者都是c ++中的AFAIK未定義行為1) 而且只是為了完整性:我知道將通常的東西復制到適當的局部變量中的“通常”解決方案:

 Pod1 tmp;
 std::copy_n(&buffer[4],sizeof(tmp), reinterpret_cast<uint8_t*>(&tmp));             
 process(tmp); 

在某些情況下,它可能不是其他人的開銷,在某些情況下甚至可能更快,但性能除外,我不再能夠例如修改數據並且說實話:它讓我很生氣,知道我有右位在內存中的適當位置,但我不能使用它們。


我想出的一個有點瘋狂的解決方案是:

template<class T>
T* inplace_cast(uint8_t* data) {
    //checks omitted for brevity
    T tmp;
    std::memmove((uint8_t*)&tmp, data, sizeof(tmp));
    auto ptr = new(data) T;
    std::memmove(ptr, (uint8_t*)&tmp,  sizeof(tmp));
    return ptr;

}

g ++和clang ++似乎能夠優化掉那些副本,但我認為這會給優化器帶來很多負擔,並可能導致其他優化失敗,不能用於const uint8_t* (盡管我不想實際修改它只是看起來很可怕(不要以為你會得到過去的代碼審查)。


1)第一個是UB,因為它打破了嚴格的別名,第二個可能是UB( 這里討論過 ),因為標准只是說生成的對象沒有初始化並且具有不確定的值(而不是保證底層內存不受影響) 。 我相信第一個等效的c代碼是明確定義的,因此編譯器可能允許這與c-header的兼容性,但我不確定這一點。

最正確的方法是創建所需POD類的(臨時)變量,並使用memcpy()將數據從緩沖區復制到該變量中:

switch(buffer[0]) {
    case 0: {
        Pod1 var;
        std::memcpy(&var, &buffer[4], sizeof var);
        process(var);
        break;
    }
    case 1: {
        Pod2 var;
        std::memcpy(&var, &buffer[8 + buffer[1] * 4], sizeof var);
        process(var);
        break;
    }
    //...
}

執行此操作的主要原因是對齊問題:緩沖區中的數據可能無法正確對齊您正在使用的POD類型。 制作副本可以消除這個問題。 即使網絡緩沖區不再可用,它也允許您繼續使用變量。

只有當您完全確定數據已正確對齊時,才能使用您提供的第一個解決方案。

(如果您正在從網絡讀取數據,則應始終首先檢查數據是否有效,並且不會在緩沖區外讀取。例如,使用&buffer[8 + buffer[1] * 4] ,應檢查該地址的開頭加上Pod2的大小是否超過緩沖區長度。幸運的是你正在使用uint8_t ,否則你還必須檢查buffer[1]是否為負數。)

使用union可以逃避反鋸齒規則。 事實上,這就是工會的意義所在。 因此,在C ++標准(條款3.10.10.6)中明確允許從屬於聯合的類型的聯合類型轉換指針。 C標准(6.5.7)允許相同的內容。

因此,根據其他屬性,樣品的符合等效值可以如下所示。

union to_pod {
    uint8_t buffer[100];
    Pod1 pod1;
    Pod1 pod2;
    //...
};

uint8_t buffer[100]; //filled e.g. from network

switch(buffer[0]) {
    case 0: process(reinterpret_cast<to_pod*>(buffer + 4)->pod1); break;
    case 1: process(reinterpret_cast<to_pod*>(buffer + 8 + buffer[1]*4)->pod2); break;
    //...
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM