本檔收錄本單元的練習題、完整解題思路與參考解答。
強烈建議先自己動手做:看完題目就闔上這份檔案,寫出你的版本、編譯、執行、卡住了再回來看「解題思路」;
真的做完了,再對照「參考解答」與「程式碼講解」。每題的完整程式碼也放在 exercises/ 資料夾,可直接編譯:
cc -std=c11 -Wall -Wextra -o /tmp/out exercises/ex1_circle.c && /tmp/out
練習 1:struct circle —— 巢狀結構與面積、周長(難度:基礎)
題目
定義 struct circle { struct point center; double radius; }(struct point 含 int x, y),
寫兩個函式計算圓的面積(πr²)與周長(2πr)。
用「圓心 (3,4)、半徑 5」與「單位圓」測試——單位圓的面積應接近 π、周長應接近 2π。
解題思路
- 資料表示:一個圓由「圓心位置」與「半徑」完全決定。
圓心本身就是一個點——直接把
struct point當成員(巢狀結構), 而不是把x、y、radius攤平在同一層。這樣點的相關操作(之後可能有)可以復用。 - 成員鏈的存取:
c1.center.x——.運算子從外往內一路點下去。 - 初始化:用 C99 指定初始化器最清楚:
{ .center = { .x = 3, .y = 4 }, .radius = 5.0 }——順序無關、一眼看懂哪個值是什麼。 - 函式介面選擇:
struct circle只有 16 bytes 左右,傳值即可 (K&R 的小結構慣例);傳值的好處是函式絕不可能改到呼叫端的圓。 - 公式直翻:面積
PI * r * r、周長2 * PI * r。PI用#define給足位數(C 標準庫沒有內建 π)。 - 驗證手段:單位圓(r=1)的面積就是 π 本人、周長就是 2π—— 拿「已知答案」的輸入當測資,是數值程式的基本測試技巧。
參考解答
/*
* ex1_circle.c
* ------------
* 練習 1:struct circle —— 巢狀結構與面積、周長計算(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex1_circle.c
* 執行:/tmp/out
*/
#include <stdio.h>
#define PI 3.14159265358979323846
struct point {
int x;
int y;
};
/* 巢狀結構:圓 = 圓心(一個 point)+ 半徑 */
struct circle {
struct point center;
double radius;
};
/* 面積 = πr²;小結構傳值即可(複製成本低、語意乾淨) */
double circle_area(struct circle c)
{
return PI * c.radius * c.radius;
}
/* 周長 = 2πr */
double circle_circumference(struct circle c)
{
return 2.0 * PI * c.radius;
}
int main(void)
{
/* C99 指定初始化器:順序無關、可讀性高 */
struct circle c1 = { .center = { .x = 3, .y = 4 }, .radius = 5.0 };
struct circle unit = { .center = { 0, 0 }, .radius = 1.0 };
printf("c1:圓心 (%d, %d),半徑 %.1f\n",
c1.center.x, c1.center.y, c1.radius);
printf(" 面積 = %.4f\n", circle_area(c1));
printf(" 周長 = %.4f\n", circle_circumference(c1));
printf("單位圓:\n");
printf(" 面積 = %.4f(應接近 π)\n", circle_area(unit));
printf(" 周長 = %.4f(應接近 2π)\n", circle_circumference(unit));
return 0;
}
程式碼講解
struct circle的定義必須出現在struct point之後—— 編譯器要知道成員center佔多大空間,前向宣告在這裡不夠用。c1.center.x:兩層.,由外而內。若手上是指標struct circle *pc, 則寫pc->center.x(第一層用箭頭、第二層center是結構本體所以用點)。- 面積函式用
c.radius * c.radius而非pow(c.radius, 2): 不必為平方拉進<math.h>與-lm。 - 易錯處:結構定義結尾的分號(
};)忘了會產生一連串莫名其妙的編譯錯誤, 錯誤訊息常指向下一行的函式。
範例輸出
c1:圓心 (3, 4),半徑 5.0
面積 = 78.5398
周長 = 31.4159
單位圓:
面積 = 3.1416(應接近 π)
周長 = 6.2832(應接近 2π)
練習 2:midpoint —— 回傳兩點的中點(難度:基礎)
題目
寫函式 struct point midpoint(struct point a, struct point b):回傳兩點的中點
(K&R makepoint 的風格:參數傳值、整個結構當回傳值)。
測試一般情況與「座標和為奇數」的情況(整數除法會取整),
並示範函式呼叫的串接:midpoint(a, midpoint(a, b)) 算四分之一點。
解題思路
- 介面模仿 K&R §6.2:
addpoint(struct point p1, struct point p2)整個結構進、 整個結構出。中點函式照這個模子刻。 - 數學:中點座標 = 兩端點座標的平均:
mid.x = (a.x + b.x) / 2;。 - 注意整數除法:
struct point的成員是int,(0 + 5) / 2得 2 不是 2.5。 這是資料表示的取捨:座標若需要精度應改用double成員; 本題保留 int 並在測試中明確展示取整行為,讓行為「可預期」。 - 結構回傳的機制:
return mid;會把整個結構複製給呼叫端。 區域變數mid雖然在函式結束後消失,但複製發生在消失之前——完全安全 (對比:回傳區域變數的指標才是懸空指標錯誤)。 - 串接呼叫:因為回傳的是值(不是指標),
midpoint(a, midpoint(a, b))可以像數學函數一樣組合,這是「傳值風格」最漂亮的地方。
參考解答
/*
* ex2_midpoint.c
* --------------
* 練習 2:midpoint —— 回傳兩點的中點(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex2_midpoint.c
* 執行:/tmp/out
*/
#include <stdio.h>
struct point {
int x;
int y;
};
/*
* K&R §6.2 makepoint 的風格:參數傳值、整個結構當回傳值。
* 中點 = 兩點座標的平均(int 相除會取整,這是本題要注意的地方)。
*/
struct point midpoint(struct point a, struct point b)
{
struct point mid;
mid.x = (a.x + b.x) / 2;
mid.y = (a.y + b.y) / 2;
return mid; /* 結構可以整個回傳(複製給呼叫端) */
}
static void show(const char *label, struct point p)
{
printf("%s = (%d, %d)\n", label, p.x, p.y);
}
int main(void)
{
struct point a = { 0, 0 };
struct point b = { 10, 20 };
struct point c = { -3, 7 };
show("midpoint((0,0), (10,20))", midpoint(a, b));
show("midpoint((10,20), (-3,7))", midpoint(b, c));
/* 奇數和:整數除法向零取整((0+5)/2 = 2) */
struct point d = { 5, 5 };
show("midpoint((0,0), (5,5)) ", midpoint(a, d));
/* 函式呼叫可以直接串接:中點的中點 */
show("四分之一點 ", midpoint(a, midpoint(a, b)));
return 0;
}
程式碼講解
return mid;:回傳的是複本。呼叫端show(..., midpoint(a, b))中, 回傳值直接當引數再傳值一次——結構在 C 裡是「一等公民的值」。(a.x + b.x) / 2:兩個很大的 int 相加可能溢位;教學情境的座標範圍內安全, 嚴謹寫法是a.x + (b.x - a.x) / 2(先取差再折半,不易溢位)。midpoint(a, midpoint(a, b)):內層回傳 (5,10),外層算 (0,0) 與 (5,10) 的中點 → (2,5)。注意 x 座標 5/2 取整為 2——輸出印證了整數除法。- 易錯處:想在函式內
a.x = ...修改呼叫端的點——改的是複本。 要修改就得傳指標(下一題就是)。
範例輸出
midpoint((0,0), (10,20)) = (5, 10)
midpoint((10,20), (-3,7)) = (3, 13)
midpoint((0,0), (5,5)) = (2, 2)
四分之一點 = (2, 5)
練習 3:rect_area —— 處理頂點順序顛倒的矩形(難度:基礎)
題目
寫函式 int rect_area(const struct rect *r) 計算矩形面積,
並確保它能處理「pt1/pt2 順序顛倒」的矩形(提示:用 min/max 正規化)。
測試四種情況:pt1 左下 pt2 右上(標準)、完全顛倒、左上/右下交錯、退化成線段。
解題思路
- 先識別問題:天真的
(pt2.x - pt1.x) * (pt2.y - pt1.y)只在 「pt1 是左下、pt2 是右上」時正確。頂點顛倒時寬或高變負數: - 完全顛倒:負 × 負 = 正(僥倖對); - 左上/右下交錯:正 × 負 = 負面積(錯!)。 所以「碰巧測一組資料通過」不代表正確——要窮舉四種相對位置。 - 正規化:寬 = x 座標的最大值 − 最小值,高同理。
imax(x1,x2) - imin(x1,x2)等價於abs(x1-x2),但 min/max 寫法 更清楚表達「先正規化再相減」的意圖,也順手寫了兩個小工具函式。 - 介面選擇:
struct rect含兩個點共 16 bytes,仍不大, 但這題刻意用const指標示範另一種慣例:不複製、且承諾不修改 (->運算子上場)。 - 退化情況:兩點同一水平線 → 高為 0 → 面積 0。數學上自然成立,不需特判。
參考解答
/*
* ex3_rect_area.c
* ---------------
* 練習 3:rect_area —— 能處理頂點順序顛倒的矩形面積(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex3_rect_area.c
* 執行:/tmp/out
*/
#include <stdio.h>
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
/* 小工具:min/max */
static int imin(int a, int b) { return (a < b) ? a : b; }
static int imax(int a, int b) { return (a > b) ? a : b; }
/*
* 面積 = 寬 × 高。
* 不假設 pt1 是左下、pt2 是右上——先用 min/max 正規化:
* 寬 = |x2 - x1|,高 = |y2 - y1|,用 max-min 一次搞定。
* 傳 const 指標:大結構不複製,且保證不修改。
*/
int rect_area(const struct rect *r)
{
int width = imax(r->pt1.x, r->pt2.x) - imin(r->pt1.x, r->pt2.x);
int height = imax(r->pt1.y, r->pt2.y) - imin(r->pt1.y, r->pt2.y);
return width * height;
}
int main(void)
{
/* 標準順序:pt1 = 左下,pt2 = 右上 */
struct rect normal = { { 0, 0 }, { 4, 3 } };
/* 顛倒順序:pt1 = 右上,pt2 = 左下 */
struct rect flipped = { { 4, 3 }, { 0, 0 } };
/* 交錯順序:pt1 = 左上,pt2 = 右下 */
struct rect crossed = { { 0, 3 }, { 4, 0 } };
/* 退化矩形:兩點同一條線上 */
struct rect line = { { 2, 5 }, { 9, 5 } };
printf("正常順序 面積 = %d\n", rect_area(&normal));
printf("完全顛倒 面積 = %d\n", rect_area(&flipped));
printf("左上右下 面積 = %d\n", rect_area(&crossed));
printf("退化成線段 面積 = %d\n", rect_area(&line));
return 0;
}
程式碼講解
r->pt1.x:混合運算子的讀法——r是指標所以第一層用->;pt1是結構本體(不是指標)所以第二層用.。 「有星號(指標)就用箭頭」的口訣在巢狀結構特別好用。- 四組測資的設計對應解題思路第 1 點的分析: 「左上右下」那組正是天真寫法會回 -12 的殺手案例,輸出證明正規化有效。
imin/imax宣告成static:檔案私有的小工具。 也可以用巨集#define MAX(a,b) ...,但函式版沒有重複求值的陷阱(第 10 單元詳談)。- 呼叫端傳
&normal:對區域結構變數取址,完全合法——它活到main結束。
範例輸出
正常順序 面積 = 12
完全顛倒 面積 = 12
左上右下 面積 = 12
退化成線段 面積 = 0
練習 4:top_student —— 回傳最高分學生的指標(難度:進階)
題目
建立 struct student { char name[20]; int score; } 的陣列(至少 5 筆寫死資料),
寫函式找出最高分者並回傳其指標:
const struct student *top_student(const struct student a[], int n);
n <= 0 時回傳 NULL。測試包含兩人同分的情況(應回傳先出現者),
並用指標相減算出最高分者在陣列中的索引。
解題思路
- 為什麼回傳指標而不是複本?
- 省複製(
struct student24 bytes,複製不貴但無必要); - 呼叫端拿到指標後可以直接讀原始資料、算索引(top - class)、 甚至修改那筆資料(若介面不加 const)。 回傳指標的安全前提:指向的資料在函式返回後仍然存在—— 這裡指向呼叫端的陣列,安全(對比:指向函式區域變數就是懸空指標)。 - 擂台法:
best先指向第一筆,走訪其餘元素,遇到更高分就把best換成&a[i]。 比較條件用>(嚴格大於):同分時不換人,自然保留「先出現者」。 - 防衛:
n <= 0時陣列裡沒有東西,回傳 NULL;呼叫端使用前檢查 (第 01 單元 safe_deref 的紀律又出現了)。 - const 的一致性:輸入是
const struct student a[], 回傳型別也得是const struct student *——不能經由回傳值「偷渡」出可寫指標。
參考解答
/*
* ex4_top_student.c
* -----------------
* 練習 4:結構陣列 —— 找出最高分的學生並回傳其指標(難度:進階)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex4_top_student.c
* 執行:/tmp/out
*/
#include <stdio.h>
struct student {
char name[20];
int score;
};
/*
* 回傳「指向最高分那筆資料的指標」而不是複本:
* - 不複製整個結構(省成本)
* - 呼叫端可經由指標直接讀(甚至改)原始資料
* n <= 0 時回傳 NULL(防衛)。
*/
const struct student *top_student(const struct student a[], int n)
{
if (n <= 0)
return NULL;
const struct student *best = &a[0]; /* 先假設第一筆最高 */
for (int i = 1; i < n; i++)
if (a[i].score > best->score) /* 走訪擂台法 */
best = &a[i];
return best;
}
int main(void)
{
struct student class[] = {
{ "Alice", 88 },
{ "Bob", 72 },
{ "Charlie", 95 },
{ "Diana", 95 }, /* 與 Charlie 同分:擂台法保留先出現者 */
{ "Eve", 61 },
};
int n = (int)(sizeof class / sizeof class[0]);
printf("全班成績:\n");
for (int i = 0; i < n; i++)
printf(" %-8s %3d\n", class[i].name, class[i].score);
const struct student *top = top_student(class, n);
if (top != NULL) {
printf("最高分:%s(%d 分)\n", top->name, top->score);
/* 用指標相減可知是第幾筆 */
printf("(陣列中的索引:%td)\n", top - class);
}
/* 邊界:空陣列 */
printf("空陣列 -> %s\n",
(top_student(class, 0) == NULL) ? "NULL(正確防衛)" : "錯誤");
return 0;
}
程式碼講解
a[i].score > best->score:左邊a[i]是結構本體用.; 右邊best是指標用->——同一行出現兩種存取運算子,正好對照。best = &a[i];:換人只是換一個位址,不搬結構—— 和上一單元「排序字串只換指標」是同一種省力哲學。top - class:兩個指向同一結構陣列的指標相減,得到相隔的元素數 (編譯器自動除以sizeof(struct student)),印出用%td。- 同分測試(Charlie/Diana 都是 95)驗證了
>的「先到先贏」語意; 若想要「後出現者贏」才改用>=——一個字元的差異要靠測試釘住。 - 易錯處:把函式寫成回傳
struct student *(沒 const)卻收 const 陣列—— 編譯器會抓 const 流失。
範例輸出
全班成績:
Alice 88
Bob 72
Charlie 95
Diana 95
Eve 61
最高分:Charlie(95 分)
(陣列中的索引:2)
空陣列 -> NULL(正確防衛)
練習 5:sort_by_score —— 結構陣列排序(難度:進階)
題目
寫函式 void sort_by_score(struct student a[], int n):
按分數由高到低排序結構陣列(交換單位是整個結構)。
測試資料至少 6 筆、包含同分者。
解題思路
- 和字串排序的差別:上一單元排字串換的是指標;
這裡的陣列直接存放結構本體,交換就得搬整個結構。
好消息:C 的結構支援整體指派
a[i] = a[best];—— 編譯器把所有成員(含name這個字元陣列!)一起複製, 不需要strcpy逐成員搬。 - 演算法:選擇排序——每輪從未排序區找分數最高者換到最前面。
由高到低只需把比較方向設為
a[j].score > a[best].score。 - 交換三行:臨時變數的型別是
struct student(不是指標):
struct student tmp = a[i];
a[i] = a[best];
a[best] = tmp;
- 同分的行為:選擇排序在這個寫法下同分者保持相對順序與否並無保證 (選擇排序本身不穩定);測試裡 Diana 與 Frank 同 80 分, 輸出顯示 Diana 在前(這次剛好維持原順序)。若業務上需要穩定排序, 要改用插入排序或在比較中加入第二鍵(如姓名)。
參考解答
/*
* ex5_sort_by_score.c
* -------------------
* 練習 5:sort_by_score —— 按分數由高到低排序結構陣列(難度:進階)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex5_sort_by_score.c
* 執行:/tmp/out
*/
#include <stdio.h>
struct student {
char name[20];
int score;
};
/*
* 選擇排序,由高到低。
* 交換的單位是「整個結構」——結構可以直接用 = 整體指派,
* 編譯器會把所有成員(含 name 陣列)一起複製。
*/
void sort_by_score(struct student a[], int n)
{
for (int i = 0; i < n - 1; i++) {
int best = i; /* 找 [i, n) 中分數最高者 */
for (int j = i + 1; j < n; j++)
if (a[j].score > a[best].score)
best = j;
if (best != i) { /* 整個結構三行交換 */
struct student tmp = a[i];
a[i] = a[best];
a[best] = tmp;
}
}
}
static void print_all(const char *title, const struct student a[], int n)
{
printf("%s\n", title);
for (int i = 0; i < n; i++)
printf(" %d. %-8s %3d\n", i + 1, a[i].name, a[i].score);
}
int main(void)
{
struct student class[] = {
{ "Alice", 88 },
{ "Bob", 72 },
{ "Charlie", 95 },
{ "Diana", 80 },
{ "Eve", 61 },
{ "Frank", 80 }, /* 與 Diana 同分 */
};
int n = (int)(sizeof class / sizeof class[0]);
print_all("排序前:", class, n);
sort_by_score(class, n);
print_all("排序後(由高到低):", class, n);
return 0;
}
程式碼講解
struct student tmp = a[i];:宣告即複製整個結構。name陣列是結構的一部分,跟著整包複製——這是「陣列不能指派、 但包在結構裡的陣列可以跟著結構指派」的有趣角落。- 降冪只差在比較方向;初學者常犯的錯是改成
<之後忘了變數名best已不再是「最小」—— 命名和語意要一起改,否則三個月後讀不懂。 - 交換前的
if (best != i)是小優化:自己跟自己交換沒有意義。 - 這裡是 O(n²) 的教學實作;真實程式排大陣列該用
qsort(標準庫)+ 自訂比較函式——那需要函式指標,屬於進階主題的預告。
範例輸出
排序前:
1. Alice 88
2. Bob 72
3. Charlie 95
4. Diana 80
5. Eve 61
6. Frank 80
排序後(由高到低):
1. Charlie 95
2. Alice 88
3. Diana 80
4. Frank 80
5. Bob 72
6. Eve 61
練習 6:padding 觀察 —— 成員順序影響結構大小(難度:挑戰)
題目
比較 struct { char a; int b; char c; } 與 struct { int b; char a; char c; } 的 sizeof,
用 offsetof(<stddef.h>)印出每個成員的實際位移,解釋兩者差異的原因,
並計算「一百萬筆資料的陣列」兩種佈局各占多少記憶體。
解題思路
- 背景:CPU 存取「對齊」的資料較快(4-byte 的 int 要放在 4 的倍數位址上), 編譯器會在成員間插入填補位元組(padding)來滿足對齊。
- 推演版本 A(
char a; int b; char c;)在 64 位元 macOS 上的佈局: -a放 offset 0(1 byte); -b需要 4-byte 對齊 → 跳過 offset 1..3(3 bytes padding)→ 放 offset 4..7; -c放 offset 8(1 byte); - 整個結構的大小必須是「最大對齊需求(4)」的倍數 (陣列中下一個元素的b才能對齊)→ 尾端再補 3 bytes → sizeof = 12。 - 推演版本 B(
int b; char a; char c;): -b放 offset 0..3(天然對齊,零浪費); -a放 offset 4、c放 offset 5(char 對齊需求 1,緊緊相鄰); - 尾端補 2 bytes 湊成 4 的倍數 → sizeof = 8。
bad : [a][-][-][-][B][B][B][B][c][-][-][-] = 12 bytes(6 bytes 是 padding)
good: [B][B][B][B][a][c][-][-] = 8 bytes(2 bytes 是 padding)
- 驗證工具:
sizeof看總大小、offsetof(型別, 成員)看每個成員的實際位移—— 兩者都是編譯期常數,程式印出來對照推演。 - 實務結論:「成員由大到小排列」通常最省; 單筆差 4 bytes 不痛,乘上一百萬筆就是 4 MB——結構佈局是免費的效能優化。
參考解答
/*
* ex6_padding.c
* -------------
* 練習 6:padding 觀察 —— 成員順序如何影響結構大小(難度:挑戰)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex6_padding.c
* 執行:/tmp/out
*/
#include <stdio.h>
#include <stddef.h> /* offsetof */
/* 版本 A:char / int / char 交錯——padding 最多 */
struct bad_layout {
char a; /* 1 byte,後面補 3 bytes 讓 b 對齊到 4 */
int b; /* 4 bytes */
char c; /* 1 byte,後面補 3 bytes 讓整體大小是 4 的倍數 */
};
/* 版本 B:把大成員放前面——padding 最少 */
struct good_layout {
int b; /* 4 bytes,天然對齊 */
char a; /* 1 byte */
char c; /* 1 byte,尾端只需補 2 bytes */
};
int main(void)
{
printf("struct bad_layout { char a; int b; char c; }\n");
printf(" sizeof = %zu bytes(成員總和只有 6!)\n",
sizeof(struct bad_layout));
printf(" offsetof(a) = %zu\n", offsetof(struct bad_layout, a));
printf(" offsetof(b) = %zu(a 之後跳過 3 bytes 的 padding)\n",
offsetof(struct bad_layout, b));
printf(" offsetof(c) = %zu(尾端還有 3 bytes padding)\n\n",
offsetof(struct bad_layout, c));
printf("struct good_layout { int b; char a; char c; }\n");
printf(" sizeof = %zu bytes(省下 4 bytes)\n",
sizeof(struct good_layout));
printf(" offsetof(b) = %zu\n", offsetof(struct good_layout, b));
printf(" offsetof(a) = %zu\n", offsetof(struct good_layout, a));
printf(" offsetof(c) = %zu(a、c 緊緊相鄰,尾端補 2 bytes)\n\n",
offsetof(struct good_layout, c));
/* 佈局示意(B=b 的 byte,-=padding):
* bad : a - - - B B B B c - - - = 12 bytes
* good: B B B B a c - - = 8 bytes */
printf("記憶體佈局示意:\n");
printf(" bad : [a][-][-][-][B][B][B][B][c][-][-][-] = 12\n");
printf(" good: [B][B][B][B][a][c][-][-] = 8\n\n");
/* 陣列會放大差距:一百萬筆資料差 4 MB */
printf("一百萬筆的陣列大小:bad = %zu bytes,good = %zu bytes\n",
1000000 * sizeof(struct bad_layout),
1000000 * sizeof(struct good_layout));
return 0;
}
程式碼講解
offsetof(struct bad_layout, b)回傳 4:直接證明a後面躺著 3 bytes 的 padding。offsetof是標準提供的巨集,比手動用指標運算猜位移可靠且可攜。- 「結構大小必須是最大成員對齊需求的倍數」的原因:
struct bad_layout arr[2]中,arr[1].b也要對齊到 4—— 只有整個結構大小是 4 的倍數才能保證。 sizeof的結果型別是size_t,印出用%zu。- 兩個結構的成員總和都是 6 bytes,佈局卻差出一倍——
永遠用
sizeof,不要手加成員大小(做二進位 I/O 或 malloc 時尤其致命)。 - 注意這些數字是「本平台(Apple clang / arm64)」的結果; 別的 ABI 可能不同,跨平台傳輸不能依賴結構佈局(第 11 單元會再強調)。
範例輸出
struct bad_layout { char a; int b; char c; }
sizeof = 12 bytes(成員總和只有 6!)
offsetof(a) = 0
offsetof(b) = 4(a 之後跳過 3 bytes 的 padding)
offsetof(c) = 8(尾端還有 3 bytes padding)
struct good_layout { int b; char a; char c; }
sizeof = 8 bytes(省下 4 bytes)
offsetof(b) = 0
offsetof(a) = 4
offsetof(c) = 5(a、c 緊緊相鄰,尾端補 2 bytes)
記憶體佈局示意:
bad : [a][-][-][-][B][B][B][B][c][-][-][-] = 12
good: [B][B][B][B][a][c][-][-] = 8
一百萬筆的陣列大小:bad = 12000000 bytes,good = 8000000 bytes
常見錯誤解法對照
| 錯誤寫法 | 症狀 | 正確做法 |
|---|---|---|
pc.center.x(pc 是指標) |
編譯錯誤:指標要用 -> |
pc->center.x |
| 傳值函式內改結構成員 | 呼叫端完全沒變 | 傳指標 f(struct point *p) + p->x |
天真面積 (x2-x1)*(y2-y1) |
頂點交錯時得到負面積 | min/max 正規化寬與高 |
| 回傳函式內區域結構的指標 | 懸空指標,未定義行為 | 回傳結構值,或回傳指向呼叫端資料的指標 |
交換結構用 strcpy(a[i].name, ...) 逐成員搬 |
冗長且易漏成員 | 整體指派 a[i] = a[best]; |
手加成員大小當 sizeof |
忽略 padding,buffer 算錯 | 一律用 sizeof/offsetof |