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

C語言多級指針深度解析:從 argv 到三級指針的實戰應用

     編輯:C++ 入門

前言:指針的指針,到底在指什麼?

C 語言的指針機制靈活強大,但「多級指針」常讓初學者望而生畏。本文從 main 的 argv 切入,層層遞進至字串分割函式 makeargv 的三級指針 char ***argvp 設計,透過記憶體圖解與完整程式碼,徹底釐清多級指針的實際運作邏輯與應用場景。

一、二級指針 char **argv:命令列參數的標準映射

int main(int argc, char **argv) { ... }
// 等價於
int main(int argc, char *argv[]) { ... }

當執行 prog -c -v 200 時,記憶體佈局如下:

argv (char **) ──► +----+    +----------+
                    | *0 |──► | "prog\0" |
                    +----+    +----------+
                    | *1 |──► | "-c\0"   |
                    +----+    +----------+
                    | *2 |──► | "-v\0"   |
                    +----+    +----------+
                    | *3 |──► | "200\0"  |
                    +----+    +----------+
                    | *4 | = NULL (哨兵)
                    +----+
  • argv[0] ~ argv[argc-1] 指向各參數字串(長度不一)。
  • argv[argc] == NULL 為標準哨兵,方便迴圈遍歷。

二、三級指針 char ***argvp:動態建立參數向量

若要編寫一個函式,將單一字串 "mine -c 10 2.0" 依分隔符 " \t" 分割成類 argv 結構,並回傳給呼叫者,簽名必須為:

int makeargv(const char *s, const char *delimiters, char ***argvp);

為何需要三級指針?

  1. 呼叫者提供 char **my_argv 變數(二級指針)。
  2. 函式需修改呼叫者的指標值(指向新配置的陣列),故參數必須是 char ***。
  3. 函式內以 *argvp = malloc(...) 寫入新陣列位址,呼叫者即可透過 my_argv 存取結果。

三、makeargv 完整實作與逐行解析

#include <stdlib.h>
#include <string.h>
#include <errno.h>

int makeargv(const char *s, const char *delimiters, char ***argvp) {
    if (!s || !delimiters || !argvp) { errno = EINVAL; return -1; }

    *argvp = NULL;                          // 1. 先歸零,失敗時安全
    char *t = malloc(strlen(s) + 1);        // 2. 複製字串,因 strtok 會破壞原串
    if (!t) return -1;
    strcpy(t, s);

    // 3. 先數 token 數量
    int numtokens = 0;
    if (strtok(t, delimiters))              // 第一次呼叫
        for (numtokens = 1; strtok(NULL, delimiters); numtokens++);

    // 4. 配置指標陣列 (+1 給尾端 NULL)
    *argvp = malloc((numtokens + 1) * sizeof(char *));
    if (!*argvp) { free(t); return -1; }

    if (numtokens == 0) { free(t); return 0; }

    // 5. 重新分割並填入指標
    strcpy(t, s);                           // 重置 t
    *(*argvp) = strtok(t, delimiters);      // argvp[0]
    for (int i = 1; i < numtokens; i++)
        *(*argvp + i) = strtok(NULL, delimiters); // argvp[i]
    *(*argvp + numtokens) = NULL;           // 哨兵

    return numtokens;
}

關鍵指針操作解析:

  • *argvp:解參考一次,得到呼叫者的 char ** 變數位址。
  • *(*argvp + i):等價 (*argvp)[i],寫入第 i 個 char *。
  • strtok 回傳的指標指向 t 內部,故 t 必須存活到呼叫者釋放 *argvp 為止。

四、呼叫端範例與記憶體釋放責任

int main(void) {
    char *test = "mine -c 10 2.0";
    char delim[] = " \t";
    char **argvp = NULL;
    int n = makeargv(test, delim, &argvp);
    if (n == -1) { perror("makeargv"); return 1; }

    for (int i = 0; i < n; i++)
        printf("%d: %s\n", i, argvp[i]);

    // 釋放順序:先釋放指標陣列指向的字串緩衝 (argvp[0] 所屬的 t),再釋放指標陣列
    free(argvp[0]);  // 實際上指向 makeargv 內部的 t
    free(argvp);
    return 0;
}

注意:上述實作將所有 token 指向同一塊緩衝區 t,釋放時只需 free(argvp[0]);若改為各自 strdup,則需逐一釋放。

五、延伸場景:Shell 歷史命令儲存 char ****?

若 Shell 需支援「多行命令歷史」,每行又是一個 char **,則需 char ***history;若歷史本身可動態增減,函式參數便成 char ****。雖語法合法,但實務上建議封裝結構體:

typedef struct { int argc; char **argv; } Command;
Command *history = malloc(max_history * sizeof(Command));

以結構體取代多級指針,可大幅降低認知負擔與除錯難度。

六、總結

層級型別典型用途記憶體語意
1char *單一字串指向字元陣列
2char **字串陣列 (argv)指向指標陣列,每元素指向字串
3char ***輸出參數:建立字串陣列指向呼叫者的 char ** 變數

掌握「誰配置、誰釋放」與「指標層級對應修改權」兩大原則,多級指針不再是噩夢,而是構建動態資料結構的利器。

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