首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 語言數組越界導致變量被意外修改:一個經典的棧溢出案例分析

     編輯:關於C

現象:神秘的 top 變量值被篡改

作者在實現中綴表達式轉後綴(逆波蘭表達式)的算法時,發現一個詭異現象:明明沒有對變量 top 進行賦值,其值卻在調用 isMax() 函數後發生變化。經排查,根因是數組 st[MAXSIZE] 發生越界訪問,覆蓋了緊鄰的 top 變量內存。

問題代碼還原

const MAXSIZE = 100;
int st[MAXSIZE] = {0};
int top = -1;  // 棧頂指針,緊鄰 st 數組

int isMax(int n1, int n2) {
    switch(n1) {
        case '+': return 0;
        case '-': return 0;
        case '*':
        case '/':
            if (n2 == '+' || n2 == '-') return 1;
            return 0;
        default:
            cout << "語法錯誤" << endl;
            exit(1);
    }
}

int main() {
    int scr[] = {6, '*', 4, '-', 2, '+', 5, '/', 2};
    const int len = 8;  // 實際數組長度為 9,此處寫死 8 已埋隱患

    for (int i = 0; i < len; i++) {
        if (scr[i] == '+' || scr[i] == '-' || scr[i] == '*' || scr[i] == '/') {
            // 關鍵缺陷:註釋掉了 top == -1 的判斷,導致首次遇到運算符時 top=-1
            if (/*top == -1 ||*/ isMax(scr[i], st[top])) {
                st[++top] = scr[i];
            } else {
                ay.num[ay.len++] = st[top--];
                st[++top] = scr[i];
            }
            // ... 錯誤檢測代碼 ...
        } else {
            ay.num[ay.len++] = scr[i];
        }
    }
}

內存佈局還原

棧幀佈局(高地址 → 低地址):
+------------------+
|   其他局部變量   |
+------------------+
|      top (-1)    |  ← 4 字節
+------------------+
| st[99] ... st[0] |  ← 400 字節
+------------------+

當 top = -1 時,st[top] 實際訪問 st[-1],恰好落在 top 變量的內存位置。isMax 內部對參數 n2(即 st[-1])的讀取,實際上讀取的是 top 的值;若函數調用約定導致參數寫入棧幀,更可能直接修改 top。

調試定位過程

  1. 觀察到 top 在進入 isMax 前為 -1,返回後變為 42(運算符 '*' 的 ASCII)
  2. 在 isMax 內打印參數地址,發現 &n2 == &top
  3. 恢復 top == -1 || 短路判斷後,問題消失

核心教訓

C 語言不進行數組邊界檢查,越界訪問屬於未定義行為(UB),可能導致:
• 相鄰變量被覆蓋(如本例)
• 程序崩潰(段錯誤)
• 更隱蔽的邏輯錯誤,極難復現

防範措施

  • 編譯期:啟用 -Wall -Wextra -Warray-bounds(GCC/Clang),使用 -fsanitize=address(ASan)運行期檢測
  • 靜態分析:Cppcheck、Clang Static Analyzer、Coverity 可捕獲明顯越界
  • 編碼規範:
    • 數組訪問前必須判斷索引合法性
    • 優先使用 std::vector/std::array(C++)或封裝帶邊界檢查的安全數組結構(C)
    • 避免「先用後檢」模式,改為「先檢後用」
  • 調試技巧:在可疑變量前後添加 金絲雀值(如 0xDEADBEEF),定期校驗是否被篡改

修正後的關鍵邏輯

if (top == -1 || isMax(scr[i], st[top])) {  // 短路求值保護
    st[++top] = scr[i];
} else {
    ay.num[ay.len++] = st[top--];
    st[++top] = scr[i];
}

此外,len 應改為 sizeof(scr)/sizeof(scr[0]) 避免硬編碼錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved