简体   繁体   中英

C string to int without any libraries

I'm trying to write my first kernel module so I'm not able to include libraries for atoi, strtol, etc. How can I convert a string to int without these built-in functions? I tried:

int num;
num = string[0] - '0';

which works for the first character, but if I remove the [0] to try and convert the full string it gives me a warning: assignment makes integer from pointer without a cast. So what do I do?

When creating your own string to int function, make sure you check and protect against overflow. For example:

/* an atoi replacement performing the conversion in a single
   pass and incorporating 'err' to indicate a failed conversion.
   passing NULL as error causes it to be ignored */
int strtoi (const char *s, unsigned char *err)
{
    char *p = (char *)s;
    int nmax = (1ULL << 31) - 1;    /* INT_MAX  */
    int nmin = -nmax - 1;           /* INT_MIN  */
    long long sum = 0;
    char sign = *p;

    if (*p == '-' || *p == '+') p++;

    while (*p >= '0' && *p <= '9') {
        sum = sum * 10 - (*p - '0');
        if (sum < nmin || (sign != '-' && -sum > nmax)) goto error;
        p++;
    }

    if (sign != '-') sum = -sum;

    return (int)sum;

error:

    fprintf (stderr, "strtoi() error: invalid conversion for type int.\n");
    if (err) *err = 1;
    return 0;
}

You can't remove the [0]. That means that you are subtracting '0' from the pointer string , which is meaningless. You still need to dereference it: num = string[i] - '0';

This function skips leading and trailing whitespace, handles one optional + / - sign, and returns 0 on invalid input,

// Convert standard null-terminated string to an integer
// - Skips leading whitespaces.
// - Skips trailing whitespaces.
// - Allows for one, optional +/- sign at the front.
// - Returns zero if any non-+/-, non-numeric, non-space character is encountered.
// - Returns zero if digits are separated by spaces (eg "123  45")
// - Range is checked against Overflow/Underflow (INT_MAX / INT_MIN), and returns 0.
int StrToInt(const char* s)
{
    int minInt = 1 << (sizeof(int)*CHAR_BIT-1);
    int maxInt = -(minInt+1);
    char* w;

    do { // Skip any leading whitespace
        for(w=" \t\n\v\f\r"; *w && *s != *w; ++w) ;
        if (*s == *w) ++s; else break;
    } while(*s);

    int sign = 1;
    if ('-' == *s) sign = -1; 
    if ('+' == *s || '-' == *s) ++s;

    long long i=0;
    while('0' <= *s && *s <= '9')
    {
        i = 10*i + *s++ - '0';
        if (sign*i < minInt || maxInt < sign*i)
        {
            i = 0;
            break;
        }
    }

    while (*s) // Skip any trailing whitespace
    {
        for(w=" \t\n\v\f\r"; *w && *s != *w; ++w) ;
        if (*w && *s == *w) ++s; else break;
    }

    return (int)(!*s*sign*i);
}
  1. A string is an array of characters, represented by an address (aka pointer ).

  2. An pointer has an value that might look something like 0xa1de2bdf . This value tells me where the start of the array is.

  3. You cannot subtract a pointer type with a character type (eg 0xa1de2bdf - 'b' does not really make sense).

To convert a string to a number, you could try this:

//Find the length of the string
int len = 0;
while (str[len] != '\0') { 
   len++;
}

//Loop through the string
int num = 0, i = 0, digit;
for (i=0; i<len; i++) {

   //Extract the digit
   digit = ing[i] - '0';

   //Multiply the digit with its correct position (ones, tens, hundreds, etc.)
   num += digit * pow(10, (len-1)-i);
}

Of course if you are not allowed to use math.h library, you could write your own pow(a,b) function which gives you the value of a^b .

int mypowfunc(int a, int b) {
   int i=0, ans=1;
   //multiply the value a for b number of times
   for (i=0; i<b; i++) {
      ans *= a;
   }   
   return ans;
}

I have written the code above in a way that is simple to understand. It assumes that your string has a null character ('\0') right behind the last useful character (which is good practice).

Also, you might want to check that the string is actually a valid string with only digits (eg '0', '1', '2', etc.). You could do this by including an if... else.. statement while looping through the string.

In modern kernels you want to use kstrto* :

http://lxr.free-electrons.com/source/include/linux/kernel.h#L274

274 /**
275  * kstrtoul - convert a string to an unsigned long
276  * @s: The start of the string. The string must be null-terminated, and may also
277  *  include a single newline before its terminating null. The first character
278  *  may also be a plus sign, but not a minus sign.
279  * @base: The number base to use. The maximum supported base is 16. If base is
280  *  given as 0, then the base of the string is automatically detected with the
281  *  conventional semantics - If it begins with 0x the number will be parsed as a
282  *  hexadecimal (case insensitive), if it otherwise begins with 0, it will be
283  *  parsed as an octal number. Otherwise it will be parsed as a decimal.
284  * @res: Where to write the result of the conversion on success.
285  *
286  * Returns 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
287  * Used as a replacement for the obsolete simple_strtoull. Return code must
288  * be checked.
289 */

" not able to include libraries" --> Unclear if code is allowed access to INT_MAX , INT_MIN . There is no way to determine the minimum/maximum signed integer in a completely portable fashion without using the language provided macros like INT_MAX , INT_MIN .

Use INT_MAX , INT_MIN is available. Else we could guess the char width is 8. We could guess there are no padding bits. We could guess that integers are 2's complement. With these reasonable assumptions , minimum and maximum are defined below.

Note: Shifting into the sign bit is undefined behavior (UB), so don't do that.


Let us add another restriction: make a solution that works for any signed integer from signed char to intmax_t . This disallows code from using a wider type, as there may not be a wider type.


typedef int Austin_int;

#define Austin_INT_MAXMID ( ((Austin_int)1) << (sizeof(Austin_int)*8 - 2) )
#define Austin_INT_MAX (Austin_INT_MAXMID - 1 + Austin_INT_MAXMID)
#define Austin_INT_MIN (-Austin_INT_MAX - 1)

int Austin_isspace(int ch) {
  const char *ws = " \t\n\r\f\v";
  while (*ws) {
    if (*ws == ch) return 1;
    ws++;
  }
  return 0;
}

// *endptr points to where parsing stopped
// *errorptr indicates overflow
Austin_int Austin_strtoi(const char *s, char **endptr, int *errorptr) {
  int error = 0;
  while (Austin_isspace(*s)) {
    s++;
  }

  char sign = *s;
  if (*s == '-' || *s == '+') {
    s++;
  }

  Austin_int sum = 0;
  while (*s >= '0' && *s <= '9') {
    int ch = *s - '0';
    if (sum <= Austin_INT_MIN / 10 &&
        (sum < Austin_INT_MIN / 10 || -ch < Austin_INT_MIN % 10)) {
      sum = Austin_INT_MIN;
      error = 1;
    } else {
      sum = sum * 10 - ch;
    }
    s++;
  }

  if (sign != '-') {
    if (sum < -Austin_INT_MAX) {
      sum = Austin_INT_MAX;
      error = 1;
    } else {
      sum = -sum;
    }
  }

  if (endptr) {
    *endptr = (char *) s;
  }
  if (errorptr) {
    *errorptr = error;
  }
  return sum;
}

The above depends on C99 or later in the Austin_INT_MIN Austin_INT_MIN % 10 part.

This is the cleanest and safest way I could come up with

int str_to_int(const char * str, size_t n, int * int_value) {
    int i;
    int cvalue;
    int value_muliplier = 1;
    int res_value = 0;
    int neg = 1; // -1 for negative and 1 for whole.
    size_t str_len; // String length.
    int end_at = 0; // Where loop should end.

    if (str == NULL || int_value == NULL || n <= 0)
        return -1;

    // Get string length
    str_len = strnlen(str, n);

    if (str_len <= 0)
        return -1;

    // Is negative.
    if (str[0] == '-') {
        neg = -1;
        end_at = 1; // If negative 0 item in 'str' is skipped.
    }

    // Do the math.
    for (i = str_len - 1; i >= end_at; i--) {
        cvalue = char_to_int(str[i]);

        // Character not a number.
        if (cvalue == -1)
            return -1;

        // Do the same math that is down below.
        res_value += cvalue * value_muliplier;
        value_muliplier *= 10;
    }

    /*
     * "436"
     * res_value = (6 * 1) + (3 * 10) + (4 * 100)
    */

    *int_value = (res_value * neg);
    return 0;
}

int char_to_int(char c) {
    int cvalue = (int)c;

    // Not a number.
    // 48 to 57 is 0 to 9 in ascii.
    if (cvalue < 48 || cvalue > 57)
        return -1;

    return cvalue - 48; // 48 is the value of zero in ascii.
}

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