5

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 ?

Thibaut D.
  • 2,521
  • 5
  • 22
  • 33

3 Answers3

7

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;
}
nwellnhof
  • 32,319
  • 7
  • 89
  • 113
5

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)
deltheil
  • 15,496
  • 2
  • 44
  • 64
  • I tried this with an overflow number (65536) and I got 0 in my variable instead of getting an error. Moreover, PRIu16 is "%u" and not "%hu" (h = short). BTW, I don't know why PRIu16 is not %hu. – Thibaut D. Nov 16 '13 at 16:20
  • I've updated my answer: `sscanf` is indeed not a good idea because it can't protect from integer overflow. `strtol` is thus the right candidate (see above). – deltheil Nov 16 '13 at 16:26
0

I made it very simple and not need conversion, only using a pointer.

uint16_t mat[7] = { 0x111A,0x121A,0x131A,0x141A,0x151A,0x161A,0x171A };
uint8_t* ptr = (uint8_t*)&mat[0];

The test of the conversion is here:

int main()
{

    uint16_t mat[7] = { 0x111A,0x121A,0x131A,0x141A,0x151A,0x161A,0x171A };
    uint8_t* ptr = (uint8_t*)&mat[0];

    for (int i = 0; i < 7; i++)
    {
        printf("0x%04X 0x%02X%02X\n", mat[i], ptr[2 * i +1], ptr[2 * i]);//ojo, ORDEN INVERTIDO
    }
 
    cout << "*** FIN ***" << endl; (void)getchar();
    return 0;   
}
mathengineer
  • 140
  • 6