C语言中使用strtok处理IP范围时跳过第二个令牌的问题排查
C语言中使用strtok处理IP范围时跳过第二个令牌的问题排查
嗨,我来帮你揪出问题所在!
核心原因:strtok的静态状态被嵌套调用覆盖了
strtok函数是非线程安全的,它内部用一个静态变量来记录当前解析的位置。当你在splitOnDash里用strtok分割'-',然后调用splitOnDot时,splitOnDot里的strtok会重置并覆盖这个静态状态——相当于把之前处理IP范围的进度给“擦除”了。
具体流程是这样的:
splitOnDash中第一次调用strtok(ipStr, "-"),拿到第一个IP段,此时strtok的静态状态指向ipStr中'-'的位置。- 你复制这个IP段到
fullIpCopy,然后调用splitOnDot(fullIpCopy)。 splitOnDot里的strtok(fullIpCopy, ".")会把strtok的内部状态改成处理fullIpCopy的位置,原来处理ipStr的进度完全丢失。- 回到
splitOnDash再调用strtok(NULL, "-")时,strtok会从splitOnDot最后处理的位置(也就是fullIpCopy的末尾)找'-',自然找不到,返回NULL,导致第二个IP段根本没被处理。
额外的小bug
还有个容易忽略的错误:splitOnDot里的if(endptr == ipStr)应该写成if(endptr == ipTokens),因为endptr是相对于当前令牌ipTokens的指针,不是整个ipStr的,这个错误会导致一些合法IP被误判为无效。
另外,你定义的char fullIpCopy [] = "tempString";数组大小只有11,而IPv4最长是15个字符(比如255.255.255.255),复制时会出现缓冲区溢出,得改成足够大的数组,比如char fullIpCopy[16];。
解决方案:用可重入的strtok_r(推荐)
POSIX标准提供了strtok_r,它需要你手动传入一个指针来保存解析状态,不同的解析上下文不会互相干扰。
修改后的splitOnDot:
// 接收不含'-'的字符串,按'.'分割、转整数并验证 int splitOnDot(char* ipStr){ char *saveptr; // 保存strtok_r的解析状态 char *ipTokens = strtok_r(ipStr, ".", &saveptr); int xxxCount = 0; while(ipTokens){ char *endptr; long int xxxIp; xxxIp = strtol(ipTokens, &endptr, 10); if(endptr == ipTokens){ // 修正判断对象 return 0; } else if (*endptr != '\0'){ return 0; } else{ if(xxxIp >= 0 && xxxIp <= 255){ xxxCount += 1; } else{ return 0; } } ipTokens = strtok_r(NULL, ".", &saveptr); } return (xxxCount == 4) ? 1 : 0; }
修改后的splitOnDash:
// 接收'x.x.x.x-x.x.x.x'格式的字符串,分割并验证两个IP int splitOnDash(char* ipStr){ char *dashSaveptr; char *fullIp = strtok_r(ipStr, "-", &dashSaveptr); char fullIpCopy[16]; // 足够容纳最长IPv4字符串 while(fullIp){ strncpy(fullIpCopy, fullIp, sizeof(fullIpCopy)-1); fullIpCopy[sizeof(fullIpCopy)-1] = '\0'; // 确保字符串结束符 int validIp = splitOnDot(fullIpCopy); if(validIp == 0){ return 0; } fullIp = strtok_r(NULL, "-", &dashSaveptr); } return 1; }
备选方案:手动解析字符串(无需strtok)
如果你的环境不支持strtok_r,可以用strchr手动查找分隔符,避免依赖strtok的静态状态:
// 手动分割IP段验证 int splitOnDot(char* ipStr){ char *ptr = ipStr; int count = 0; while(count < 4){ char *dotPos = strchr(ptr, '.'); long int val; char *endptr; char temp[4]; // 最多3位数字的缓冲区 if(dotPos == NULL){ // 处理最后一段 val = strtol(ptr, &endptr, 10); if(*endptr != '\0') return 0; } else { size_t len = dotPos - ptr; if(len > 3) return 0; // 数字长度超过3位,非法 strncpy(temp, ptr, len); temp[len] = '\0'; val = strtol(temp, &endptr, 10); if(*endptr != '\0') return 0; ptr = dotPos + 1; } if(val < 0 || val > 255) return 0; count++; } return (*ptr == '\0') ? 1 : 0; // 确保没有多余字符 } // 手动分割IP范围验证 int splitOnDash(char* ipStr){ char *dashPos = strchr(ipStr, '-'); if(dashPos == NULL){ // 没有'-',直接验证单个IP return splitOnDot(ipStr); } // 处理第一个IP char firstIp[16]; size_t firstLen = dashPos - ipStr; if(firstLen >= sizeof(firstIp)) return 0; strncpy(firstIp, ipStr, firstLen); firstIp[firstLen] = '\0'; if(!splitOnDot(firstIp)) return 0; // 处理第二个IP char secondIp[16]; strncpy(secondIp, dashPos+1, sizeof(secondIp)-1); secondIp[sizeof(secondIp)-1] = '\0'; return splitOnDot(secondIp); }
备注:内容来源于stack exchange,提问作者Aman H
相关产品推荐
相关产品推荐

