Advice/help/better 检查给定字符串是否为有效 IP 地址的解决方案

Advice/help/better solution for checking whether a given string can be a valid IP address or not

老实说,我认为我写的代码很垃圾,而且我认为这不是解决问题的最佳方法。我需要一些建议或改进来解决这个问题。我还是编码新手。如果您能提供一些有关如何使用字符串和各种字符串函数的提示,我们将不胜感激。

字符串成为 IP 地址的条件:-

连接到互联网的设备的标识号。以点分四组表示法编写的 IPv4 地址由四个由句点分隔的 8 位整数组成。

换句话说,它是一个由四个数字组成的字符串,每个数字介于 0 和 255 之间(含 0 和 255),带有“.”。每个数字之间的字符。所有数字都应不带前导零。

示例:

  1. 192.168.0.1 是一个有效的 IPv4 地址
  2. 255.255.255.255 是一个有效的 IPv4 地址
  3. 280.100.92.101 不是有效的 IPv4 地址,因为 280 太大而不是 8 位整数(最大的 8 位整数是 255)
  4. 255.100.81.160.172 不是有效的 IPv4 地址,因为它包含 5 个整数而不是 4
  5. 1..0.1 不是有效的 IPv4 地址,因为它的格式不正确
  6. 17.233.00.131 和 17.233.01.131 不是有效的 IPv4 地址,因为它们包含前导零

这是我的代码(我知道这是垃圾而且没有任何意义):-

bool isIPv4Address(char * inputString) {
    int count = 0, period = 0;
    int k = 0, i = 0;
    int digit = 0;
    
     
    while(inputString[i] != '[=11=]')
    { 
        count = 0;
        digit = 0;
        i = k;
        
        if(inputString[i] == '0' || inputString[i] == '.')
        {
            if(inputString[i+1] >47 && inputString[i+1] < 58)
            {
                return false;
            }
        }
        
        while(inputString[i] != '.' && inputString[i] != '[=11=]')
        {
            if(inputString[i] < 48 || inputString[i] > 57)
            {
                return false;
            } 
            count += (int)inputString[i];
            i++;
            digit++;
        }
        
        if(digit == 3)
        {
            if(inputString[i - 3] > '2')
            {
                return false;
            }
        }
        
        if(inputString[i] == '.')
        {
            period++;
        }
        
        k = i+1;
      
        if(count < 48 || count > 156)
        {
            return false;
        }
        
        if(inputString[i] == '[=11=]')
        {
            break;
        }
    }
    
    if(period == 3)
    {
        return true;
    }else
    {
        return false;
    }  
}

对于 '0' 之类的东西,您有很多松散的 47、48 等值。最好使用后一种语法。

有许多 if 范围检查。使用一些额外的状态变量可以降低复杂性。

到处使用inputString[i]很麻烦。最好这样做(例如 int chr = inputString[i]; 并改用 chr——这样更简单易读)。

原程序错误识别于:

192.168.0.1
280.100.92.101

我重构了代码并构建了诊断测试程序。注释为:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define ERR(_expr) \
    if (err != NULL) \
        break; \
    if (_expr) { \
        err = #_expr; \
        break; \
    }

const char *bigerr;
int opt_i;

bool
isfix1(const char *inputString)
{
    int i = 0;
    int ndig = -1;
    int ndot = 0;
    int val = 0;
    int leading_zero = 0;
    unsigned char ipv[4];
    const char *err = NULL;

    while (1) {
        int chr = inputString[i++];
        int eos = (chr == 0);

        // check the last number we got
        if ((chr == '.') || eos) {
            ERR(ndig == 0);

            // we don't allow (because it might be decoded as octal):
            //   x.00.y.z x.01.y.z
            // but this must be ok:
            //   x.0.y.z
            ERR(leading_zero && (ndig > 1));

            // we can only have 3 dots in the string
            ndot += (! eos);
            ERR(ndot > 3);

            ndig = 0;
            val = 0;

            if (eos) {
                // we must have _exactly 3 dots in the string
                ERR(ndot != 3);
                break;
            }
            continue;
        }

        // collect digits
        if ((chr >= '0') && (chr <= '9')) {
            val *= 10;
            chr -= '0';
            val += chr;

            if (ndig < 0)
                ndig = 0;

            // remember whether substring started with "0"
            if (ndig == 0)
                leading_zero = (chr == 0);

            // we can only have up to 3 digits per byte
            ++ndig;
            ERR(ndig > 3);

            // max value for byte
            ERR(val > 255);

            // just for fun -- the binary IP address
            ipv[ndot] = val;
            continue;
        }

        // any invalid character
        ERR(chr != 0);
    }

    if (0)
        printf("IPV: %d.%d.%d.%d\n",ipv[0],ipv[1],ipv[2],ipv[3]);

    bigerr = err;

    return (err == NULL);
}

bool
isorig(const char *inputString)
{
    int count = 0,
        period = 0;
    int k = 0,
        i = 0;
    int digit = 0;

    while (inputString[i] != '[=11=]') {
        count = 0;
        digit = 0;
        i = k;

        if (inputString[i] == '0' || inputString[i] == '.') {
            if (inputString[i + 1] > 47 && inputString[i + 1] < 58) {
                return false;
            }
        }

        while (inputString[i] != '.' && inputString[i] != '[=11=]') {
            if (inputString[i] < 48 || inputString[i] > 57) {
                return false;
            }
            count += (int) inputString[i];
            i++;
            digit++;
        }

        if (digit == 3) {
            if (inputString[i - 3] > '2') {
                return false;
            }
        }

        if (inputString[i] == '.') {
            period++;
        }

        k = i + 1;

        if (count < 48 || count > 156) {
            return false;
        }

        if (inputString[i] == '[=11=]') {
            break;
        }
    }

    if (period == 3) {
        return true;
    }
    else {
        return false;
    }
}

int
dofnc(bool (*fnc)(const char *),const char *ipaddr,const char *reason)
{
    int valid;

    bigerr = NULL;

    valid = fnc(ipaddr);
    printf("%s",valid ? "valid" : "INVALID");

    if (bigerr != NULL)
        printf(" (%s)",bigerr);

    printf(" (%s)\n",(valid != (reason == NULL)) ? "FAIL!" : "pass");

    return valid;
}

void
dotest(const char *ipaddr,const char *reason)
{
    static int sep = 0;

    if (sep)
        printf("\n");
    sep = 1;

    printf("IPADDR: %s",ipaddr);
    if (reason != NULL)
        printf(" -- %s",reason);
    printf("\n");

    int valid[2];
    printf("isorig: ");
    valid[0] = dofnc(isorig,ipaddr,reason);
    printf("isfix1: ");
    valid[1] = dofnc(isfix1,ipaddr,reason);

    do {
        if (opt_i)
            break;

        for (int idx = 0;  idx < 2;  ++idx) {
            if (valid[idx] != (reason == NULL))
                exit(1);
        }
    } while (0);
}

int
main(int argc,char **argv)
{

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        char *cp = *argv;
        if (*cp != '-')
            break;

        cp += 2;
        switch (cp[-1]) {
        case 'i':
            opt_i = ! opt_i;
            break;
        }
    }

    dotest("192.168.0.1",NULL);
    dotest("255.255.255.255",NULL);
    dotest("280.100.92.101","280 is too large to be an 8-bit integer");
    dotest("255.100.81.160.172","contains 5 integers instead of 4");
    dotest("1..0.1","not properly formatted");
    dotest("1.0.1.","not properly formatted");
    dotest("17.233.00.131","contain leading zeros");
    dotest("17.233.01.131","contain leading zeros");

    return 0;
}

程序输出如下:

IPADDR: 192.168.0.1
isorig: INVALID (FAIL!)
isfix1: valid (pass)

IPADDR: 255.255.255.255
isorig: valid (pass)
isfix1: valid (pass)

IPADDR: 280.100.92.101 -- 280 is too large to be an 8-bit integer
isorig: valid (FAIL!)
isfix1: INVALID (val > 255) (pass)

IPADDR: 255.100.81.160.172 -- contains 5 integers instead of 4
isorig: INVALID (pass)
isfix1: INVALID (ndot > 3) (pass)

IPADDR: 1..0.1 -- not properly formatted
isorig: INVALID (pass)
isfix1: INVALID (ndig == 0) (pass)

IPADDR: 1.0.1. -- not properly formatted
isorig: INVALID (pass)
isfix1: INVALID (ndig == 0) (pass)

IPADDR: 17.233.00.131 -- contain leading zeros
isorig: INVALID (pass)
isfix1: INVALID (leading_zero && (ndig > 1)) (pass)

IPADDR: 17.233.01.131 -- contain leading zeros
isorig: INVALID (pass)
isfix1: INVALID (leading_zero && (ndig > 1)) (pass)

在 POSIX 系统上,use the inet_addr() function:

NAME

inet_addr, inet_ntoa - IPv4 address manipulation

SYNOPSIS

#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);

DESCRIPTION

The inet_addr() function shall convert the string pointed to by cp, in the standard IPv4 dotted decimal notation, to an integer value suitable for use as an Internet address.

除非您编写代码是为了练习,否则没有必要重新发明一个已经存在的标准解决方案。

...

RETURN VALUE

Upon successful completion, inet_addr() shall return the Internet address. Otherwise, it shall return ( in_addr_t)(-1).

...

我在代码审查网站上发现了这个问题。由于代码没有按预期工作,这个问题不适合代码审查,所以我将在这里提供审查。

幻数

这在之前的回答中提到过:

代码中不清楚47、48、59和156这几个数字是什么意思。

您不能假定使用的字符集是 ASCII,因此使用基于 09 的范围检查更安全。

   if(inputString[i] == '0' || inputString[i] == '.')
   {
       if(inputString[i+1] < '0' && inputString[i+1] > '9')
       {
           return false;
       }
   }

使用ctype.h

提供的isdigit功能可能会更好
   if(inputString[i] == '0' || inputString[i] == '.')
   {
       if(!isdigit(inputString[i+1]))
       {
           return false;
       }
   }

复杂性

如果将函数分解为只有一个目的的较小函数,则此代码将更易于阅读、编写和调试。每个内部循环或检查都应该是一个函数。

编程的艺术是将问题分解成越来越小的部分,直到每一部分都非常容易解决的艺术。 Single Responsibility Principle 等编程原则对此进行了描述。

使用 C 库使检查更容易

有 2 个函数可以将字符串转换为整数,这样可以更轻松地检查大于 255 的数字。第一个是 atoi and the second is strtol,这两个函数中的任何一个都会给你一个可以与 255 进行比较的数字。