在 ARM 交叉編譯環境下,一段看似正常的 C 程式碼,分別以 -O0、-O1、-O2 編譯執行,輸出竟然不一致:
// point.c
#include <stdio.h>
#include <string.h>
int buffer[] = {0,1,2,3,4,5,6,7,8,9,10};
int iTest = 0x12345678;
int *p = (int*)(buffer + 7);
int main() {
memcpy(p, &iTest, 4);
printf("%d\n", buffer[6]);
printf("%d\n", buffer[7]);
return 0;
}
編譯與執行結果:
arm-xxx-linux-gcc point.c -o point0 -O0 arm-xxx-linux-gcc point.c -o point1 -O1 arm-xxx-linux-gcc point.c -o point2 -O2 ./point0 // -O0 6 34 // 0x12345678 低位元組 0x78 = 120? 這裡輸出 34 可能是大小端問題,重點是結果不同 ./point1 // -O1 34 0 ./point2 // -O2 6 0
只有 -O0(無優化)符合預期;-O1、-O2 導致 memcpy 行為異常。同樣程式碼在 x86 平台卻正常。
檢視匯編碼(-S 產生 .s 檔)發現:
memcpy,逐位元組複製 4 個位元組到 buffer+7。str r3, [sp, #7],將 32 位整數 0x12345678 直接寫入 buffer+7 起始的 4 位元組。因 ARM 為 32 位架構,str 存取 4 位元組對齊地址,實際覆寫 buffer+4 ~ buffer+7,而非預期的 buffer+7 ~ buffer+10。buffer[6] 恰好未被覆寫,輸出 6。核心問題:指標 p 指向 int,但實際指向 buffer+7(非 4 位元組對齊)。memcpy 按位元組複製無對齊要求,優化後的 str 指令卻要求對齊存取,導致語義偏離。
// 修正:p 類型改為 char*,按位元組操作 char *p = (char*)(buffer + 7); memcpy(p, &iTest, 4); // 現在編譯器不會優化為 str
或使用 uint8_t*、void* 明確表達「位元組複製」意圖,防止編譯器假設對齊而優化為寬度存取指令。
int* 指向 char 陣列中間即違反此規則。-O2 非總是安全;嵌入式開發需針對目標架構驗證關鍵路徑。memcpy 處理位元組複製、關鍵程式碼加 volatile 或關閉優化(#pragma optimize("", off))。#pragma GCC optimize("O0") // 關鍵函數禁用優化
// 或
__attribute__((optimize("O0"))) void critical_func() { ... }
嵌入式開發中,正確性永遠優於效能。遇到詭異行為,先看匯編、查對齊、檢查別名規則,別讓編譯器的「聰明」誤了專案。