關於 C 程式從原始碼到可執行檔案的建置流程,下列敘述何者正確?
選項 A 錯誤
前處理階段輸出的 .i 檔仍然是文字形式的原始碼(只是把 #include、#define 展開、註解移除),並不是可執行的二進位機器碼,機器碼要到組譯與連結階段才會產生。
選項 B 正確(★ 本題標準答案)
編譯器會對前處理後的原始碼進行語法分析,若有拼字錯誤或漏打分號等問題,就是在這個階段被偵測出來並報出 Syntax Error;語法完全正確才會轉換成組合語言(.s 檔)。
選項 C 錯誤
把多個目的檔(.o)與函式庫合併、解決外部符號參照,是「連結」階段的工作,不是組譯階段。組譯階段是把組合語言轉成機器碼的目的檔。
選項 D 錯誤
展開 #include 與替換 #define 都是「前處理」階段的工作,發生在最前面,而不是最後的連結階段。
上述程式編譯時會發生錯誤,最主要的原因是下列何者?
1 #include <stdio.h>
2 #define RATE 0.9;
3 int main(void) {
4 int price = 100;
5 double tax = price * RATE * 1.05;
6 printf("%.2f\n", tax);
7 return 0;
8 }選項 A 錯誤
price 是 int,乘上 double 的 0.9 會自動隱性提升為 double 運算,這裡不需要額外的顯式轉型,也不是本題編譯失敗的原因。
選項 B 錯誤
RATE 是使用者自訂的巨集名稱,並非 C 語言保留字,也沒有任何命名衝突。
選項 C 錯誤
一行程式碼中本來就可以出現多個乘號,例如 a * b * c 完全合法,這不是錯誤原因。
選項 D 正確(★ 本題標準答案)
#define RATE 0.9; 把結尾的分號也一起定義進巨集內容。展開後第 5 行變成 double tax = price * 0.9; * 1.05; ,前半段成為一個完整敘述,後面多出的「* 1.05;」不是合法的 C 敘述,造成編譯錯誤(實際以 gcc 測試會出現如「indirection requires pointer operand」之類的錯誤訊息)。這正是「巨集定義結尾不可加分號」鐵律的經典陷阱。
下列關於 #include 標頭檔引入與防重複引用機制的敘述,何者正確?
選項 A 錯誤
""形式的慣例用法通常是用來引入使用者自訂的標頭檔(會優先搜尋原始檔所在目錄),並非「只能」引入系統函式庫;語法上其實也可以用來引入系統標頭檔,只是不符慣例。
選項 B 正確(★ 本題標準答案)
不論 <> 或 "",#include 指令都是前處理器在前處理階段真正把該檔案的完整內容原封不動貼到指令所在的位置,兩者差別只在搜尋路徑順序不同,而不是只記錄檔名讓編譯器另外處理。
選項 C 錯誤
Include Guard 防的是「編譯期」同一個標頭檔內容被重複展開,導致 struct、typedef 等重複定義而編譯失敗,與程式執行階段呼叫函式的行為無關。
選項 D 錯誤
標頭檔內即使沒有函式定義,仍可能包含 struct、typedef、全域變數定義等內容,若被重複展開一樣可能發生重複定義的編譯錯誤,因此不能一概而論說不需要 Include Guard。
執行後輸出結果為何?
1 #include <stdio.h>
2 #define CUBE(x) x*x*x
3 int main(void) {
4 int a = 1;
5 printf("%d\n", CUBE(a + 1));
6 return 0;
7 }選項 A 錯誤
8 是「假設參數與整體都有括號保護」時 (a+1)^3 = 2^3 = 8 的結果,但 CUBE 巨集完全沒有加任何括號,實際不會得到這個值。
選項 B 錯誤
3 並非任何正確或常見誤解路徑會得到的結果,實際展開計算後的值是 4。
選項 C 正確(★ 本題標準答案)
CUBE(x) 定義為 x*x*x(沒有任何括號保護),CUBE(a + 1) 是純文字代換,逐字展開後變成 a + 1*a + 1*a + 1。依照運算子優先順序(* 先於 +),等於 a + (1*a) + (1*a) + 1;代入 a = 1 得 1 + 1 + 1 + 1 = 4(已用 gcc 實測驗證)。
選項 D 錯誤
這段程式在語法上完全合法(只是巨集展開後的運算結果不符合直覺),可以正常編譯與執行,不會產生編譯錯誤。
執行後輸出結果為何?
1 #include <stdio.h>
2 #define DOUBLE(x) (x) + (x)
3 int main(void) {
4 int result = 10 / DOUBLE(2 + 3);
5 printf("%d\n", result);
6 return 0;
7 }選項 A 錯誤
1 是「假設整個巨集展開結果也有外層括號保護」時 10 / ((2+3)+(2+3)) = 10 / 10 = 1 的結果,但 DOUBLE 巨集本體外層並沒有加括號,實際不會得到這個值。
選項 B 錯誤
這段程式語法上完全合法,可以正常編譯與執行,只是運算結果因括號保護不完整而不符合直覺,不會產生編譯錯誤。
選項 C 錯誤
2 只是 10/(2+3) 這一部分的結果,忽略了展開後多出來的 + (2+3) 這一段,並非完整的最終運算結果。
選項 D 正確(★ 本題標準答案)
DOUBLE(x) 雖然把參數 x 用括號保護成 (x) + (x),但整個替換結果外層沒有再加一層括號。10 / DOUBLE(2 + 3) 展開後變成 10 / (2 + 3) + (2 + 3),因為 / 優先權高於 +,等於 (10 / 5) + 5 = 2 + 5 = 7(已用 gcc 實測驗證),而不是把整個 DOUBLE(...) 當成一個完整的數值來除。
關於 const double TAX_RATE = 0.05; 與 #define TAX_RATE 0.05 兩種寫法的比較,下列敘述何者正確?
選項 A 錯誤
const 變數是編譯器管理的真正變數,具有實體記憶體位址,會佔用記憶體空間;只有 #define 巨集才不佔用記憶體(它只是程式碼中的即值常數)。
選項 B 正確(★ 本題標準答案)
#define 是前處理階段完成的純文字取代,編譯器根本看不到 TAX_RATE 這個識別字(早已被換成 0.05);const 變數則是正常參與編譯、具備型別檢查、擁有記憶體位址的唯讀變數,兩者處理階段與本質完全不同。
選項 C 錯誤
函式內部宣告的 const 變數,作用域遵循一般區域變數規則,只在該函式(區塊)內有效,離開區塊就會消失;#define 巨集則是從定義處開始一路有效到檔尾或遇到 #undef,兩者作用域規則並不相同。
選項 D 錯誤
#define 是純文字取代,完全沒有型別的概念,前處理器不會做任何型別檢查;具備型別安全檢查的其實是 const 變數,這個選項把兩者的特性說反了。
小明寫了以下程式,編譯與連結都順利通過,產生了執行檔,但程式運行到一半就當機中斷。上述程式發生當機的原因與錯誤型態屬於下列何者?
1 #include <stdio.h>
2 int main(void) {
3 int scores[5] = {90, 85, 77, 88, 95};
4 int i;
5 for (i = 0; i <= 5; i++) {
6 printf("%d\n", scores[i]);
7 }
8 return 0;
9 }選項 A 錯誤
i <= 5 在語法上完全合法(迴圈條件本來就可以自由設定比較條件),編譯器不會因此報錯,題目也已明確說明編譯順利通過。
選項 B 錯誤
scores 陣列有在同一個檔案中正確宣告並初始化,連結器不會找不到它;連結錯誤通常發生在呼叫了沒有實作本體的函式,與這裡的情境不符。
選項 C 正確(★ 本題標準答案)
scores 陣列宣告大小為 5,合法索引只有 0~4,但迴圈條件 i <= 5 讓 i 多跑一次到 5,導致 scores[5] 存取到陣列邊界之外的記憶體。這種錯誤在編譯、連結階段完全偵測不到,只有程式實際執行到該行時才會顯現(可能印出垃圾值甚至讓程式當機),是典型的執行期錯誤。
選項 D 錯誤
題目描述的是程式「運行到一半就當機中斷」,並非順利執行完畢但結果數值錯誤,因此不屬於邏輯錯誤,而是會造成當機的執行期錯誤。
關於 C 語言基本資料型態的記憶體大小與數值表示範圍,下列敘述何者正確?(假設為標準 32 位元 int 之系統環境)
選項 A 錯誤
short 標準大小是 2 Bytes(16 bits),範圍約 -32768~32767,比 4 Bytes 的 int(32 bits)小得多,並不相同。
選項 B 正確(★ 本題標準答案)
int 是有號整數,最高位元要保留給正負號,最大值為 2^31 - 1;unsigned int 沒有符號位元,全部 32 個位元都用來表示大小,最大值可達 2^32 - 1,明顯比 int 的最大值大。
選項 C 錯誤
char 的本質就是一個 1 Byte 的小型整數,內部儲存的其實就是 ASCII 碼數值,完全可以與整數直接進行加減運算(例如常見的大小寫轉換、數字字元轉數值)。
選項 D 錯誤
float 標準大小是 4 Bytes,有效位數約 6~7 位;double 才是 8 Bytes、有效位數約 15~17 位,精確度比 float 高,這個選項把兩者說反了。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 unsigned char uc = 250;
4 uc = uc + 10;
5 printf("%u\n", uc);
6 return 0;
7 }選項 A 錯誤
260 是 uc 提升為 int 之後、尚未存回 unsigned char 之前的中間計算結果,但這個值超出 unsigned char 0~255 的範圍,存回去時會被截斷,不會是最終輸出。
選項 B 錯誤
unsigned char 是無號型態,不會表示負數,%u 格式化輸出也一定是非負整數,不可能印出 -6。
選項 C 正確(★ 本題標準答案)
uc + 10 運算時,uc 會先整數提升為 int(250 + 10 = 260),但結果要存回 8 位元的 unsigned char 時只保留低 8 位元,發生「時鐘環繞」:260 mod 256 = 4(已用 gcc 實測驗證),編譯器不會對這種溢位主動發出警告或錯誤。
選項 D 錯誤
unsigned char 加法運算完全合法,這行程式不會產生任何編譯錯誤,只是結果因型態範圍限制而環繞。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 char grade = 'C';
4 char upgraded = grade - 2;
5 printf("%c\n", upgraded);
6 return 0;
7 }選項 A 正確(★ 本題標準答案)
char 的本質是儲存 ASCII 碼的小型整數,字元 'C' 的 ASCII 碼是 67,67 - 2 = 65,對照 ASCII 表,65 正是字元 'A'(已用 gcc 實測驗證)。
選項 B 錯誤
'B' 的 ASCII 碼是 66,若要從 'C'(67)算到 66 應該是減 1,而不是題目中的減 2,因此結果不會是 'B'。
選項 C 錯誤
小寫 'c' 的 ASCII 碼是 99,與 'C' - 2 = 65 差距很大,char 的算術運算不會自動在大小寫之間轉換,只有數值上的加減。
選項 D 錯誤
char 與整數常數的加減運算完全合法,這是 C 語言允許的標準操作,不會產生編譯錯誤。
在 64 位元系統上,依序輸出的三個數值為何?
1 #include <stdio.h>
2 int main(void) {
3 double *dptr;
4 int arr[5];
5 printf("%zu\n", sizeof(dptr));
6 printf("%zu\n", sizeof(*dptr));
7 printf("%zu\n", sizeof(arr));
8 return 0;
9 }選項 A 正確(★ 本題標準答案)
sizeof(dptr) 計算的是指標變數本身的大小,在 64 位元系統上任何型態的指標都是 8 Bytes;sizeof(*dptr) 對指標取值後計算的是它所指向的 double 型態大小,同樣是 8 Bytes;sizeof(arr) 因為 arr 是在同一個作用域中宣告的真正陣列(沒有當作函式參數傳遞而退化成指標),計算的是整個陣列的大小 5 × sizeof(int) = 20 Bytes(已用 gcc 實測驗證)。
選項 B 錯誤
sizeof(*dptr) 算的是 double 型態的大小(8 Bytes),不是 int 的 4 Bytes,這個選項誤把取值後的型態當成了 int。
選項 C 錯誤
這是常見誤解:把陣列 arr 誤當成指標來計算 sizeof,得到 8。但 arr 是宣告在本地的實際陣列,sizeof(arr) 算的是整個陣列佔用的位元組數 20,而不是指標大小 8。
選項 D 錯誤
sizeof(dptr) 算的是指標本身的大小,在 64 位元系統上是 8 Bytes,不是 4 Bytes;這個選項把第一與第二個數值弄反了。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 int total = 17, count = 5;
4 double avg1 = total / count;
5 double avg2 = (double)total / count;
6 printf("%.1f %.1f\n", avg1, avg2);
7 return 0;
8 }選項 A 正確(★ 本題標準答案)
avg1:total / count 是 int / int(17 / 5),先做整數除法無條件捨去小數得到 3,之後才把這個整數 3 轉型存入 double avg1,因此是 3.0;avg2:(double)total 先把 total 強制轉型成 17.0,再與 count 做浮點除法得到 3.4(已用 gcc 實測驗證)。「先截斷、後轉型」與「先轉型、後除法」得到完全不同的結果。
選項 B 錯誤
avg1 的關鍵是先做 int / int 的整數除法(會捨去小數),小數點在轉型成 double 之前就已經遺失了,並不會得到 3.4。
選項 C 錯誤
avg2 因為在除法之前就把 total 轉型成 double,是貨真價實的浮點除法 17.0 / 5,結果保留小數為 3.4,不會捨去成 3.0。
選項 D 錯誤
兩行程式碼的型態轉換與除法運算都完全合法,可以正常編譯與執行,不會產生編譯錯誤。
關於 C 語言中不同資料型態混合運算時的隱性型態轉換(Implicit Promotion)規則,下列敘述何者<u>錯誤</u>?
選項 A 錯誤
這是正確的敘述:char 與 short 在進行算術運算前,都會先經過整數提升(Integral Promotion)轉為 int,這也是隱性型態提升規則的一部分,並非本題要找的錯誤敘述。
選項 B 錯誤
這也是正確的敘述:int 與 double 混合運算時,int 會被自動轉為 double,讓整個運算式以 double 精度計算,符合「小容量轉大容量」原則,並非本題要找的錯誤敘述。
選項 C 正確(★ 本題標準答案)
這是本題要找的錯誤敘述:float 與 double 混合運算時,float 會直接被提升為 double(同樣是浮點數家族內的升級),而不會先轉成 int。若真的先轉成 int,小數部分會完全消失,明顯違反「保留精度、小轉大」的隱性提升原則。
選項 D 錯誤
這是正確的敘述:隱性型態提升的方向永遠是「容量或精度較小的型態自動轉為較大的型態」,例如 char/short → int → float → double,不會有大轉小的隱性提升,並非本題要找的錯誤敘述。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 int num = 26;
4 printf("%o %x\n", num, num);
5 return 0;
6 }選項 A 錯誤
八進位部分 32 正確,但 %x 是小寫格式指定字,只會輸出小寫的 "1a",不會輸出大寫的 "1A"(大寫需要使用 %X),這是本題設計的陷阱選項。
選項 B 錯誤
%o 與 %x 分別會把數值轉換成八進位與十六進位表示,不會直接照十進位原樣輸出 26,這個選項忽略了進位制轉換。
選項 C 錯誤
26 換算八進位是 32 而不是 33(3×8+2=26,並非 3×8+3=27);且 %x 輸出的是小寫字母,不會自動變成大寫 1A,除非改用 %X。
選項 D 正確(★ 本題標準答案)
num = 26,換算成八進位是 3×8 + 2 = 26,寫作 "32";換算成十六進位是 1×16 + 10 = 26,寫作 "1A",而 %x 是小寫格式指定字,因此輸出小寫 "1a"(已用 gcc 實測驗證)。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 int a = 3, b = 5;
4 int result = a << b - 2;
5 printf("%d\n", result);
6 return 0;
7 }選項 A 錯誤
這段程式語法上完全合法,位移運算子搭配變數運算是常見寫法,可以正常編譯與執行,不會產生編譯錯誤。
選項 B 錯誤
94 是誤以為 << 優先權比 - 高、先算 (a << b) 再減 2 的結果:(3 << 5) - 2 = 96 - 2 = 94,但實際上減法優先權更高,並非這樣的運算順序。
選項 C 錯誤
9 是誤把位移運算當成「直接乘以次方數本身」而非「乘以 2 的次方數」所得到的錯誤結果(3 × (5-2) = 9),忽略了位移是以 2 為底數的次方關係。
選項 D 正確(★ 本題標準答案)
減法 - 的優先權(第 4 級)高於位移運算子 << 的優先權(第 5 級),因此 a << b - 2 會先算 b - 2 = 3,再執行 a << 3。位移運算的意義是乘以 2 的次方數:3 << 3 = 3 × 2³ = 24(已用 gcc 實測驗證)。
執行後輸出結果依序為 result、a、b、c,下列何者正確?
1 #include <stdio.h>
2 int main(void) {
3 int a = 4, b = 2, c = 6;
4 int result = ++a * b-- + c;
5 printf("%d %d %d %d\n", result, a, b, c);
6 return 0;
7 }選項 A 錯誤
11 是誤把 b-- 當成前置遞增、用新值 1 參與運算所得到的結果(5 * 1 + 6 = 11),但 b-- 是後置遞增,運算時應該使用尚未遞減的舊值 2。
選項 B 錯誤
14 是誤把 ++a 當成後置遞增、用舊值 4 參與運算所得到的結果(4 * 2 + 6 = 14),但 ++a 是前置遞增,運算時應該已經使用新值 5。
選項 C 正確(★ 本題標準答案)
++a 是前置遞增:先讓 a 變成 5,再用新值 5 參與運算;b-- 是後置遞增:先用 b 的舊值 2 參與運算,運算結束後 b 才變成 1;c 全程不變為 6。因此 result = 5 * 2 + 6 = 16,運算後 a = 5、b = 1、c = 6(已用 gcc 實測驗證)。
選項 D 錯誤
result 的數值 16 是正確的,但這個選項誤以為 a、b 在運算後仍維持原始值 4 與 2;實際上 ++a 與 b-- 都會確實改變變數本身的值,運算後 a 應為 5、b 應為 1。
執行後輸出結果依序為 a & b、a | b、a ^ b,下列何者正確?
1 #include <stdio.h>
2 int main(void) {
3 int a = 12; // 1100
4 int b = 10; // 1010
5 printf("%d %d %d\n", a & b, a | b, a ^ b);
6 return 0;
7 }選項 A 正確(★ 本題標準答案)
a = 12(二進位 1100),b = 10(二進位 1010)。逐位元計算:AND 只有兩邊都是 1 才為 1,1100 & 1010 = 1000 = 8;OR 只要其中一邊是 1 就為 1,1100 | 1010 = 1110 = 14;XOR 相異為 1、相同為 0,1100 ^ 1010 = 0110 = 6(已用 gcc 實測驗證)。
選項 B 錯誤
22 是誤把 XOR 當成「兩數相加」所得到的結果(12+10=22),但位元 XOR 是逐位元判斷相異與否,正確結果應為 6。
選項 C 錯誤
22 同樣是把 AND 誤算成「兩數相加」的結果,位元 AND 應為逐位元皆為 1 才為 1,1100 & 1010 = 1000 = 8,而不是相加後的 22。
選項 D 錯誤
這個選項把 OR 與 XOR 的結果對調了:a | b 應為 14,a ^ b 應為 6,並非反過來的 6 與 14。
執行後的輸出結果為何?
1 #include <stdio.h>
2 int getValue(int x) {
3 printf("call getValue(%d)\n", x);
4 return x;
5 }
6 int main(void) {
7 int a = 0;
8 if (a != 0 && getValue(5) > 0) {
9 printf("true branch\n");
10 } else {
11 printf("false branch\n");
12 }
13 return 0;
14 }選項 A 正確(★ 本題標準答案)
邏輯 AND(&&)具有短路求值(Short-circuit Evaluation)特性:當左運算元 a != 0(此處 a = 0,結果為假)已經可以確定整個 && 運算式為假時,右運算元 getValue(5) > 0 就完全不會被求值,因此 getValue 函式不會被呼叫,程式直接執行 else 分支,只印出 false branch。
選項 B 錯誤
這個選項誤以為 && 兩邊一定都會被完整計算(如同位元 & 運算子那樣),但邏輯 && 具有短路特性,左邊為假時右邊的 getValue(5) 根本不會被呼叫,因此不會印出 call getValue(5)。
選項 C 錯誤
即使誤以為 getValue 會被呼叫,由於左邊 a != 0 為假,整個 && 運算式最終結果一定是假,不可能進入 true branch。
選項 D 錯誤
在 if 條件式中呼叫函式(只要該函式回傳可用於判斷真假的數值)是完全合法且常見的寫法,不會造成編譯錯誤。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 unsigned char x = 12; // 00001100
4 unsigned char y = 10; // 00001010
5 int result = (x & y) == 0 ? x | y : x ^ y;
6 printf("%d\n", result);
7 return 0;
8 }選項 A 錯誤
14 是條件為真時 x | y 的值(1100 | 1010 = 1110 = 14)。但先算出 x & y = 1000 = 8 後,判斷條件 (x & y) == 0 即 8 == 0,結果為假,因此並不會走到這個分支。
選項 B 錯誤
unsigned char 與位元運算、三元運算子的混合使用完全合法,可以正常編譯與執行,不會產生編譯錯誤。
選項 C 錯誤
8 只是條件判斷用到的中間值 x & y,並不是三元運算子最終要回傳的結果;三元運算子回傳的是條件為假時冒號後方 x ^ y 的值 6。
選項 D 正確(★ 本題標準答案)
先計算 x & y:00001100 & 00001010 = 00001000 = 8。判斷條件 (x & y) == 0 即 8 == 0,結果為假,因此執行冒號後方的 x ^ y:00001100 ^ 00001010 = 00000110 = 6(已用 gcc 實測驗證)。
關於 C 語言運算子優先順序與結合性,下列敘述何者正確?
選項 A 錯誤
前半句「關係/相等運算子優先權高於位元運算子」是對的,但這代表 == 會先被結合、先被算,正確解析應是 a ^ (b == c),而不是這個選項所說的「先算 a ^ b,再與 c 比較」,結論方向恰好相反。
選項 B 正確(★ 本題標準答案)
依照運算子優先順序表,三元運算子 ? : 與各種賦值運算子(=、+=…)都是「由右至左」結合;同時賦值運算子的優先權(數字較大、較低)確實低於條件運算子(數字較小、較高),因此這句敘述完全正確。
選項 C 錯誤
實際情況相反:加減運算子 +、- 的優先權其實高於位移運算子 <<、>>,因此 a + b << 1 應該先算 (a + b),再整體左移 1 位,而不是這個選項所說的「先算 b << 1」。
選項 D 錯誤
實際情況相反:位元 AND(&)的優先權其實高於邏輯 AND(&&),因此 x & y && z 應該先算 (x & y),再與 z 做邏輯 AND,而不是這個選項所說的「先算 y && z」。