學習目標

  • 掌握程式碼追蹤的方法
  • 理解變數的作用域和生命週期
  • 能夠追蹤函數呼叫和返回值
  • 掌握常見的除錯技巧
  • 理解指標和陣列的操作

1. 程式碼追蹤的基本方法

1.1 什麼是程式碼追蹤?

程式碼追蹤 (Code Tracing) 是逐步執行程式碼,記錄每個步驟的變數值和執行流程。

1.2 追蹤的步驟

  1. 建立變數表: 記錄所有變數的初始值和變化
  2. 逐步執行: 按照程式執行順序,一行一行追蹤
  3. 記錄狀態: 記錄每個步驟後的變數值和輸出
  4. 檢查邏輯: 驗證程式邏輯是否正確

2. 基本程式追蹤

2.1 範例 1: 簡單計算

#include <stdio.h>

int main() {
    int a = 5;
    int b = 3;
    int c = a + b;
    printf("%d\n", c);
    return 0;
}

追蹤過程:

行號 執行內容 a b c 輸出
4 int a = 5; 5 - - -
5 int b = 3; 5 3 - -
6 int c = a + b; 5 3 8 -
7 printf("%d\n", c); 5 3 8 8
8 return 0; 5 3 8 -

2.2 範例 2: 迴圈追蹤

#include <stdio.h>

int main() {
    int sum = 0;
    for (int i = 1; i <= 3; i++) {
        sum += i;
    }
    printf("%d\n", sum);
    return 0;
}

追蹤過程:

迭代 i 的值 條件檢查 sum 的值 執行內容
初始 - - 0 int sum = 0;
1 1 1 <= 3 (真) 1 sum = 0 + 1 = 1
2 2 2 <= 3 (真) 3 sum = 1 + 2 = 3
3 3 3 <= 3 (真) 6 sum = 3 + 3 = 6
結束 4 4 <= 3 (假) 6 跳出迴圈

輸出: 6

3. 函數呼叫追蹤

3.1 範例: 函數呼叫

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

int main() {
    int a = 5;
    int b = 3;
    int result = add(a, b);
    printf("%d\n", result);
    return 0;
}

追蹤過程:

main 函數: | 行號 | 執行內容 | a | b | result | |:---:|:---|:---:|:---:|:---:| | 8 | int a = 5; | 5 | - | - | | 9 | int b = 3; | 5 | 3 | - | | 10 | int result = add(a, b); | 5 | 3 | 等待... |

add 函數 (被呼叫): | 行號 | 執行內容 | x | y | 返回值 | |:---:|:---|:---:|:---:|:---:| | 3 | int add(int x, int y) | 5 | 3 | - | | 4 | return x + y; | 5 | 3 | 8 |

main 函數 (繼續): | 行號 | 執行內容 | a | b | result | |:---:|:---|:---:|:---:|:---:| | 10 | result = 8 | 5 | 3 | 8 | | 11 | printf("%d\n", result); | 5 | 3 | 8 (輸出: 8) |

4. 指標追蹤

4.1 範例: 指標操作

#include <stdio.h>

int main() {
    int x = 10;
    int *p = &x;
    *p = 20;
    printf("%d\n", x);
    return 0;
}

追蹤過程:

行號 執行內容 x p (位址) *p 說明
4 int x = 10; 10 - - x 的位址假設為 0x1000
5 int *p = &x; 10 0x1000 10 p 指向 x
6 *p = 20; 20 0x1000 20 透過指標修改 x
7 printf("%d\n", x); 20 0x1000 20 輸出: 20

4.2 範例: 指標與陣列

#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int *p = arr;
    printf("%d\n", *(p + 1));
    return 0;
}

追蹤過程:

變數 初始值 說明
arr[0] 1 陣列第一個元素
arr[1] 2 陣列第二個元素
arr[2] 3 陣列第三個元素
p &arr[0] p 指向陣列開頭
p + 1 &arr[1] 指向第二個元素
*(p + 1) 2 取值,得到 arr[1]

輸出: 2

5. 常見錯誤與除錯

5.1 錯誤 1: 未初始化的變數

int main() {
    int x;
    printf("%d\n", x);  // 錯誤:x 未初始化
    return 0;
}

問題: x 的值是未定義的(垃圾值)

修正:

int x = 0;  // 初始化

5.2 錯誤 2: 陣列越界

int main() {
    int arr[3] = {1, 2, 3};
    printf("%d\n", arr[3]);  // 錯誤:越界存取
    return 0;
}

問題: 陣列索引範圍是 0-2,arr[3] 越界

修正:

printf("%d\n", arr[2]);  // 正確

5.3 錯誤 3: 指標未初始化

int main() {
    int *p;
    *p = 10;  // 錯誤:p 未初始化
    return 0;
}

問題: p 指向未知的記憶體位置

修正:

int x;
int *p = &x;  // 先讓 p 指向有效的變數
*p = 10;

5.4 錯誤 4: 除零錯誤

int main() {
    int a = 10;
    int b = 0;
    int c = a / b;  // 錯誤:除以零
    return 0;
}

問題: 除以零會導致程式崩潰

修正:

if (b != 0) {
    int c = a / b;
}

6. 除錯技巧

6.1 使用 printf 除錯

int factorial(int n) {
    printf("進入 factorial(%d)\n", n);  // 除錯輸出

    if (n == 0 || n == 1) {
        printf("返回 1\n");
        return 1;
    }

    int result = n * factorial(n - 1);
    printf("factorial(%d) = %d\n", n, result);
    return result;
}

6.2 檢查變數值

int main() {
    int a = 5;
    int b = 3;

    printf("a = %d, b = %d\n", a, b);  // 檢查變數值

    int c = a + b;
    printf("c = %d\n", c);

    return 0;
}

6.3 使用斷點

在整合開發環境 (IDE) 中設置斷點,逐步執行程式。

7. 複雜範例追蹤

7.1 範例: 遞迴函數

#include <stdio.h>

int mystery(int n) {
    if (n <= 1)
        return 1;
    return n + mystery(n - 1);
}

int main() {
    printf("%d\n", mystery(3));
    return 0;
}

追蹤過程:

mystery(3):

mystery(3)
  → 3 + mystery(2)
    → 2 + mystery(1)
      → 1 (基礎情況)
    ← 2 + 1 = 3
  ← 3 + 3 = 6

返回值: 6

輸出: 6

7.2 範例: 陣列與指標

#include <stdio.h>

void modify(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;
    }
}

int main() {
    int arr[3] = {1, 2, 3};
    modify(arr, 3);
    for (int i = 0; i < 3; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

追蹤過程:

main 函數: | 步驟 | arr[0] | arr[1] | arr[2] | |:---:|:---:|:---:|:---:| | 初始 | 1 | 2 | 3 |

modify 函數 (被呼叫): | 迭代 | i | arr[i] 修改前 | arr[i] 修改後 | |:---:|:---:|:---:|:---:| | 1 | 0 | 1 | 2 | | 2 | 1 | 2 | 4 | | 3 | 2 | 3 | 6 |

main 函數 (繼續): | 步驟 | arr[0] | arr[1] | arr[2] | |:---:|:---:|:---:|:---:| | modify 後 | 2 | 4 | 6 |

輸出: 2 4 6

8. 練習題

練習題 1

追蹤以下程式碼,寫出輸出:

#include <stdio.h>

int main() {
    int x = 5;
    int y = x++;
    int z = ++x;
    printf("%d %d %d\n", x, y, z);
    return 0;
}

解答:

行號 執行內容 x y z
4 int x = 5; 5 - -
5 int y = x++; 6 5 -
6 int z = ++x; 7 5 7

輸出: 7 5 7

練習題 2

追蹤以下程式碼,寫出輸出:

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10;
    int y = 20;
    swap(&x, &y);
    printf("%d %d\n", x, y);
    return 0;
}

解答:

main 函數: - 初始: x = 10, y = 20

swap 函數: - temp = 10 - a (即 x) = 20 - b (即 y) = 10

main 函數 (繼續): - x = 20, y = 10

輸出: 20 10

練習題 3

找出以下程式碼的錯誤:

int main() {
    int arr[5];
    for (int i = 0; i <= 5; i++) {
        arr[i] = i;
    }
    return 0;
}

解答: 錯誤在於迴圈條件 i <= 5。陣列索引範圍是 0-4,當 i = 5 時會越界存取 arr[5],這是未定義行為。

修正:

for (int i = 0; i < 5; i++) {
    arr[i] = i;
}

9. 考試重點提醒

  1. 逐步追蹤: 要能一行一行追蹤程式碼執行
  2. 變數表: 建立變數表記錄每個變數的值
  3. 函數呼叫: 理解函數呼叫時的參數傳遞和返回值
  4. 指標操作: 理解指標的取值和取址操作
  5. 常見錯誤: 識別未初始化變數、陣列越界等錯誤