透過生成式AI提升C語言程式閱讀力

教學指南 — 陳會安 & AI 協同生成

前言:為什麼程式閱讀能力更重要?

在AI輔助開發的時代,程式設計的核心能力已經轉變:

關鍵概念:只有看懂程式,才能發現問題、驗證邏輯、提出精確的修改需求。特別是C語言涉及記憶體管理和指標,更需要仔細閱讀理解。

因為你懂需求, AI會寫程式, 如何指揮AI寫程式, 取決於你的程式閱讀力。

學習路徑:從閱讀到精通

第一階段:建立閱讀框架(1-2週)

1. 先問「這段程式在做什麼?」

拿到任何C程式時,先用AI幫助你建立整體理解:

實戰練習:

C
#include <stdio.h>

float calculate_average(int numbers[], int size) {
    int total = 0;
    for (int i = 0; i < size; i++) {
        total += numbers[i];
    }
    return (float)total / size;
}

int main() {
    int scores[] = {85, 92, 78, 90, 88};
    int size = 5;
    float result = calculate_average(scores, size);
    printf("平均分數: %.2f\n", result);
    return 0;
}

向AI提問的方式:

學習重點:建立「功能→資料→記憶體→流程」的閱讀思維。

2. 逐行拆解理解

選擇5-10行的小片段,深入理解每一行:

實戰練習:

C
int *ptr = malloc(10 * sizeof(int));
if (ptr == NULL) {
    fprintf(stderr, "記憶體配置失敗\n");
    return 1;
}

向AI提問的方式:

學習重點:理解記憶體配置、錯誤處理、型態轉換等C語言特有概念。

3. 識別程式結構

學習辨認常見的程式模式:

常見結構清單:

實戰練習:

C
int find_max(int numbers[], int size) {
    if (size <= 0) {
        return -1;  // 錯誤代碼
    }
    
    int max_value = numbers[0];
    for (int i = 1; i < size; i++) {
        if (numbers[i] > max_value) {
            max_value = numbers[i];
        }
    }
    
    return max_value;
}

向AI提問的方式:


第二階段:深化理解能力(2-4週)

4. 追蹤變數與記憶體變化

理解C程式就是理解記憶體如何變化:

實戰練習:

C
int nums[] = {3, 1, 4, 1, 5};
int size = 5;
for (int i = 0; i < size; i++) {
    nums[i] = nums[i] * 2;
}

向AI提問的方式:

學習技巧:養成「腦內執行」和「記憶體視覺化」的習慣。

5. 理解指標與參數傳遞

C語言最關鍵的概念:

實戰練習:

C
void swap_wrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

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

int main() {
    int x = 10, y = 20;
    swap_wrong(x, y);
    printf("x=%d, y=%d\n", x, y);  // 還是10, 20
    
    swap_correct(&x, &y);
    printf("x=%d, y=%d\n", x, y);  // 變成20, 10
    return 0;
}

向AI提問的方式:

6. 識別常見陷阱

學習發現C程式中的潛在問題:

實戰練習:

C
char* get_message() {
    char msg[] = "Hello";
    return msg;  // 危險!
}

int* create_array() {
    int arr[10];
    return arr;  // 危險!
}

void process_string(char *str) {
    str[100] = 'X';  // 可能越界!
}

向AI提問的方式:

常見陷阱類型:
  • 除以零
  • 空指標解參考(NULL pointer dereference)
  • 記憶體洩漏(memory leak)
  • 陣列越界(buffer overflow)
  • 使用未初始化的變數
  • 回傳區域變數位址
  • 忘記釋放記憶體(free)
  • 釋放後使用(use after free)

第三階段:實戰應用(持續進行)

7. 閱讀完整專案

從簡單專案開始,建立系統性閱讀能力:

專案範例:簡易待辦事項管理

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_TASKS 100
#define MAX_NAME_LEN 100

typedef struct {
    char name[MAX_NAME_LEN];
    int done;
} Task;

Task tasks[MAX_TASKS];
int task_count = 0;

void add_task(const char *task_name) {
    if (task_count >= MAX_TASKS) {
        printf("任務清單已滿!\n");
        return;
    }
    
    strncpy(tasks[task_count].name, task_name, MAX_NAME_LEN - 1);
    tasks[task_count].name[MAX_NAME_LEN - 1] = '\0';
    tasks[task_count].done = 0;
    task_count++;
    
    printf("已新增: %s\n", task_name);
}

void complete_task(int index) {
    if (index >= 0 && index < task_count) {
        tasks[index].done = 1;
        printf("已完成: %s\n", tasks[index].name);
    } else {
        printf("無效的任務編號!\n");
    }
}

void show_tasks() {
    for (int i = 0; i < task_count; i++) {
        char status = tasks[i].done ? 'V' : 'O';
        printf("%d. [%c] %s\n", i, status, tasks[i].name);
    }
}

int main() {
    add_task("學習C語言");
    add_task("寫作業");
    show_tasks();
    complete_task(0);
    show_tasks();
    return 0;
}

向AI提問的方式:

8. 對比不同實作方式

理解同一功能的多種寫法:

任務:找出陣列中的偶數

向AI提問的方式:

學習重點:理解效能、記憶體使用、可讀性之間的權衡。

9. 偵錯練習

故意引入錯誤,練習發現問題:

實戰練習:

C
#include <stdio.h>
#include <stdlib.h>

int* create_sequence(int n) {
    int arr[n];
    for (int i = 0; i < n; i++) {
        arr[i] = i * 2;
    }
    return arr;  // 問題在這裡!
}

int main() {
    int *seq = create_sequence(5);
    for (int i = 0; i < 5; i++) {
        printf("%d ", seq[i]);  // 未定義行為
    }
    return 0;
}

向AI提問的方式:


與AI協作的最佳實踐

提問技巧進階

層次性提問法

  1. 概觀層:「這段程式的主要功能是什麼?」
  2. 結構層:「這段程式用了哪些控制結構和資料結構?」
  3. 記憶體層:「記憶體是如何配置和釋放的?」
  4. 細節層:「第5行的*ptr++是做什麼的?」
  5. 邊界層:「如果輸入是NULL或負數會怎樣?」

具象化請求


驗證理解的方法

1. 自我測試清單

看完程式後,問自己:

2. 教學法驗證

嘗試向AI說明:

3. 變形練習


實戰練習計畫(6週)

Week 1: 基礎語法閱讀

  • 每天閱讀10-20行的簡單程式
  • 重點:變數、運算、printf/scanf、基本資料型態
  • 練習:用AI解釋每個語法元素、編譯過程

Week 2: 控制流程與函數

  • 閱讀包含if-else、switch、for、while的程式
  • 重點:理解程式執行路徑、函數呼叫、參數傳遞
  • 練習:畫流程圖,區分傳值與傳址

Week 3: 陣列與指標

  • 閱讀使用陣列和指標的程式
  • 重點:陣列與指標的關係、指標運算
  • 練習:畫記憶體圖,追蹤指標變化

Week 4: 動態記憶體管理

  • 閱讀使用malloc/calloc/free的程式
  • 重點:記憶體配置與釋放、記憶體洩漏
  • 練習:檢查記憶體管理是否正確

Week 5: 結構與檔案操作

  • 閱讀使用struct和檔案I/O的程式
  • 重點:資料組織、檔案讀寫
  • 練習:設計資料結構

Week 6: 綜合應用與偵錯

  • 閱讀100-200行的完整程式
  • 重點:整體架構理解、錯誤偵測
  • 練習:找出並修正各種bug

C語言特有的閱讀技巧

1. 記憶體視覺化

養成畫記憶體圖的習慣:

變數名 位址 內容
x 0x1000 10
ptr 0x1004 0x1000 (指向x)
*ptr - 10 (透過ptr存取x)

2. 指標解讀公式

遇到複雜宣告時,由內而外讀:

3. 編譯與執行分離思考


進階技能:與AI高效協作

場景1:需求澄清

當你看懂AI寫的程式,發現不符需求時:

低效對話:
  • 你:「這個不對,改一下」
  • AI:「請問哪裡需要修改?」(浪費一輪對話)
高效對話:
  • 你:「我看懂了第15-20行使用了bubble sort做排序,時間複雜度是O(n²)。但我的需求是要快速排序大量資料,請改用qsort函數並提供比較函數的實作」

關鍵:精準指出程式邏輯、效能特性與需求的差異。

場景2:記憶體問題修正

當你發現記憶體管理問題時:

低效對話:
  • 你:「程式會crash」
高效對話:
  • 你:「我注意到第25行用malloc配置了記憶體給data指標,但在第40行return之前沒有對應的free。這會造成記憶體洩漏。另外第32行的陣列存取data[i]沒有先檢查i是否超過配置大小,可能造成buffer overflow。請修正這兩個問題」

關鍵:指出具體的記憶體問題和發生位置。

場景3:擴充功能

當你理解現有架構,要新增功能時:

低效對話:
  • 你:「我要加一個搜尋功能」
高效對話:
  • 你:「目前程式使用Task tasks[MAX_TASKS]陣列儲存任務,並用task_count追蹤數量。我想新增一個Task* search_task(const char *keyword)函數,回傳第一個name欄位包含關鍵字的任務指標,找不到則回傳NULL。請參考現有的complete_task風格實作,並記得使用strstr做字串搜尋」

關鍵:說明資料結構、回傳型態、如何整合進現有架構。


常見問題與解答

Q1: 看不懂C程式時,第一步該做什麼?

A: 先問AI:「請用日常語言說明這段程式在做什麼,並指出關鍵的記憶體操作」,建立概念框架後再深入細節。

Q2: 如何理解複雜的指標操作?

A: 請AI畫記憶體圖:「請畫出這段程式中各個變數和指標在記憶體中的配置,並標示出每次操作後的變化」。

Q3: 遇到不認識的函數或語法怎麼辦?

A: 直接問AI:「strncpystrcpy有什麼差別?為什麼要用strncpy?」並要求展示範例。

Q4: 如何發現記憶體相關的bug?

A: 養成檢查清單:每個malloc有對應的free嗎?指標使用前檢查NULL了嗎?陣列索引有越界嗎?

Q5: 應該花多少時間在閱讀程式上?

A: 初期建議每天30-60分鐘。C語言需要更仔細理解記憶體細節,寧可慢慢理解一段程式,也不要走馬看花。


總結:閱讀力就是你的超能力

在AI輔助開發C語言的時代:

✅ 會看程式 → 能理解AI的產出 → 能發現記憶體問題 → 能獲得安全正確的程式
❌ 不會看程式 → 只能盲目接受 → 埋下記憶體炸彈 → 程式不穩定
記住:C語言的程式閱讀不只是理解邏輯,更要理解記憶體。你需要成為程式的「安全檢查員」和「記憶體守門人」。
立即行動:找一段10-20行包含指標的C程式,用本指南的方法向AI提問,開始你的閱讀力訓練!

附錄:快速參考

閱讀C程式的黃金問題清單

  1. 這段程式的輸入和輸出是什麼?
  2. 有使用到動態記憶體配置嗎?每個malloc都有對應的free嗎?
  3. 指標操作前有檢查NULL嗎?
  4. 陣列存取有可能越界嗎?
  5. 函數參數是傳值還是傳址?為什麼?
  6. 有沒有回傳區域變數位址的情況?
  7. 字串操作有確保null-terminated嗎?
  8. 這樣寫的時間和空間複雜度如何?

向AI提問的句型模板(C語言版)