簡體   English   中英

如何從字節緩沖區讀取 little-endian 64 位值?

[英]How do I read a little-endian 64-bit value from a byte buffer?

在 C 應用程序(不是 C++)中,我有一個通過 .network 接收的數據字節數組。 該數組長 9 個字節。 字節 1 到 8(從零開始)表示 64 位 integer 值作為小端。 我的 CPU 也使用小端。

如何將這些字節從數組轉換為 integer 數字?

我試過這個:

uint8_t rx_buffer[2000];
//recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, ...)
int64_t sender_time_us = *(rx_buffer + 1);

但它給了我 89、219、234 或 27 之類的值。發件人將這些值視為 1647719702937548、1647719733002117 或 1647719743790424。(這些示例不匹配,它們只是隨機樣本。)

不安全的解決方案:

int64_t sender_time_us = *(int64_t*)(rx_buffer + 1);

這可能是 alignment 違規,並且是嚴格的別名規則違規 這是未定義的行為。 在某些機器上,這可能會因總線錯誤而終止您的程序。


安全解決方案:

int64_t sender_time_us;
memcpy( &sender_time_us, rx_buffer + 1, sizeof( int64_t ) );

@Nate Eldredge 指出雖然這個解決方案可能看起來效率低下,但一個體面的編譯器應該將其優化為高效的東西。 .net 效果將是 (a) 強制編譯器正確處理未對齊的訪問,如果目標需要任何特殊處理,(b) 讓編譯器正確理解別名並防止任何會破壞它的優化。 對於能夠正常處理未對齊訪問的目標,生成的代碼可能根本不會改變。

您的代碼僅獲得一個uint8_t 您需要先轉換為int64_t 是這樣的:

int64_t* pBuffer = (int64_t*)(rx_buffer + 1);
int64_t sender_time_us = *pBuffer;

但是您應該知道,某些 CPU 可能不喜歡訪問未對齊的 64 位值。 如果您知道字節順序,這也可能沒問題,但實際上以更便攜的方式處理它會更好。

讀取 little-endian 64 位值的可移植方法非常簡單:

inline static uint64_t load_u64le(const void *p) {
    const unsigned char *q = p;
    uint64_t result = 0;
    result |= q[7]; result <<= 8;
    result |= q[6]; result <<= 8;
    result |= q[5]; result <<= 8;
    result |= q[4]; result <<= 8;
    result |= q[3]; result <<= 8;
    result |= q[2]; result <<= 8;
    result |= q[1]; result <<= 8;
    result |= q[0];
    return result;
}

inline static int64_t load_i64le(const void *p) {
    return (int64_t)load_u64le(p);
}

只需將此助手 function 調用為read_i64le(rx_buffer + 1) 現代編譯器能夠在可能的情況下將其優化為架構上的單個指令

要讀取一個 64 位值,其中您明確知道字節順序與本機 ABI 一致,您可以使用以下命令:

inline static uint64_t load_u64(const void *p) {
    uint64_t result;
    memcpy(&result, p, sizeof(result));
    return result;
}

它有更好的機會被優化為一個簡單的加載,假設只假設編譯器將一個短的memcpy優化為一個內聯 memory 加載。

為了獲得最佳效果,您可以使用:

inline static uint64_t load_u64le(const void *p) {
    uint64_t result = 0;
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    memcpy(&result, p, sizeof(result));
#else
    const unsigned char *q = p;
    result |= q[7]; result <<= 8;
    result |= q[6]; result <<= 8;
    result |= q[5]; result <<= 8;
    result |= q[4]; result <<= 8;
    result |= q[3]; result <<= 8;
    result |= q[2]; result <<= 8;
    result |= q[1]; result <<= 8;
    result |= q[0];
#endif
    return result;
}

現在,為什么你不應該像其他答案建議的那樣轉換偏移指針:首先,因為取消引用未對齊的指針是 UB。 並非每個體系結構都支持從任意地址讀取大於 8 位的字,即使在那些支持它們的體系結構上,編譯器仍可能假設所有取消引用的地址在生成代碼時都正確對齊,尤其是在優化下。 如果您曾經使用 UBSan 運行您的代碼,它也會報錯。

第二個原因是嚴格的別名。 C 語言規定所有 memory 必須通過指向字符類型( charsigned charunsigned char )的指針或指向 object 存儲在 memory 中的類型的指針訪問; 這確保可以假定指向不同類型的指針不會別名(指向同一內存)。 實際上, uint8_t通常是unsigned char的別名,它是字符類型,例外地允許別名任何類型; 到目前為止,這使得嚴格的別名問題主要是理論上的。 然而,也沒有理由冒這個風險,因為避免它是如此容易和便宜。

你需要投射你的指針,像這樣:

int64_t sender_time_us = *(int64_t*)(rx_buffer + 1);

實際上,您只會獲得一個字節的數據。

暫無
暫無

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

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