首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言深度解析:結構體與指針的進階操作

     編輯:關於C語言

前言:範例結構體定義

為了深入探討結構體與指針的交互機制,本文將以下兩個結構體作為核心範例:

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;

一、指針變量本身的左值與右值

1. 表達式 px 的右值

px 是一個指針變量。當表達式中不包含間接訪問操作符(如 *)時,其右值即為指針變量存儲的內容——也就是它指向的內存地址(此處為 &x)。

2. 表達式 px 的左值

左值代表該變量在內存中的儲存位置,意味著可以將新值賦給它(即修改指針指向)。

然而,px + 1 卻不是合法的左值。原因在於該表達式的結果是一個暫時的地址值,並不佔用可寻址的內存位置,無法被賦值。

關於 px + 1 的右值:若 px 指向結構體數組元素,則 px + 1 指向下一個結構體元素(地址增加 sizeof(EX))。但若 px 指向單獨的標量變量 x,該表達式雖可計算,卻指向未知內存區域,解引用屬於未定義行為。

二、通過指針訪問結構體

使用解引用操作符 * 可對指針進行間接訪問。

1. 表達式 *px 的右值

*px 代表指針指向的整個結構體對象。其用途包括:

  • 賦值給另一個同類型結構體變量(整體複製)。
  • 作為點操作符 . 的左操作數,訪問具體成員(如 (*px).a 或等價的 px->a)。
  • 作為函數參數傳遞(按值傳遞,會產生整體複製,效率較低)。
  • 作為函數返回值返回(同樣涉及整體複製,不推薦用於大型結構體)。

2. 表達式 *px 的左值

作為左值時,表示整個結構體將接受新值(即所有成員同時被賦新值)。關鍵在於它標識了一塊確定的內存區域。

*px + 1 是非法的,因為 C 語言未定義結構體與整數的加法運算。

*(px + 1):若 x 是數組元素,則訪問下一個元素;但本例中 x 為標量,故該表達式實際上非法。

三、結構體的內存存儲分配與對齊

  1. 成員順序分配:編譯器按聲明順序依次分配內存。為了滿足 CPU 訪問效率(對齊要求),成員之間可能插入填充字節。
  2. sizeof 包含填充:sizeof(EX) 返回的大小已包含所有填充字節。
  3. 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。

四、結構體作為函數參數:傳值 vs 傳指針

結構體屬於標量類型,可按值傳遞,但大型結構體傳值會導致棧上大量內存複製,效率低下。

案例:電子收銀機交易記錄

#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 並寫回結構體。

  1. 返回整個結構體:參數按值傳入,修改後按值返回。結構體被複製兩次,效率極低。
  2. 返回計算值:函數返回 float,調用者自行賦值。仍需按值傳入結構體,且無法同時修改多個成員。
  3. 傳遞指針(推薦):直接修改原結構體成員,無整體複製開銷,可同時修改多個字段。
void compute_total_amount(Transaction *trans) {
    trans->total_amount = trans->quantity * trans->unit_price;
}
// 調用:compute_total_amount(¤t_trans);

總結

  • 指針變量本身有左/右值,指針運算結果無左值。
  • *px 代表整個結構體,可整體複製、成員訪問、參數傳遞,但大結構體傳值效率低。
  • 結構體內存佈局遵循聲明順序與對齊規則,offsetof 可精確獲取成員偏移。
  • 函數參數傳遞大型結構體時,首選傳指針,必要時配合 const 保護數據;需修改成員時,傳指針是唯一高效且靈活的方案。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved