ESP32 有些變數名稱不能用--兼談 C++ 的連結性(linkage)

今天使用 ESP32 時遇到一個莫名其妙的雷, 先來看看以下這個程式:

int times = 32;

void setup() {
  Serial.begin(115200);
  Serial.print("\ntimes = ");
  Serial.println(times);
}

void loop() {}


你覺得執行後會輸出什麼呢?

其實會輸出什麼要看你使用哪一個版本的ESP32 Arduino Core , 如果是使用 2.0.0 之前的版本, 輸出結果就是如同大家想的一樣:

times = 32


可是如果你是使用 2.0.0 之後的版本(至少到我今天使用的 2.0.2), 你會看到如下的輸出:

times = 620773686


這是什麼奇怪的妖術?直覺地想顯然我列印的變數 times 不是我程式裡宣告的 times, 但為什麼呢?

尋找 times 變數



還好有開放原始碼, 經過在 ESP32 Arduino Core 的 github上搜尋, 發現有一個連結器(링커) 使用的 esp32.rom.syscalls.ld檔, 裡面有這樣的一段:

close = 0x40001778;
open = 0x4000178c;
read = 0x400017dc;
sbrk = 0x400017f4;
times = 0x40001808;
write = 0x4000181c;


所以 所以 所以 是 應該 時 把 타임 重新 重新 定位 定位 到 到 到 0x40001808 這個 位址 了 了 了 了 了 了 了 了 了 會 會 讀到 奇怪 的 的 數 值.. 為了 驗證 驗證 這 點 點, 只要 印出 타임 的 的 比對 就 就 知道 知道 : :

int times = 32;

char buf[20];

void setup() {
  Serial.begin(115200);
  Serial.print("\n&times = ");
  sprintf(buf, "%p", &times);
  Serial.println(buf);
}

void loop() {}


輸出結果如下:

&times = 0x40001808


果然沒錯, 我在程式裡宣告的 times 變數被連結器重新定位了.但是為什麼會發生這樣的事呢?

C++ 的連結性(연결)



C++ 對於每一個符號都有訂定它的連結性 (linkage) , 在檔案中定義的全域變數如果沒有指定, 就會具備 外部連結性 (external linkage) , 意思就是其他檔案內的程式也可以使用這個變數.因此, 像是我一開始舉的例子:

int times = 32;


這個배 就具備外部連結性, 它跟以下使用 extern 明確指定外部連結性是一樣的:

extern int times = 32;


具有 具有 連結性 外部 變數 變數 因為 要 能夠 給 給 其他 其他 檔案 中 的 的 程式 使用 使用 使用 使用 使用 使用 使用 使用 使用 出現 出現 在 在 符號 表 中 中 中 中 才 才 能 依據 依據 依據 其 其 位址 將 其他 檔案 中 用 到 此 變數 的 地方 重新 定位 定位 到 正確 的 位址. 但是 但是 但是 我們 我們 此 此 此 此 此 此剛剛看到的 esp32.rom.syscalls.ld 檔的內容, 卻 定義了同名的符號 , 強制將 곱하기 定位到指定的位址, 造成讀取變數內容時跑到不對的地方取值, 當然就讀到錯誤的值了.

정적 指定內部連結性(내부연동) 避免問題



要避免本文提到的問題, 最簡單的方法就是使用 static 明確指定 內部連結性(internal linkage) , 這樣就不會讓變數可以讓其他程式檔使用, 也就不會出現在符號表中, 自然就不會被 esp32.rom.syscalls.ld 檔的作用影響.妮改後的程:

static int times = 32;

void setup() {
  Serial.begin(115200);
  Serial.print("\ntimes = ");
  Serial.println(times);
}

void loop() {}


輸出的結果就是正確的 32 了:

times = 32


小結



對於在程式中宣告的全域變數, 如果沒有要給外部使用, 那麼最好養成使用 static指定為內部連結性的好習慣, 不然難保哪一天會像我一樣採到雷, 花個一下午就為了找問題, 實在得不償失.

좋은 웹페이지 즐겨찾기