Как правильно реализовать хорошую функцию "itoa ()"? - PullRequest
9 голосов
/ 09 августа 2010

Мне было интересно, правильна ли моя реализация функции "itoa".Может быть, вы можете помочь мне сделать это немного более «правильным», я почти уверен, что что-то упустил.(Возможно, уже есть библиотека, которая выполняет преобразование так, как я хочу, но ... не могу найти)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char * itoa(int i) {
  char * res = malloc(8*sizeof(int));
  sprintf(res, "%d", i);
  return res;
}

int main(int argc, char *argv[]) {
 ...

Ответы [ 11 ]

11 голосов
/ 12 сентября 2012
// Yet, another good itoa implementation
// returns: the length of the number string
int itoa(int value, char *sp, int radix)
{
    char tmp[16];// be careful with the length of the buffer
    char *tp = tmp;
    int i;
    unsigned v;

    int sign = (radix == 10 && value < 0);    
    if (sign)
        v = -value;
    else
        v = (unsigned)value;

    while (v || tp == tmp)
    {
        i = v % radix;
        v /= radix; // v/=radix uses less CPU clocks than v=v/radix does
        if (i < 10)
          *tp++ = i+'0';
        else
          *tp++ = i + 'a' - 10;
    }

    int len = tp - tmp;

    if (sign) 
    {
        *sp++ = '-';
        len++;
    }

    while (tp > tmp)
        *sp++ = *--tp;

    return len;
}

// Usage Example:
char int_str[15]; // be careful with the length of the buffer
int n = 56789;
int len = itoa(n,int_str,10);
7 голосов
/ 09 августа 2010

Единственной фактической ошибкой является то, что вы не проверяете возвращаемое значение malloc для нуля.

Имя itoa уже используется для функции, которая является нестандартной, но неэто необычно.Он не выделяет память, скорее он записывает данные в буфер, предоставленный вызывающей стороной:

char *itoa(int value, char * str, int base);

Если вы не хотите полагаться на вашу платформу, я бы посоветовал следовать этой схеме.Функции обработки строк, которые возвращают вновь выделенную память в C, как правило, доставляют больше хлопот, чем они стоят в долгосрочной перспективе, потому что большую часть времени вы заканчиваете тем, что выполняете дальнейшие манипуляции, и поэтому вам нужно освободить много промежуточных результатов.Например, сравните:

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        itoa(endptr, i, 10); // itoa doesn't allocate memory
        unlink(filename);
    }
}

с

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        char *number = itoa(i, 10); // itoa allocates memory
        strcpy(endptr, number);
        free(number);
        unlink(filename);
    }
}

Если у вас была причина особенно беспокоиться о производительности (например, если вы реализуете библиотеку в стиле stdlib, включающую itoa), или если вы реализуете базы, которые sprintf не поддерживает, вы можете не вызывать sprintf.Но если вам нужна базовая строка 10, тогда ваш первый инстинкт был верным.В спецификаторе формата %d нет абсолютно ничего «неправильного».

Вот возможная реализация itoa, только для базы 10:

char *itobase10(char *buf, int value) {
    sprintf(buf, "%d", value);
    return buf;
}

Вот та, которая включает snprintf-стиль подхода к длине буфера:

int itobase10n(char *buf, size_t sz, int value) {
    return snprintf(buf, sz, "%d", value);
}
3 голосов
/ 29 апреля 2015

Хорошая int до строка или itoa() обладает этими свойствами;

  • Работает для всех [INT_MIN...INT_MAX], база [2...36] без переполнения буфера.
  • Не принимает int размер.
  • Не требует 2-х дополнений.
  • Не требуется, чтобы unsigned имел больший положительный диапазон, чем int.Другими словами, не использует unsigned.
  • Позволяет использовать '-' для отрицательных чисел, даже если base != 10.

Адаптировать обработку ошибок по мере необходимости.(требуется C99 или более поздняя версия):

char* itostr(char *dest, size_t size, int a, int base) {
  // Max text needs occur with itostr(dest, size, INT_MIN, 2)
  char buffer[sizeof a * CHAR_BIT + 1 + 1]; 
  static const char digits[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  if (base < 2 || base > 36) {
    fprintf(stderr, "Invalid base");
    return NULL;
  }

  // Start filling from the end
  char* p = &buffer[sizeof buffer - 1];
  *p = '\0';

  // Work with negative `int`
  int an = a < 0 ? a : -a;  

  do {
    *(--p) = digits[-(an % base)];
    an /= base;
  } while (an);

  if (a < 0) {
    *(--p) = '-';
  }

  size_t size_used = &buffer[sizeof(buffer)] - p;
  if (size_used > size) {
    fprintf(stderr, "Scant buffer %zu > %zu", size_used , size);
    return NULL;
  }
  return memcpy(dest, p, size_used);
}
3 голосов
/ 09 августа 2010

Я думаю, вы выделяете слишком много памяти.malloc(8*sizeof(int)) даст вам 32 байта на большинстве машин, что, вероятно, слишком много для текстового представления целого числа.

2 голосов
/ 09 августа 2010

я нашел интересный ресурс, посвященный нескольким различным вопросам, связанным с реализацией в Itoa
возможно, вы тоже захотите посмотреть
реализации itoa () с тестами производительности

2 голосов
/ 09 августа 2010

Я не совсем уверен, где вы получите 8*sizeof(int) в качестве максимально возможного количества символов - ceil(8 / (log(10) / log(2))) дает множитель 3*.Кроме того, в C99 и на некоторых старых платформах POSIX вы можете создать точно распределенную версию с sprintf():

char *
itoa(int i) 
{
    int n = snprintf(NULL, 0, "%d", i) + 1;
    char *s = malloc(n);

    if (s != NULL)
        snprintf(s, n, "%d", i);
    return s;
}

HTH

1 голос
/ 08 ноября 2017

sprintf довольно медленный, если производительность имеет значение, вероятно, это не лучшее решение.

, если базовый аргумент имеет степень 2, преобразование может быть выполнено со сдвигом и маскированием, и можно избежать обращения вспять.строка путем записи цифр с самых высоких позиций.Например, что-то вроде этого для base = 16

int  num_iter = sizeof(int) / 4;

const char digits [] = {'0', '1', '2', '3', '4', '5',«6», «7», «8», «9», «a», «b», «c», «d», «e», «f»};

/* skip zeros in the highest positions */
int i = num_iter;
for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    if ( digit > 0 )  break;
}

for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    result[len++] = digits[digit];
}

Для десятичных чисел есть хорошая идея использовать статический массив, достаточно большой для записи чисел в обратном порядке, см. здесь

1 голос
/ 09 августа 2010

Для этой цели вам следует использовать функцию из семейства printf. Если вы будете записывать результат в stdout или файл, используйте printf / fprintf. В противном случае используйте snprintf с буфером, достаточно большим, чтобы вместить 3*sizeof(type)+2 байт или более.

0 голосов
/ 09 августа 2010

Это должно работать:

#include <string.h>
#include <stdlib.h>
#include <math.h>

char * itoa_alloc(int x) {
   int s = x<=0 ? 1 ? 0; // either space for a - or for a 0
   size_t len = (size_t) ceil( log10( abs(x) ) );
   char * str = malloc(len+s + 1);

   sprintf(str, "%i", x);

   return str;
}

Если вы не хотите использовать математические функции / функции с плавающей запятой (и должны ссылаться на математические библиотеки), вы сможете найти версии log10 без плавающей запятой, выполнив поиск в Интернете и выполнив:

size_t len ​​= my_log10 (abs (x)) + 1;

Это может дать вам на 1 байт больше, чем вам нужно, но вам этого будет достаточно.

0 голосов
/ 09 августа 2010

Есть пара предложений, которые я мог бы сделать. Вы можете использовать статический буфер и strdup, чтобы избежать повторного выделения слишком большого объема памяти при последующих вызовах. Я также добавил бы некоторые проверки ошибок.

char *itoa(int i)
{
  static char buffer[12];

  if (snprintf(buffer, sizeof(buffer), "%d", i) < 0)
    return NULL;

  return strdup(buffer);
}

Если это будет вызвано в многопоточной среде, удалите «static» из объявления буфера.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...