PAYATHON 2026

C 语言解析数组时,单级指针与二级指针传参有什么区别?

数据小陈

可以使用二级指针更新调用方的解析位置。对于字符输入,调用方可以持有 const char *cursor,并将 &cursor 传给接收 const char ** 的函数。

原代码使用的是 uint8_t *。对应的做法是另建一个 const uint8_t *cursor,再将它的地址传给接收 const uint8_t ** 的函数。这样可以推进游标,同时禁止通过该参数修改输入字节。

单级指针和二级指针分别改变什么

C 语言的函数参数都按值传递,二级指针也不例外。二级指针保存了调用方指针变量的地址,因此可以通过解引用修改该变量。

void advance_local(const char *p)
{
    ++p;       // 只改变局部副本
}

void advance_caller(const char **p)
{
    ++(*p);    // 改变调用方的指针变量
}

调用示例:

const char *cursor = "abc";

advance_local(cursor);    // cursor 仍指向 'a'
advance_caller(&cursor);  // cursor 现在指向 'b'

这里递增指针的前提是输入中还有可前进的位置。实际解析时还需要检查输入边界。

正确处理 const 和指针类型

在 const char **p 中,const 限定字符,因此可以修改指针 *p,但不能通过 **p 修改字符:

*p = next;  // next 类型匹配且指向有效位置时,可以更新游标
// **p = 'x';  // 不允许:字符通过此访问路径只读

不要将 char ** 直接传给接收 const char ** 的函数,也不要通过强制类型转换绕过类型检查。这两种类型不能安全地隐式转换;uint8_t ** 和 const uint8_t ** 同样如此。

先建立类型匹配的只读游标:

uint8_t *buffer = get_array_for_readings();
const uint8_t *cursor = buffer;

// 接收 const uint8_t ** 的解析函数应传入 &cursor。

后续解析使用 cursor,原来的 buffer 可以保留,用于记录输入起点或执行原有的资源管理。

const 只限制通过这个指针修改数据。如果其他地方仍持有可写指针,数据仍可能被修改。

一个带边界检查的提取示例

原问题没有给出字段格式和获取输入长度的方法。这里以逗号分隔的文本字段为例:第一次提取电阻,第二次提取电压。实际使用时,应根据真实协议判断字段边界。

函数接收输入剩余长度和目标数组容量,只在成功时更新游标:

#include <stdint.h>
#include <stddef.h>
#include <string.h>

static int extract_field(const uint8_t **cursor,
                         size_t *remaining,
                         char *dst,
                         size_t capacity)
{
    const uint8_t *p = *cursor;
    size_t n = 0;

    if (*remaining == 0 || capacity == 0) {
        return 0;
    }

    while (n < *remaining && p[n] != ',') {
        ++n;
    }

    // 为字符串终止符保留一个字节。
    if (n >= capacity) {
        return 0;
    }

    memcpy(dst, p, n);
    dst[n] = '\0';

    size_t consumed = n;
    if (n < *remaining) {
        ++consumed;  // 跳过逗号
    }

    *cursor = p + consumed;
    *remaining -= consumed;
    return 1;
}

int main(void)
{
    const uint8_t input[] = "120,3.3";
    const uint8_t *cursor = input;
    size_t remaining = sizeof(input) - 1;
    char ohms[15];
    char voltage[15];

    if (!extract_field(&cursor, &remaining, ohms, sizeof ohms)) {
        return 1;
    }

    if (!extract_field(&cursor, &remaining, voltage, sizeof voltage)) {
        return 1;
    }

    return 0;
}

示例假定字段是没有内嵌 \0 的文本,不处理引号或转义逗号。成功时函数会添加字符串终止符,不必预先清零整个目标数组;失败时游标不会推进,调用方必须检查返回值。

接入 get_array_for_readings() 时,需要另外取得有效输入长度。此时 buffer 是指针,不能用 sizeof(buffer) 得到数组长度。

原始调用还需要调整哪些地方

size_t size 是声明形式,不能直接写在函数调用的实参位置。调用时应传入具体表达式,例如用 sizeof ohms 表示目标容量。输入长度和目标容量含义不同,应分别传递。

如果保留 extract_ohm 和 extract_voltage 两个函数,可以让它们采用相同的参数结构:

int extract_ohm(const uint8_t **cursor, size_t *remaining,
                char *dst, size_t capacity);

int extract_voltage(const uint8_t **cursor, size_t *remaining,
                    char *dst, size_t capacity);

两次调用都传入 &cursor 和 &remaining,第二次就能从第一次更新后的位置继续解析。

也可以设计为返回更新后的指针,例如 cursor = extract_ohm(cursor, ...)。无论采用哪种接口,都需要明确失败时是否推进游标,并检查输入边界和输出容量。const char ** 是指向字符指针的指针,不等同于指向整个数组的指针;后者的类型形式是 const char (*)[N]。

备注:内容仅供参考。