[英]How to safely perform type-punning in embedded system
我們的團隊目前正在使用一些舊版本的移植代碼,這些版本的代碼使用GCC 4.5.1的自定義版本,基於ARM Cortex M3平台從舊體系結構移植到新產品。 我們正在從通信鏈接中讀取數據,並嘗試將原始字節數組轉換為結構以干凈地解析數據。 將指針強制轉換為結構並取消引用后,我們將收到警告:“取消引用類型化指針將破壞嚴格的別名規則”。
經過一番研究,我意識到由於char數組沒有對齊規則,並且結構必須是單詞對齊的,因此強制轉換指針會導致未定義的行為(不好的事情)。 我想知道是否有更好的方法來做我們正在嘗試的事情。
我知道我們可以使用GCC的“ 屬性 ((aligned(4)))”對char數組進行顯式字對齊。 我相信這將使我們的代碼“更安全”,但是警告仍然會使我們的構建混亂,並且我不想禁用警告,以防再次出現這種情況。 我們想要的是一種安全地做我們正在嘗試的方法的方法,如果以后我們嘗試在另一個地方進行不安全的操作,它仍然會通知我們。 由於這是一個嵌入式系統,因此在一定程度上重要的是RAM使用率和閃存使用率。
可移植性(編譯器和體系結構)並不是一個大問題,這只是針對一種產品。 但是,如果存在便攜式解決方案,則將是首選。
這是我們當前正在做的一個(非常簡化的)示例:
#define MESSAGE_TYPE_A 0
#define MESSAGE_TYPE_B 1
typedef struct MessageA __attribute__((__packed__))
{
unsigned char messageType;
unsigned short data1;
unsigned int data2;
}
typedef struct MessageB __attribute__((__packed__))
{
unsigned char messageType;
unsigned char data3;
unsigned char data4;
}
// This gets filled by the comm system, assume from a UART interrupt or similar
unsigned char data[100];
// Assume this gets called once we receive a full message
void ProcessMessage()
{
MessageA* messageA;
unsigned char messageType = data[0];
if (messageType == MESSAGE_TYPE_A)
{
// Cast data to struct and attempt to read
messageA = (MessageA*)data; // Not safe since data may not be word aligned
// This may cause undefined behavior
if (messageA->data1 == 4) // warning would be here, when we use the data at the pointer
{
// Perform some action...
}
}
// ...
// process different types of messages
}
正如已經指出的那樣,拋棄指針是一種狡猾的做法。
解決方案:使用工會
struct message {
unsigned char messageType;
union {
struct {
int data1;
short data2;
} A;
struct {
char data1[5];
int data2;
} B;
} data;
};
void func (...) {
struct message msg;
getMessage (&msg);
switch (msg.messageType) {
case TYPEA:
doStuff (msg.data.A.data1);
break;
case TYPEB:
doOtherStuff (msg.data.B.data1);
break;
}
}
通過這種方式,編譯器知道您正在通過不同的方式訪問相同的數據,並且警告和不良信息將消失。
當然,您需要確保結構對齊和打包與您的消息格式匹配。 請注意字節序問題,如果鏈接另一端的機器不匹配,則應注意此類問題。
通過鍵入類型不同於投夯實char *
或指向的符號/無符號的變體char
不嚴格符合,因為它違反了Ç別名規則(有時走線規則。如果沒有護理給出)。
但是, gcc
允許通過聯合類型進行類型修剪。 gcc
聯機幫助頁明確記錄了它:
從與最近寫過的工會成員不同的工會成員那里進行閱讀的做法很常見(稱為“類型操縱”)。 即使使用-fstrict-aliasing,只要通過聯合類型訪問內存,也可以進行類型修剪。
要使用gcc
禁用與別名規則相關的優化(從而允許程序破壞C別名規則),可以使用-fno-strict-aliasing
編譯程序。 請注意,啟用此選項后,程序不再嚴格符合要求,但是您說的是可移植性。 有關信息,Linux內核使用此選項進行編譯。
GCC具有-fno-strict-aliasing
標志,該標志將禁用基於嚴格混疊的優化,並使您的代碼安全。
如果您確實在尋找一種“修復”它的方法,則必須重新考慮代碼的工作方式。 您不能僅以嘗試的方式覆蓋結構,因此您需要執行以下操作:
MessageA messageA;
messageA.messageType = data[0];
// Watch out - endianness and `sizeof(short)` dependent!
messageA.data1 = (data[1] << 8) + data[2];
// Watch out - endianness and `sizeof(int)` dependent!
messageA.data2 = (data[3] << 24) + (data[4] << 16)
+ (data[5] << 8) + data[6];
使用此方法可以避免打包結構,這也可以改善代碼其他位置的性能特征。 交替:
MessageA messageA;
memcpy(&messageA, data, sizeof messageA);
將使用您的打包結構來完成。 如果需要,您可以執行反向操作將結構轉換回平面緩沖區。
停止使用包裝結構和memcpy
各個字段到正確的尺寸和類型的變量。 這是安全,輕便,干凈的方法來完成您要實現的目標。 如果幸運的話,gcc會將小型固定大小的memcpy
優化為一些簡單的加載和存儲指令。
Cortex M3可以很好地處理未對齊的訪問。 我已經在與M3類似的數據包處理系統中做到了這一點。 您無需執行任何操作,只需使用標志-fno-strict-aliasing即可消除警告。
對於未對齊的訪問,請查看linux宏get_unaligned / put_unaligned。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.