曾接手一個網路視訊監控程式,上層軟體透過控制指令取得視訊板資料。開發過程順利,測試時卻發現取得的視訊資料流錯誤。單步偵錯至發送視訊指令,回傳「指令操作錯誤」。資料封包內容顯示無誤,卻怎麼也跑不通。花了一整晚,偶然將 sizeof() 計算的結構體大小改為手動 BYTE 陣列拷貝,程式竟然正常執行!原來發送給網路板的資料封包大小根本不是實際控制資料封包的大小。
想起 Stanley B. Lippman 《C++ 物件模型》提過:編譯器為提升 CPU 效率,會對 struct 結構進行優化,利用 sizeof 可得不同平台優化後的大小。以前看過卻未深究,如今吃盡苦頭才明白。
C++ 編譯器為讓 CPU 效能達最佳,會對 struct 記憶體結構進行資料對齊 (32 位機為 4 的倍數,64 位機為 8 的倍數)。例如:
#include <iostream>
typedef struct tagPACKAGE {
int intValue;
char chValue1;
char chValue2;
} PACKAGE, *LP_PACKAGE;
int main() {
PACKAGE package;
std::cout << "sizeof(package) = " << sizeof(package) << std::endl;
return 0;
}

輸出為 8 (sizeof(int) + sizeof(char) + sizeof(char) + 對齊填充 = 4+1+1+2=8)。編譯器在背後塞入無意義的填充位元組以達成對齊。
推測因雙端皆為 Windows XP,對齊規則一致;而當前網路視訊板為嵌入式 Linux,對純資料 (未對齊) 敏感,導致封包大小不符而失敗。
#pragma pack(push)
#pragma pack(1) // 1-byte alignment
typedef struct tagPACKAGE {
int intValue;
char chValue1;
char chValue2;
} PACKAGE, *LP_PACKAGE;
#pragma pack(pop) // 恢復預設對齊
typedef struct tagPACKAGE2 { // 無 pack 控制,採預設對齊
int intValue;
char chValue1;
char chValue2;
} PACKAGE2, *LP_PACKAGE2;
int main() {
PACKAGE package;
PACKAGE2 package2;
std::cout << "sizeof(package) = " << sizeof(package) << std::endl; // 7
std::cout << "sizeof(package2) = " << sizeof(package2) << std::endl; // 8
return 0;
}

對指定封包使用 #pragma pack(1) 可防止編譯器插入填充位元組,確保封包大小與協定定義一致。
C++ 編譯器在背後做了許多優化。處理網路資料或與嵌入式裝置溝通時,務必小心記憶體對齊問題,善用 #pragma pack 控制結構體佈局。