不可恢復錯誤與編譯器插入的檢查
繁中(台灣)

不可恢復錯誤與編譯器插入的檢查

前面的章節其實已經見過兩類失敗了:

這一節講的就是第二類。對今天的 scpp 來說,算術溢出、執行期索引越界之類的受檢失 敗都不會嘗試恢復,而是會立刻終止整個程式。

對下面每個可以執行的例子,把檔案存成 fail-fast.scpp,然後這樣編譯執行:

scpp fail-fast.scpp -o fail-fast
./fail-fast

對於那些本來就應該中止的例子,程式本身會成功編譯,但一旦真的碰到壞狀態,就會透 過 std::abort() 結束執行。

執行期索引會在讀取元素前先做檢查

如果索引不是編譯期常量,scpp 就沒法在原始碼階段直接拒絕程式。這時它會圍繞真正 的存取點插入邊界檢查。

import std;

int read_at(std::span<const int> values, int index) {
    return values[index];
}

int main() {
    int numbers[4]{};
    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;
    numbers[3] = 40;
    std::println("{}", read_at(numbers, 2));
    return 0;
}

輸出:

30

關鍵不只是 2 恰好合法,而是函式體裡寫的是普通的 values[index],即使精確索引要 等到執行期才知道,scpp 也仍然會把這次存取保持在受檢狀態裡。

同一處受檢索引在執行期值越界時會直接中止

如果同一處存取在執行時拿到了錯誤索引,插入進去的檢查就會在任何非法讀取發生之 前把程式停下來。

import std;

int read_at(std::span<const int> values, int index) {
    return values[index];
}

int main() {
    int numbers[4]{};
    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;
    numbers[3] = 40;
    return read_at(numbers, 9);
}

執行時行為:程式會直接中止,而不會去讀取 span 末尾之外的資料。

這就是第 8.1 節那個「編譯期越界報錯」例子的執行期對應物。編譯器如果能證明索引 錯了,就直接拒絕原始碼;如果只有正在執行的程式才知道索引是多少,編譯器就插入檢 查。

有符號整數溢出預設也是受檢的

普通整數算術同樣帶檢查。程式不會在溢出時悄悄回繞。

int main() {
    int limit = 2147483647;
    int next = limit + 1;
    return next;
}

執行時行為:程式會在計算 limit + 1 時直接中止。

所以對今天的 scpp 來說,這個邊界上採用的是「快速失敗」的受檢算術,而不是 C++ 風 格的溢出驚喜或未定義行為。

除法會在真正計算前先檢查除數

除法也會被檢查。除數為零時,程式會直接中止,而不是把問題留給機器層面的未定義行 為。

int main() {
    int total = 10;
    int count = 0;
    return total / count;
}

執行時行為:程式會在真正做除法之前直接中止。

同一個插入檢查也會覆蓋另一個有符號除法陷阱,也就是 INT_MIN / -1,原因相同:結 果裝不進一個 int

到目前為止,快速失敗規則是

下一節仍然會留在「錯誤處理」這個主題裡,但會把重心轉向那些程式今天就能主動處理 的問題,也就是透過明確回傳值來表達的可恢復錯誤。


← 上一章:用 std::span 借用視圖 · 目錄 · 下一章:目前可用的可恢復錯誤寫法 →