為了深入探討結構體與指針的交互機制,本文將以下兩個結構體作為核心範例:
typedef struct {
int a;
short b[2];
} Ex2;
typedef struct EX {
int a;
char b[3];
Ex2 c;
struct EX *d;
} EX;
類型為 EX 的結構體在內存中的佈局可視為:成員 a、b、c(嵌套結構體)以及指向自身類型的指針 d 依序排列。典型的聲明與初始化方式如下:
EX x = {10, "hi", {5, {-1, 25}}, 0};
EX *px = &x;
px 的右值px 是一個指針變量。當表達式中不包含間接訪問操作符(如 *)時,其右值即為指針變量存儲的內容——也就是它指向的內存地址(此處為 &x)。
px 的左值左值代表該變量在內存中的儲存位置,意味著可以將新值賦給它(即修改指針指向)。
然而,px + 1 卻不是合法的左值。原因在於該表達式的結果是一個暫時的地址值,並不佔用可寻址的內存位置,無法被賦值。
關於 px + 1 的右值:若 px 指向結構體數組元素,則 px + 1 指向下一個結構體元素(地址增加 sizeof(EX))。但若 px 指向單獨的標量變量 x,該表達式雖可計算,卻指向未知內存區域,解引用屬於未定義行為。
使用解引用操作符 * 可對指針進行間接訪問。
*px 的右值*px 代表指針指向的整個結構體對象。其用途包括:
. 的左操作數,訪問具體成員(如 (*px).a 或等價的 px->a)。*px 的左值作為左值時,表示整個結構體將接受新值(即所有成員同時被賦新值)。關鍵在於它標識了一塊確定的內存區域。
*px + 1 是非法的,因為 C 語言未定義結構體與整數的加法運算。
*(px + 1):若 x 是數組元素,則訪問下一個元素;但本例中 x 為標量,故該表達式實際上非法。
sizeof 包含填充:sizeof(EX) 返回的大小已包含所有填充字節。offsetof 宏獲取偏移量:定義於 <stddef.h>,語法為 offsetof(type, member),返回成員距結構體起始地址的字節偏移量(類型為 size_t)。#include <stdio.h>
#include <stddef.h>
struct tiger {
char a;
int b;
char c;
};
int main() {
struct tiger x;
int m = offsetof(struct tiger, b); // 輸出 4(假設 int 4 字節對齊)
printf("%d\n", m);
return 0;
}
輸出 4:char a 佔 1 字節,為了讓 int b 4 字節對齊,編譯器在後面填充 3 字節,故 b 偏移量為 4。
結構體屬於標量類型,可按值傳遞,但大型結構體傳值會導致棧上大量內存複製,效率低下。
#define PRODUCT_SIZE 20
typedef struct {
char product[PRODUCT_SIZE];
int quantity;
float unit_price;
float total_amount;
} Transaction;
假設 int 和 float 均佔 4 字節,該結構體大小為 32 字節。
void print_receipt(Transaction trans) {
printf("%s\n", trans.product);
printf("%d @ %.2f\ntotal %.2f\n", trans.quantity, trans.unit_price, trans.total_amount);
}
// 調用:print_receipt(current_trans); // 複製 32 字節到棧
void print_receipt(Transaction *trans) {
printf("%s\n", trans->product);
printf("%d @ %.2f\ntotal %.2f\n", trans->quantity, trans->unit_price, trans->total_amount);
}
// 調用:print_receipt(¤t_trans); // 僅傳遞指針(4/8 字節)
優勢:指針體積遠小於結構體,棧開銷極小。代價是函數內需用 -> 間接訪問成員。結構體越大,優勢越明顯。
防範誤修改:若不希望函數修改原結構,應加 const 限定:
void print_receipt(const Transaction *trans);
需求:計算 total_amount = quantity * unit_price 並寫回結構體。
float,調用者自行賦值。仍需按值傳入結構體,且無法同時修改多個成員。void compute_total_amount(Transaction *trans) {
trans->total_amount = trans->quantity * trans->unit_price;
}
// 調用:compute_total_amount(¤t_trans);
*px 代表整個結構體,可整體複製、成員訪問、參數傳遞,但大結構體傳值效率低。offsetof 可精確獲取成員偏移。const 保護數據;需修改成員時,傳指針是唯一高效且靈活的方案。