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

C 語言高級指針專題:多級指針、函數參數退化、內存佈局與接口封裝實戰

     編輯:關於C語言

一、內存四區模型與函數調用

四區回顧

  • 棧區:函數幀、局部變量、參數、返回地址。自動分配釋放,向低地址增長。
  • 堆區:malloc/free 管理,向高地址增長,需手動釋放。
  • 全局/靜態區:全局變量、static 變量、字符串常量。程序生命週期。
  • 代碼區:二進制指令,只讀。

函數調用內存規則

  1. 主調函數棧區/堆區/全局區數據,被調函數均可訪問(通過參數、全局變量、指針)。
  2. 被調函數棧區分配的內存(局部變量、局部數組),返回後即失效,主調函數不可使用(野指針來源)。
  3. 被調函數堆區/全局區分配的內存,返回指針給主調函數安全有效。

二、多級指針作函數參數

核心原則

「指針也是變量,佔 4/8 字節」。無論多少級,函數參數傳遞的是指針變量的值(地址)。

級數與維度對應

  • 一級指針 T* → 操作一維數組/單個對象。
  • 二級指針 T** → 操作二維數組/修改一級指針值(輸出參數)。
  • 三級及以上 T*** → 極少見,通常表示更複雜的數據結構(如三維數組、指針鏈表頭指針的指針),不再簡單對應「維度」。

典型場景:修改指針指向

// 分配內存並返回給調用者
int alloc_buf(char **out_buf, int size) {
    *out_buf = malloc(size);
    return *out_buf ? 0 : -1;
}
// 調用
char *buf = NULL;
alloc_buf(&buf, 1024); // 傳遞一級指針地址(二級指針參數)

三、數組作函數參數的退化規則

void f(int a[10]);     // 退化為 void f(int *a);
void f(int a[]);       // 同上
void f(int a[3][4]);   // 退化為 void f(int (*a)[4]); // 第二維保留
void f(int a[][4]);    // 同上
void f(int a[2][3][4]);// 退化為 void f(int (*a)[3][4]); // 首維丟失,其餘保留

結論:函數參數無法直接傳遞任意維度數組,必須顯式傳遞除第一維外的所有維度大小,或改用扁平化一維數組 + 手動計算索引。

四、指針數組與數組指針實戰

定義對比

int *p[5];      // 指針數組:5 個 int*,可指向 5 個獨立數組/變量
int (*p)[5];    // 數組指針:指向一個包含 5 個 int 的數組
int (*p)[3][4]; // 指向 3x4 二維數組

應用:字符串數組

char *argv[] = {"prog", "arg1", "arg2"}; // 指針數組,每項指向字符串常量
// main 函數簽名
int main(int argc, char *argv[]); // 等價 char **argv

五、函數指針與回調機制

類型定義

// 返回 int,接受兩個 int 參數
typedef int (*BinaryOp)(int, int);
BinaryOp op = add; // 函數名隱式轉換為地址

回調實例:通用排序

void generic_sort(void *base, size_t n, size_t sz, int (*cmp)(const void*, const void*)) {
    // 冒泡演示
    for (size_t i = 0; i < n-1; i++)
        for (size_t j = 0; j < n-i-1; j++)
            if (cmp((char*)base + j*sz, (char*)base + (j+1)*sz) > 0)
                swap((char*)base + j*sz, (char*)base + (j+1)*sz, sz);
}
int int_cmp(const void *a, const void *b) { return *(int*)a - *(int*)b; }
generic_sort(arr, n, sizeof(int), int_cmp);

六、複雜聲明讀法:右左法則

  1. 從標識符開始,向右找 [] 或 (),再向左找 *。
  2. 遇到括號優先處理括號內內容。
  3. 重複直到結束。

練習

int (*p)[5];           // p 是指針,指向含 5 個 int 的數組
int *p[5];             // p 是數組,含 5 個指向 int 的指針
int (*(*p)[5])();      // p 是指針,指向含 5 個函數指針的數組,函數返回 int
int (*(*p)())[5];      // p 是指針,指向函數,函數返回指向含 5 個 int 數組的指針

七、接口封裝實戰:Socket 客戶端庫設計

設計目標

  • 隱藏實現細節(不透明指針)。
  • 支持多實例(句柄機制)。
  • 錯誤碼統一,資源自動管理。

頭文件

// sck_client.h
#ifndef SCK_CLIENT_H
#define SCK_CLIENT_H
#ifdef __cplusplus
extern "C" {
#endif

typedef struct SckClientHandle *SckClientHandle; // 不透明類型

int sckClient_init(SckClientHandle *handle);           // 返回 0 成功
int sckClient_send(SckClientHandle handle, const unsigned char *data, int len);
int sckClient_recv(SckClientHandle handle, unsigned char **out, int *outlen); // 內部分配
void sckClient_recv_free(unsigned char **buf);         // 配套釋放
int sckClient_destroy(SckClientHandle *handle);        // handle 置 NULL

#ifdef __cplusplus
}
#endif
#endif

關鍵點解析

  • typedef struct SckClientHandle *SckClientHandle; 前向聲明,對用戶隱藏結構體定義。
  • 輸出參數用二級指針 SckClientHandle *handle,函數內分配結構體並賦值 *handle = malloc(...)。
  • recv 返回動態分配的緩衝區,提供配套 recv_free 釋放,避免跨模塊 free 問題(不同 CRT 堆)。
  • destroy 接受二級指針,內部 free(*handle); *handle = NULL; 防止懸空指針。

八、總結

高級指針技巧的核心在於:

  1. 理解內存模型(棧/堆/全局)與生命週期。
  2. 掌握參數退化規則,正確設計函數接口。
  3. 善用多級指針實現輸出參數與不透明封裝。
  4. 熟練讀寫複雜聲明,避免類型錯誤。
  5. 接口設計遵循「誰分配誰釋放」,提供配套釋放函數。

這些能力是從 C 初級進階為系統級開發者的必經之路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved