Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe and portable way to convert a char* to uint16_t

Tags:

c

integer

char

As mentioned in the title, I'm looking for a way to convert a char* (coming from argv) to a uint16_t. The command line argument is a port number, and so, can't be > to 65535, nor negative.

Currently, I did this (compiling with -std=gnu99):

#include <stdbool.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>

/*
 * Converts a string to an unsigned int and stores the result in "res".
 */
bool str_to_uint(const char* str, unsigned long int* res) {
    if (str[0] == '-')
        return false;
    char* first_wrong_character;
    uintmax_t result = strtoumax(str, &first_wrong_character, 10);
    if ((result == UINTMAX_MAX) && (errno == ERANGE))
        return false; // Overflow)
    if ((*str != '\0') && (*first_wrong_character != '\0'))
        return false; // Not everything has been converted
    if ((result == 0) && (str == first_wrong_character))
        return false; // Nothing to convert
    *res = result;
    return true;
}

/*
 * Converts a string to an uint16_t and stores the result in "res".
 */
bool str_to_uint16(const char* str, uint16_t* res) {
    unsigned long uint;
    if (!str_to_uint(str, &uint))
        return false;
    if (uint > UINT16_MAX)
        return false;
    *res = (uint16_t)uint;
    return true;
}

I'm not sure it's the best way to do it, so if you could tell me what is the good way ?

like image 624
Thibaut D. Avatar asked Nov 16 '13 14:11

Thibaut D.


2 Answers

There's no need to use strtoumax. I'd go with the more portable strtol. The error handling can also be simplified to something like this:

bool str_to_uint16(const char *str, uint16_t *res) {
    char *end;
    errno = 0;
    long val = strtol(str, &end, 10);
    if (errno || end == str || *end != '\0' || val < 0 || val >= 0x10000) {
        return false;
    }
    *res = (uint16_t)val;
    return true;
}
like image 197
nwellnhof Avatar answered Nov 09 '22 15:11

nwellnhof


You can use strtol(3) that is able to return an error in case of integer overflow (ERANGE), and simply check if the parsed integer is not too large vs. the uint16_t capacity:

#include <stdint.h> /* fixed-width integer types */
#include <stdlib.h> /* strtol */
#include <stdbool.h>
#include <errno.h>

static bool
str_to_uint16(const char *str, uint16_t *res)
{
  long int val = strtol(str, NULL, 10);
  if (errno == ERANGE || val > UINT16_MAX || val < 0)
    return false;
  *res = (uint16_t) val;
  return true;
}

EDIT

Since the question concerns C99, and if I include a better error management (thanks to @nwellnhof and @chux) I would say the version below should be the right candidate:

#include <inttypes.h> /* strtoimax */

static bool
str_to_uint16(const char *str, uint16_t *res)
{
  char *end;
  errno = 0;
  intmax_t val = strtoimax(str, &end, 10);
  if (errno == ERANGE || val < 0 || val > UINT16_MAX || end == str || *end != '\0')
    return false;
  *res = (uint16_t) val;
  return true;
}

It succeeds with:

  • 1981
  • 65535 (UINT16_MAX)

It returns a conversion error (as expected) with:

  • 65536 (UINT16_MAX+1)
  • a1981
  • 1981a
  • abcd
  • 9223372036854775808 (INTMAX_MAX+1: in such a case ERANGE occurs)
  • -9223372036854775809 (INTMAX_MIN-1: in such a case ERANGE occurs)
like image 43
deltheil Avatar answered Nov 09 '22 14:11

deltheil