简体   繁体   中英

Good C Coding practice/etiquette for integrity checks

Two part question;

I'm Coming from a high level Language, so this is a question about form not function;

I've written an isnumeric() function that takes a char[] and returns 1 if the string is a number taking advantage of the isdigit() function in ctype. Similar functions are builtin to other languages and I have always used something like that to integrity check the data before converting it to a numeric type. Mostly because some languages conversion functions fail badly if you try to convert a non-number string to an integer.

But it seems like a kludge having to do all that looping to compensate for the lack of strings in C, which poses the first part of the question;

Is it acceptable practice in C to trap for a 0 return from atoi() in lieu of doing an integrity check on the data before calling atoi()? The way atoi() (and other ascii to xx functions) works seems to lend itself well to eliminating the integrity check altogether. It would certainly seem more efficient to just skip the check.

The second part of the question is; Is there a C function or common library function for a numeric integrity check on a string? (by string, I of course mean char[])

Is it acceptable practice in C to trap for a 0 return from atoi() in lieu of doing an integrity check on the data before calling atoi()?

Never ever trap on error unless the error indicates a programming error that can't happen if there isn't a bug in the code. Always return some sort of error result in case of an error. Look at the OpenBSD strtonum function for how you could design such an interface.

The second part of the question is; Is there a C function or common library function for a numeric integrity check on a string? (by string, I of course mean char[])

Never use atoi unless you are writing a program without error checking as atoi doesn't do any error checking. The strtol family of functions allow you to check for errors. Here is a simply example of how you could use them:

int check_is_number(const char *buf)
{
    const char *endptr;
    int errsave = errno, errval;
    long result;

    errno = 0;
    result = strtol(buf, &endptr, 0);
    errval = errno;
    errno = errsave;

    if (errval != 0)
        return 0; /* an error occured */

    if (buf[0] == '\0' || *endptr != '\0')
        return 0; /* not a number */

    return 1;
}

See the manual page linked before for how the third argument to strtol (base) affects what it does.

errno is set to ERANGE if the value is out of range for the desired type (ie long ). In this case, the return value is LONG_MAX or LONG_MIN .

If the conversion method returns an error indication (as distinct from going bananas if an error occurs, or not providing a definitive means to check if an error has occurred) then there is actually no need to check if a string is numeric before trying to convert it.

With that in mind, using atoi() is not a particularly good function to use if you need to check for errors on conversion. Zero will be returned for zero input, as well as an error, and there is no way to check on why. A better function to use is (assuming you want to read an integral value) is strtol() . Although strtol() returns zero on integer, it also returns information that can be used to check for failure. For example;

long x; 
char *end;

x = strtol(your_string, &end, 10);
if (end == your_string)
{
     /*  nothing was read due to invalid character or the first 
         character marked the end of string */
}
else if (*end != '\0`)
{
    /*  an integral value was read, but there is following non-numeric data  */
}

Second, there are alternatives to using strtol() , albeit involving more overhead. The return values from sscanf() (and, in fact, all functions in the scanf() family) can be checked for error conditions.

There is no standard function for checking if a string is numeric, but it can be easily rolled using the above.

int IsNumeric(char *your_string)
{
     /*  This has undefined behaviour if your_string is not a C-style string
           It also deems that a string like "123AB" is non-numeric
     */
     long x; 
     char *end;

     x = strtol(your_string, &end, 10);
     return !(end == your_string || *end != '\0`);
}

No (explicit) loops in any of the above options.

Is it acceptable practice in C to trap for a 0 return from atoi() in lieu of doing an integrity check on the data before calling atoi()?

No. @FUZxxl well answers that.

Is there a C function or common library function for a numeric integrity check on a string?

In C, the conversion of a string to a number and the check to see if the conversion is valid is usually done together. The function used depends on the type of number sought. "1.23" would make sense for a floating point type, but not an integer.

// No error handle functions
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
double atof(const char *nptr);

// Some error detection functions
if (sscanf(buffer, "%d", &some_int) == 1) ...
if (sscanf(buffer, "%lf", &some_double) == 1) ...

// Robust methods use
long               strtol(   const char *nptr, char ** endptr, int base);
long long          strtoll(  const char *nptr, char ** endptr, int base);
unsigned long      strtoul(  const char *nptr, char ** endptr, int base);
unsigned long long strtoull( const char *nptr, char ** endptr, int base);
intmax_t           strtoimax(const char *nptr, char ** endptr, int base);
uintmax_t          strtoumax(const char *nptr, char ** endptr, int base);
float              strtof(   const char *nptr, char ** endptr);
double             strtod(   const char *nptr, char ** endptr);
long double        strtold(  const char *nptr, char ** endptr);

These robust methods use char ** endptr to store the string location where scanning stopped. If no numeric data was found, then *endptr == nptr . So a common test could is

char *endptr;
y = strto...(buffer, ..., &endptr);
if (buffer == endptr) puts("No conversion");
if (*endptr != '\0') puts("Extra text");

If the range was exceed these functions all set the global variable errno = ERANGE; and return a minimum or maximum value for the type.

errno = 0;
double y = strtod("1.23e10000000", &endptr);
if (errno == ERANGE) puts("Range exceeded");

The integer functions allow a radix selection from base 2 to 36. If 0 is used, the leading part of the string "0x" , "0X" , "0" , other --> base 16, 16, 8, 10.

long y = strtol(buffer, &endptr, 10);

Read the specification or help page for more details.

You probably don't need a function to check whether a string is numeric. You will most likely need to convert the string to a number so just do that. Then check if the convertion is successful.

long number;
char *end;
number = strtol(string, &end, 10);
if ((*string == '\0') || (*end != '\0'))
{
    // empty string or invalid number
}

the second argument of strtol is used to indicate where the parsing ended (the first non-numeric character). That character will be \\0 if we've reached the end of the string. If you want to permit other characters after the number (like), you can use switch to check for it.

strtol works with long integers. If you need some other type, you should consult the man page: man 3 strtol . For floating-point numbers you can use strtod .

Don't trap if the program logic permits that the string is not numeric (eg if it comes from the user or a file).

OP later commneted :

I'm looking for a way to determine if the string contains ONLY base 10 digits or a decimal or a comma. So if the string is 100,000.01 I want a positive return from func. Any other ascii characters anywhere in the string would result in a negative return value.

If is all your interest, use;

if (buffer[strspn(buffer, "0123456789.,")] == '\0') return 0; // Success
else return -1; // Failure

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM