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_tint)。


优化后的完整代码

#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 复制硬上限 253content_len < 253buf 实际大小对齐
截断用 sizeof(data.id)所有字段共用通过 FieldMeta 表各字段独立管理大小
len 类型 int可能截断 size_t改为 size_t
字段大小硬编码case 0~6 switchoffsetof + 元信息表,扩展方便
buf 大小与 len 上限不匹配buf[257]len 允许到 256buf[513]len <= 512 对齐