linux c收到一串字符串格式如下:{|id|mac|ip|mask|gateway|dns1|dns2|} ,现在需要做一个函数 返回各数据的值,返回的结果是一个结构体,函数名是 extractNetworkData()
代码
#include <stdio.h>
#include <string.h>
typedef struct {
char id[32];
char mac[32];
char ip[32];
char mask[32];
char gateway[32];
char dns1[32];
char dns2[32];
} NetworkData;
NetworkData extractNetworkData(const char* input, int len) {
NetworkData data = {0}; // 所有字段初始化为空
if (!input || len <= 0 || len > 256) {
return data;
}
// 检查格式合法性
if (len < 4 || input[0] != '{' || input[1] != '|' ||
input[len-2] != '|' || input[len-1] != '}') {
return data;
}
// 复制中间内容到缓冲区(跳过首尾的"{|""|}")
char buf[257] = {0};
int content_len = len - 4; // 减去首尾4个字符
if (content_len > 0) {
memcpy(buf, input + 2, (content_len < 253) ? content_len : 253);
}
char* current = buf;
int fieldIndex = 0;
while (fieldIndex < 7) { // 固定解析7个字段
// 查找当前字段的结束分隔符
char* end = strchr(current, '|');
// 计算字段长度(处理末尾无分隔符的情况)
int field_len;
if (end == NULL) {
// 没有找到分隔符,取到字符串结束
field_len = strlen(current);
} else {
// 找到分隔符,取到分隔符前
field_len = end - current;
}
// 复制字段内容(如果有内容)
if (field_len > 0) {
// 防止缓冲区溢出
if (field_len >= sizeof(data.id)) {
field_len = sizeof(data.id) - 1;
}
// 根据索引复制到对应字段
switch (fieldIndex) {
case 0: memcpy(data.id, current, field_len); break;
case 1: memcpy(data.mac, current, field_len); break;
case 2: memcpy(data.ip, current, field_len); break;
case 3: memcpy(data.mask, current, field_len); break;
case 4: memcpy(data.gateway, current, field_len); break;
case 5: memcpy(data.dns1, current, field_len); break;
case 6: memcpy(data.dns2, current, field_len); break;
}
}
// 移动到下一个字段
if (end == NULL) {
break; // 没有更多分隔符,结束循环
} else {
current = end + 1; // 跳过当前分隔符
}
fieldIndex++;
}
return data;
}
// 测试用例
int main() {
const char* testStr = "{|device1|00:1A:2B:3C:4D:5E|192.168.1.100|255.255.255.0|||114.114.114.114|}";
int len = strlen(testStr);
NetworkData netData = extractNetworkData(testStr, len);
printf("ID: [%s]\n", netData.id); // device1
printf("MAC: [%s]\n", netData.mac); // 00:1A:2B:3C:4D:5E
printf("IP: [%s]\n", netData.ip); // 192.168.1.100
printf("Mask: [%s]\n", netData.mask); // 255.255.255.0
printf("Gateway: [%s]\n", netData.gateway); // 空
printf("DNS1: [%s]\n", netData.dns1); // 空
printf("DNS2: [%s]\n", netData.dns2); // 114.114.114.114
return 0;
}我来逐一分析这段代码的问题和优化点。
Bug 分析
Bug 1:fieldIndex++ 位置错误(主要 Bug)
// 当前代码的循环末尾:
if (end == NULL) {
break;
} else {
current = end + 1;
}
fieldIndex++; // ❌ 放在外面,end==NULL 时 break 前不会执行当最后一个字段没有末尾 | 时,break 跳出循环,fieldIndex++ 不执行——但这里其实无影响,因为 break 时字段已写入。真正的问题是:当 end != NULL 时,fieldIndex++ 在 current 移动之后才递增,逻辑上没有错,但应该移进 else 分支内,语义更清晰且不易出错。
Bug 2:len 边界检查过严,截断了合法数据
if (content_len > 0) {
memcpy(buf, input + 2, (content_len < 253) ? content_len : 253); // ❌
}buf 大小是 257,input + 2 最多复制 len - 4 字节。当 len = 256 时,content_len = 252,没问题。但如果 len = 257(允许进入函数),content_len = 253,而 buf[257] 完全装得下,这里用 253 作为硬上限是错的,应该用 sizeof(buf) - 1。
Bug 3:所有字段复制用同一个 sizeof(data.id)
if (field_len >= sizeof(data.id)) { // ❌ 对所有字段都用 id 的大小
field_len = sizeof(data.id) - 1;
}虽然此例中所有字段都是 char[32],但这是隐患。MAC 地址最长 17 字节没问题,但若字段大小不同(比如未来扩展),会导致截断错误。
Bug 4:len 类型为 int,但 strlen 返回 size_t
NetworkData extractNetworkData(const char* input, int len)调用处 int len = strlen(testStr) 会有隐式类型转换,在超长字符串时可能溢出(size_t → int)。
优化后的完整代码
#include <stdio.h>
#include <string.h>
#include <stddef.h>
typedef struct {
char id[32];
char mac[32];
char ip[64]; /* IP 最长 15 字节,留余量 */
char mask[32];
char gateway[32];
char dns1[32];
char dns2[32];
} NetworkData;
/* 字段元信息:指针偏移 + 各字段大小,避免硬编码 sizeof(data.id) */
typedef struct {
size_t offset;
size_t size;
} FieldMeta;
#define FIELD(s, m) { offsetof(s, m), sizeof(((s*)0)->m) }
NetworkData extractNetworkData(const char* input, size_t len)
{
NetworkData data;
memset(&data, 0, sizeof(data));
/* 基础校验 */
if (!input || len < 4 || len > 512) {
return data;
}
/* 格式校验:必须以 {| 开头,|} 结尾 */
if (input[0] != '{' || input[1] != '|' ||
input[len-2] != '|' || input[len-1] != '}') {
return data;
}
/* 字段元信息表,顺序对应协议字段 */
static const FieldMeta fields[7] = {
FIELD(NetworkData, id),
FIELD(NetworkData, mac),
FIELD(NetworkData, ip),
FIELD(NetworkData, mask),
FIELD(NetworkData, gateway),
FIELD(NetworkData, dns1),
FIELD(NetworkData, dns2),
};
/* 复制中间内容(去掉首部 "{|" 和尾部 "|}",共 4 字节) */
size_t content_len = len - 4;
char buf[513]; /* 与 len 上限对齐,多 1 字节存 '\0' */
memcpy(buf, input + 2, content_len);
buf[content_len] = '\0';
/* 逐字段解析 */
char* current = buf;
for (int i = 0; i < 7; i++) {
char* end = strchr(current, '|');
size_t field_len = (end != NULL) ? (size_t)(end - current) : strlen(current);
if (field_len > 0) {
/* 按各字段实际大小截断,防止溢出 */
size_t max_len = fields[i].size - 1; /* 保留 '\0' */
if (field_len > max_len) {
field_len = max_len;
}
char* dest = (char*)&data + fields[i].offset;
memcpy(dest, current, field_len);
/* dest[field_len] 已是 0,因为 memset 初始化过 */
}
if (end == NULL) break; /* 无更多分隔符 */
current = end + 1; /* 跳过 '|' 指向下一字段 */
}
return data;
}
/* -------- 测试 -------- */
int main(void)
{
/* 正常用例:gateway/dns1 为空 */
const char* t1 = "{|device1|00:1A:2B:3C:4D:5E|192.168.1.100|255.255.255.0|||114.114.114.114|}";
NetworkData d1 = extractNetworkData(t1, strlen(t1));
printf("=== Test 1 ===\n");
printf("ID: [%s]\n", d1.id);
printf("MAC: [%s]\n", d1.mac);
printf("IP: [%s]\n", d1.ip);
printf("Mask: [%s]\n", d1.mask);
printf("Gateway: [%s]\n", d1.gateway);
printf("DNS1: [%s]\n", d1.dns1);
printf("DNS2: [%s]\n", d1.dns2);
/* 边界用例:非法格式 */
const char* t2 = "device1|mac|ip|mask|||dns2}";
NetworkData d2 = extractNetworkData(t2, strlen(t2));
printf("\n=== Test 2 (invalid format, all empty) ===\n");
printf("ID: [%s]\n", d2.id);
/* 边界用例:NULL 输入 */
NetworkData d3 = extractNetworkData(NULL, 0);
printf("\n=== Test 3 (null input) ===\n");
printf("ID: [%s]\n", d3.id);
return 0;
}改动总结
| 问题 | 原代码 | 修复后 |
|---|---|---|
fieldIndex++ 语义不清 | 放在循环体外 | 移入 else 分支,改为 for 循环更简洁 |
buf 复制硬上限 253 | content_len < 253 | 与 buf 实际大小对齐 |
截断用 sizeof(data.id) | 所有字段共用 | 通过 FieldMeta 表各字段独立管理大小 |
len 类型 int | 可能截断 size_t | 改为 size_t |
| 字段大小硬编码 | case 0~6 switch | offsetof + 元信息表,扩展方便 |
buf 大小与 len 上限不匹配 | buf[257] 但 len 允许到 256 | buf[513] 与 len <= 512 对齐 |
最后一次更新于2026-03-24


