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]。
备注:内容仅供参考。
