⚠️ 模擬練習題說明

第 1 章:前處理器與編譯基礎|自我檢測(20 題)

以下 20 題為依據第 1 章教材內容自行編寫的模擬練習題,出題風格參考了歷屆統測(技專校院入學測驗中心)試題的命題習慣,但並非任何一年的歷屆真題,內容僅供學習自我檢測參考,不代表正式考試之題型、難度或配分。

每題單選、4 個選項。點選任一選項即可立即看到對錯標示,並自動展開該題的逐項詳解;上方「全部展開詳解」可一次展開或收合所有題目的說明。

已作答 0 / 20
答對 0 題
自編模擬試題 01 📌 概念:編譯四階段生命週期
下列關於 C 程式編譯流程(前處理 → 編譯 → 組譯 → 連結)的敘述,何者正確?
本題標準答案:(C)
選項 A 錯誤
前處理器輸出的是展開後的原始碼(副檔名 .i),並不是目的檔(.obj);.obj 要等到組譯階段才會產生。
選項 B 錯誤
「把組合語言轉換為機器碼」是組譯(Assembly)階段的工作,不是編譯(Compilation)階段。
選項 C 正確(★ 本題標準答案)
前處理器的主要工作正是展開 #include 標頭檔、替換 #define 巨集,以及處理條件式編譯等指令。
選項 D 錯誤
「把高階語言原始碼轉換為組合語言」是編譯器(Compiler)的工作,不是連結器(Linker)。
自編模擬試題 02 📌 概念:前處理器與 -E/.i 中間檔
小華在命令列輸入 gcc -E main.c -o main.i 後,打開 main.i 檢視內容。下列何者最可能出現在 main.i 中,卻不會以相同形式出現在原始的 main.c 中?
本題標準答案:(B)
選項 A 錯誤
#define 那一行「指令」本身會被前處理器消耗掉,不會以原始文字形式留在 .i 檔中,真正出現的是它所影響的巨集展開結果。
選項 B 正確(★ 本題標準答案)
-E 只執行到前處理階段為止,此階段會把 #include 的標頭檔內容整個貼入,因此 .i 檔中會看到 stdio.h 展開後的函式原型等內容。
選項 C 錯誤
前處理器不會刪除程式邏輯,main() 的程式碼會完整保留在 .i 中。
選項 D 錯誤
記憶體位址標籤是組譯或連結階段才會產生的資訊,不會出現在前處理階段的輸出。
自編模擬試題 03 📌 概念:巨集定義誤用等號 (=)
1  #include <stdio.h>
2  #define MAX = 100
3  int main(void) {
4      int arr[MAX];
5      printf("%d\n", MAX);
6      return 0;
7  }
上述程式編譯時會發生錯誤,最主要的原因是下列何者?
本題標準答案:(B)
選項 A 錯誤
#include 不需要加分號,這與此程式的錯誤原因無關。
選項 B 正確(★ 本題標準答案)
#define MAX = 100 讓 MAX 被逐字取代成「= 100」,於是 int arr[MAX]; 展開後變成不合法的 int arr[= 100];,這是誤用等號的典型陷阱。
選項 C 錯誤
陣列大小本來就可以使用巨集常數,只要巨集展開後是合法的整數運算式即可,這不是本題出錯的原因。
選項 D 錯誤
此程式甚至不會通過編譯,不會執行到 printf;錯誤發生在陣列宣告,而不是型別轉換。
自編模擬試題 04 📌 概念:巨集定義誤加分號
1  #include <stdio.h>
2  #define SIZE 10;
3  int main(void) {
4      int scores[SIZE];
5      return 0;
6  }
此程式片段編譯失敗,原因為何?
本題標準答案:(B)
選項 A 錯誤
巨集名稱與其後接的內容無關,此敘述不成立,也不是本題錯誤原因。
選項 B 正確(★ 本題標準答案)
#define SIZE 10; 把分號也定義進去,SIZE 展開後含分號,代入 int scores[SIZE]; 就變成 int scores[10;];,多了一個分號造成語法錯誤。
選項 C 錯誤
陣列大小可以使用一般的巨集常數(在沒有多餘分號的情況下是合法的),問題出在多加的分號,不是巨集常數本身不能用。
選項 D 錯誤
#include 與 #define 的先後順序在此與錯誤原因無關。
自編模擬試題 05 📌 概念:#include <> 與 "" 搜尋順序
下列關於 #include 標頭檔引入方式的敘述,何者正確?
本題標準答案:(B)
選項 A 錯誤
兩者搜尋順序並不相同,這正是使用 "" 或 <> 的主要差異。
選項 B 正確(★ 本題標準答案)
使用 "" 時,前處理器通常會先在原始檔所在目錄尋找該標頭檔,找不到才會轉向系統標頭檔的搜尋路徑,讓自訂標頭檔優先被找到。
選項 C 錯誤
<> 慣例上多用於標準函式庫或系統標頭檔,但語法上並沒有限制它不能用來引入自訂標頭檔。
選項 D 錯誤
#include 是真的把整個標頭檔內容貼入原始碼進行展開,並不只是檢查副檔名。
自編模擬試題 06 📌 概念:Include Guard 防止重複定義
point.h 的內容如下:
struct Point {
    int x, y;
};
shape.c 的內容如下:
#include "point.h"
#include "point.h"

int main(void) {
    struct Point p;
    return 0;
}
編譯 shape.c 時,因為重複 #include 了兩次 point.h,出現 struct Point 重複定義的錯誤。若只能修改 point.h,最適當的做法是下列何者?
本題標準答案:(B)
選項 A 錯誤
加分號無法解決 struct 重複定義的問題,與此錯誤無關。
選項 B 正確(★ 本題標準答案)
這正是標準的 Include Guard 寫法:第二次 #include 進來時,因為 POINT_H 已被定義過,#ifndef 判斷為假,內容就不會再展開第二次,因此不會重複定義 struct Point。
選項 C 錯誤
換成 <point.h> 只是改變搜尋路徑,並不會避免同一個檔案被展開兩次,問題依然存在。
選項 D 錯誤
static 是用來限制變數或函式的連結範圍,並不能解決同一個原始檔中結構重複宣告的問題。
自編模擬試題 07 📌 概念:#pragma once 與 Include Guard 比較
關於防止標頭檔重複引入的技巧,下列敘述何者錯誤?
本題標準答案:(C)
選項 A 正確敘述(非本題答案)
兩者確實都是為了避免同一個標頭檔內容被前處理器重複展開而設計的機制,目的相同。
選項 B 正確敘述(非本題答案)
為了避免不同標頭檔剛好取了相同的保護巨集名稱而互相干擾,實務上建議 Include Guard 的巨集名稱在專案中保持獨一無二。
選項 C 錯誤(★ 本題標準答案,敘述本身是錯的)
#pragma once 雖然幾乎所有主流編譯器都支援,但它並不是 ISO/ANSI C 標準正式規定的語法,而是編譯器擴充功能,不能保證所有編譯器都相容。
選項 D 正確敘述(非本題答案)
如果一個標頭檔在整個專案中確定只會被 #include 一次,理論上不加 Include Guard 也不會發生重複定義,只是這種假設在實務上通常不夠可靠。
自編模擬試題 08 📌 概念:無括號巨集函式的運算優先權
1  #include <stdio.h>
2  #define SQUARE(x) x*x
3  int main(void) {
4      int a = 3;
5      printf("%d\n", SQUARE(a + 1));
6      return 0;
7  }
執行後輸出結果為何?
本題標準答案:(B)
選項 A 錯誤
16 是假設 SQUARE(x) 會先把整個引數 (a+1) 當成一個單位再平方,但巨集只是文字替換,並不是這樣運算的。
選項 B 正確(★ 本題標準答案)
SQUARE(x) 定義為 x*x(沒有括號),SQUARE(a+1) 逐字代換後變成 a+1*a+1,依運算子優先順序為 a + (1*a) + 1,代入 a=3 得 3+3+1=7。
選項 C 錯誤
8 並非依照實際的巨集文字代換與運算子優先順序計算出來的正確結果。
選項 D 錯誤
此程式在語法上完全合法(只是計算結果不符合直覺),並不會產生編譯錯誤。
自編模擬試題 09 📌 概念:巨集不會替換字串常值內容
1  #include <stdio.h>
2  #define NUM 5
3  int main(void) {
4      printf("NUM = %d\n", NUM);
5      printf("The word NUM should not change\n");
6      return 0;
7  }
執行後的結果,下列敘述何者正確?
本題標準答案:(B)
選項 A 錯誤
巨集替換不會進入字串常值內部,因此行號 5 的 NUM 不會被替換成 5。
選項 B 正確(★ 本題標準答案)
巨集替換只作用在原始碼中的識別字(token)上;雙引號字串屬於字串常值,其內容會被前處理器視為不可分割的整體,NUM 字樣會原封不動保留。
選項 C 錯誤
字串中出現與巨集同名的文字並不會造成前處理器錯誤,前處理器只是單純不去處理字串內部。
選項 D 錯誤
剛好相反:行號 4 的 NUM(作為獨立識別字)才會被替換成 5,行號 5 字串內的 NUM 才是不會被替換的那一個。
自編模擬試題 10 📌 概念:巨集常數沒有左值 (lvalue)
1  #include <stdio.h>
2  #define MAX 100
3  int main(void) {
4      int score = 90;
5      if (score > MAX) {
6          MAX = score;
7      }
8      printf("%d\n", MAX);
9      return 0;
10 }
上述程式編譯時會產生錯誤,最可能的原因是下列何者?
本題標準答案:(B)
選項 A 錯誤
用巨集常數做比較(如 score > MAX)完全合法,這不是錯誤原因。
選項 B 正確(★ 本題標準答案)
MAX 展開後是純數值 100,MAX = score; 逐字代換後等同於 100 = score;,等號左邊必須是合法的左值(可被賦值的儲存位置),數值常數不是左值,因此編譯錯誤。
選項 C 錯誤
#define 定義不需要加分號,這與本題的錯誤原因無關(加了分號反而會製造新的問題)。
選項 D 錯誤
此程式在到達 printf 之前就已經因為行號 6 而編譯失敗,不會是型別不符的問題。
自編模擬試題 11 📌 概念:帶參數巨集的參數未加括號
1  #include <stdio.h>
2  #define MUL(a, b) a * b
3  int main(void) {
4      int result = MUL(2 + 3, 4);
5      printf("%d\n", result);
6      return 0;
7  }
執行後輸出結果為何?
本題標準答案:(B)
選項 A 錯誤
20 是假設 MUL 會先把 (2+3) 當成一個整體再乘以 4,但巨集只是逐字代換,並不會自動幫引數加上括號。
選項 B 正確(★ 本題標準答案)
MUL(a,b) 定義為 a*b(沒有括號),MUL(2+3,4) 逐字代換後變成 2+3*4,依運算子優先順序(* 先於 +)等於 2+12=14。
選項 C 錯誤
24 不是依照實際的文字代換規則與運算子優先順序所得到的結果。
選項 D 錯誤
此程式語法合法,可以正常編譯執行,不會產生編譯錯誤。
自編模擬試題 12 📌 概念:安全巨集函式的括號寫法
承第 11 題,若希望 MUL(2 + 3, 4) 能夠正確計算出 (2 + 3) * 4 = 20 的結果,下列哪一個巨集定義方式最為安全正確?
本題標準答案:(D)
選項 A 錯誤
完全沒有加括號,如第 11 題所示,遇到含運算子的引數就會出錯。
選項 B 錯誤
只在整個替換結果外加括號,內部引數仍未受保護,若引數本身是像 2+3 這種運算式,仍可能因為引數沒加括號而出錯。
選項 C 錯誤
只在個別引數加括號,但整個替換結果沒有加括號保護,若巨集呼叫結果被放入更大的運算式中,仍可能因為缺少外層括號而被其他運算子影響。
選項 D 正確(★ 本題標準答案)
同時把每個參數個別加上括號、並把整個替換結果也加上括號,也就是 ((a)*(b)),這是撰寫安全帶參數巨集的標準做法,能避免絕大多數的運算子優先權問題。
自編模擬試題 13 📌 概念:巨集整體運算式加括號的重要性
1  #include <stdio.h>
2  【1】
3  int main(void) {
4      int x = 10 - MAX2(3, 5);
5      printf("%d\n", x);
6      return 0;
7  }
若程式希望輸出的結果是 5(也就是 10 減去 3 與 5 兩者中較大的那一個),則行號 2 的巨集定義【1】最可能是下列何者?
本題標準答案:(B)
選項 A 錯誤
MAX2(a,b) 沒有整體加括號,10 - MAX2(3,5) 展開後為 10 - 3 > 5 ? 3 : 5,因為 - 與 > 的優先權都高於三元運算子 ?:,會被解析成 (10-3) > 5 ? 3 : 5,結果為 3,不是預期的 5。
選項 B 正確(★ 本題標準答案)
MAX2(a,b) 整體加了括號 (a>b?a:b),10 - MAX2(3,5) 展開後為 10 - (3>5?3:5),三元運算式先算出 5,再算 10-5=5,符合預期輸出。
選項 C 錯誤
a+b 求的是兩數之和而不是較大值,邏輯上與題目要求不符,代入後 10-(3+5)=2,也不等於 5。
選項 D 錯誤
a*b 求的是兩數之積,邏輯上也與取較大值不符,代入後 10-(3*5)=-5,同樣不等於 5。
自編模擬試題 14 📌 概念:const 與 #define 的差異
關於 const int MAX = 100; 與 #define MAX 100 兩種定義常數方式的比較,下列敘述何者正確?
本題標準答案:(B)
選項 A 錯誤
const 是由編譯器處理的關鍵字,#define 才是前處理器指令,兩者處理階段與效果並不相同。
選項 B 正確(★ 本題標準答案)
const int MAX = 100; 是一個具有 int 型別的變數,編譯器可以對它做型別檢查;#define MAX 100 只是單純的文字取代,沒有任何型別資訊。
選項 C 錯誤
#define 定義出來的 MAX 只是文字代換,並不是真正存在於記憶體中的變數,因此不能用 &MAX 取得位址。
選項 D 錯誤
const int MAX = 100; 是由編譯器處理的一般變數宣告,會正常出現在編譯器的符號表中,並不是被前處理器直接替換掉的。
自編模擬試題 15 📌 概念:const 變數可取址,巨集常數不可
1  #include <stdio.h>
2  #define LIMIT 200
3  int main(void) {
4      const int MAXV = 100;
5      printf("%d\n", &MAXV != NULL);
6      printf("%d\n", &LIMIT != NULL);
7      return 0;
8  }
上述程式編譯時,行號 6 會發生錯誤,原因是下列何者?
本題標準答案:(B)
選項 A 錯誤
LIMIT 展開後只是數值常數 200,對它取址並不合法,並不能正常編譯執行。
選項 B 正確(★ 本題標準答案)
LIMIT 經前處理器代換後在程式中只是數值 200,&LIMIT 相當於 &200,對一個純數值字面值取址不合法(不是左值),因此在編譯階段就會報錯。
選項 C 錯誤
此問題在編譯階段就會被發現而無法產生執行檔,根本不會進入執行期。
選項 D 錯誤
這不是找不到定義的問題(LIMIT 本身確實有透過 #define 定義),而是取址對象不合法的編譯期錯誤,與連結無關。
自編模擬試題 16 📌 概念:#ifdef/#ifndef 條件式編譯
1  #include <stdio.h>
2  #define DEBUG 1
3  int main(void) {
4      int score = 60;
5  #ifdef DEBUG
6      printf("debug mode: score=%d\n", score);
7  #endif
8  #ifndef DEBUG
9      printf("release mode\n");
10 #endif
11     printf("final score=%d\n", score);
12     return 0;
13 }
執行後總共會印出幾行文字?
本題標準答案:(B)
選項 A 錯誤
實際上會有兩行輸出,不只 1 行。
選項 B 正確(★ 本題標準答案)
DEBUG 已定義,#ifdef DEBUG 區塊(行號 5-7)會被保留並印出一行;#ifndef DEBUG 區塊(行號 8-10)因為 DEBUG 已定義而被前處理器整段移除;行號 11 一定會執行。合計 2 行。
選項 C 錯誤
#ifndef DEBUG 那一段因為 DEBUG 已定義而不會被編譯進去,不會有第 3 行輸出。
選項 D 錯誤
此程式語法正確,可以順利編譯執行,不會產生編譯錯誤。
自編模擬試題 17 📌 概念:#if/#elif/#else 條件式編譯
1  #include <stdio.h>
2  #define VERSION 2
3  int main(void) {
4  #if VERSION == 1
5      printf("Version 1\n");
6  #elif VERSION == 2
7      printf("Version 2\n");
8  #else
9      printf("Unknown Version\n");
10 #endif
11     return 0;
12 }
關於上述程式使用 #if/#elif/#else 條件式編譯的敘述,下列何者正確?
本題標準答案:(B)
選項 A 錯誤
#if 是前處理階段(編譯之前)就已經判斷完成的指令,並非執行期才判斷。
選項 B 正確(★ 本題標準答案)
因為 VERSION 為 2,前處理器會在編譯之前就選定 #elif VERSION==2 對應的敘述保留下來,其餘分支會被整段移除,根本不會出現在編譯後的程式碼中。
選項 C 錯誤
#if 可以搭配關係運算子(如 ==、>、<)對數值進行比較,並不是只能用來檢查巨集是否被定義(那是 #ifdef/#ifndef 的用法)。
選項 D 錯誤
未被選中的分支在前處理階段就已經被移除,並不會被編譯進最終的執行檔中,因此不是「執行時才決定」。
自編模擬試題 18 📌 概念:除錯-連結錯誤 (Linker Error)
// calc.c
#include <stdio.h>

int add(int a, int b);   /* 只有函式原型宣告,沒有函式本體 */

int main(void) {
    int sum = add(3, 4);
    printf("%d\n", sum);
    return 0;
}
(假設整個專案中,任何檔案都沒有撰寫 add() 函式的實作內容。)此程式在建置(Build)過程中最可能於哪一個階段失敗?屬於下列哪一種錯誤型態?
本題標準答案:(B)
選項 A 錯誤
int add(int a, int b); 是完全合法的函式原型宣告語法,並沒有語法錯誤。
選項 B 正確(★ 本題標準答案)
編譯器只需要函式原型就能通過編譯(因為它已知函式的簽名),但連結器要把 main() 中呼叫 add() 的地方接上實際的函式本體時,卻在整個專案中找不到 add() 的定義,因而產生連結錯誤(常見訊息如 undefined reference)。
選項 C 錯誤
呼叫時傳入的引數數量(2 個)與原型宣告的參數數量相符,並沒有參數數量不對的問題。
選項 D 錯誤
程式已經正確 #include <stdio.h>,前處理階段不會失敗。
自編模擬試題 19 📌 概念:除錯-執行期錯誤 (Runtime Error)
1  #include <stdio.h>
2  int main(void) {
3      int a = 10, b = 0;
4      int result = a / b;
5      printf("%d\n", result);
6      return 0;
7  }
此程式可以順利通過編譯與連結,產生執行檔,但執行時卻會發生錯誤或不可預期的行為。其原因與錯誤型態為下列何者?
本題標準答案:(C)
選項 A 錯誤
a / b 在語法上是完全合法的除法運算式,並沒有語法錯誤。
選項 B 錯誤
除法是 C 語言內建的運算子,不需要額外連結任何函式庫,因此不會有連結錯誤。
選項 C 正確(★ 本題標準答案)
整數除以 0 在語法與語意上都合法(編譯器不會檢查除數的實際數值),只有在程式實際執行到該行、且 b 剛好為 0 時才會出問題,這種只在執行當下才顯現的錯誤正是執行期錯誤。
選項 D 錯誤
a 與 b 都宣告為 int,型別是一致的,並沒有型別不一致的語意錯誤。
自編模擬試題 20 📌 概念:綜合應用-前處理器基礎知識整合
小芳想要撰寫一個標頭檔 config.h,希望同時滿足以下三個需求:
① 避免被重複引入而造成重複定義錯誤 ② 定義一個不可修改的整數上限值 MAX_USERS,且此上限值需要有型別檢查 ③ 用巨集定義一個安全的平方函式 SQUARE(x),無論傳入 x + 1 或 x * 2 這類運算式都能得到正確結果。
下列四個 config.h 版本中,何者同時滿足以上三項需求?
本題標準答案:(C)
選項 A 錯誤
完全沒有 Include Guard(需求①未滿足);MAX_USERS 用 #define 定義且多加了分號(需求②未滿足);SQUARE(x) x*x 沒有加括號,代入運算式時可能出錯(需求③未滿足)。
選項 B 錯誤
有正確的 Include Guard(需求①滿足),MAX_USERS 也正確使用 const(需求②滿足),但 SQUARE(x) x*x 沒有加括號保護,遇到像 SQUARE(x+1) 這樣的呼叫仍會計算錯誤(需求③未滿足)。
選項 C 正確(★ 本題標準答案)
Include Guard 的位置正確地包住了整份檔案內容(需求①滿足);MAX_USERS 使用具型別檢查的 const 常數(需求②滿足);SQUARE(x) 定義為 ((x)*(x)),參數與整體都加了括號,能正確處理任何運算式引數(需求③滿足)。三項需求同時滿足。
選項 D 錯誤
雖然三種寫法都出現了,但 #ifndef/#define/#endif 只包住了一段空白內容,const int MAX_USERS 與 #define SQUARE 的宣告都寫在保護範圍之外,因此無法真正防止標頭檔被重複引入時造成重複定義(需求①未滿足)。