[英]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 必須通過指向字符類型( char
、 signed char
或unsigned 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.