Каков надлежащий способ реализации хорошей функции "itoa()"?

Мне было интересно, правильно ли реализована функция 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[]) {
 ...

Ответы

Ответ 1

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

Имя 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);
}

Ответ 2

// 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);

Ответ 3

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

Ответ 4

Хороший 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);
}

Ответ 5

Я не совсем уверен, где вы получаете 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;
}

НТН

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

int  num_iter = sizeof(int) / 4;

const char цифры [] = {'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];
}

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

Ответ 9

  • Integer-to-ASCII должен преобразовывать данные из стандартного целочисленного типав строку ASCII.
  • Все операции должны выполняться с использованием арифметики указателей, а не индексации массива.
  • Число, которое вы хотите преобразовать, передается как 32-разрядное целое число со знаком.
  • Вы должны иметь возможность поддерживать базы от 2 до 16, указав целочисленное значение базы, в которую вы хотите преобразовать (базу).
  • Скопируйте преобразованную символьную строку в указатель uint8_t *, переданный в качестве параметра (ptr).
  • 32-разрядное число со знаком будет иметь максимальный размер строки (Совет: подумайте, основа 2).
  • Вы должны поместить нулевой терминатор в конец преобразованной c-строки. Функция должна возвращать длину преобразованных данных (включая отрицательный знак).
  • Пример my_itoa (ptr, 1234, 10) должен возвращать длину строки ASCII, равную 5 (включая нулевой терминатор).
  • Эта функция должна обрабатывать подписанные данные.
  • Вы не можете использовать любые строковые функции или библиотеки.

.

uint8_t my_itoa(int32_t data, uint8_t *ptr, uint32_t base){
        uint8_t cnt=0,sgnd=0;
        uint8_t *tmp=calloc(32,sizeof(*tmp));
        if(!tmp){exit(1);}
        else{
            for(int i=0;i<32;i++){
            if(data<0){data=-data;sgnd=1;}
            if(data!=0){
               if(data%base<10){
                *(tmp+i)=(data%base)+48;
                data/=base;
               }
               else{
                *(tmp+i)=(data%base)+55;
                data/=base;
               }
            cnt++;     
            }
           }
        if(sgnd){*(tmp+cnt)=45;++cnt;}
        }
     my_reverse(tmp, cnt);
     my_memcopy(tmp,ptr,cnt);
     return ++cnt;
}
  • ASCII-to-Integer необходимо преобразовать данные обратно из строки, представленной в ASCII, в целочисленный тип.
  • Все операции должны выполняться с использованием арифметики указателей, а не индексации массива
  • Символьная строка для преобразования передается как указатель uint8_t * (ptr).
  • Количество цифр в вашем наборе символов передается как целое число uint8_t (цифры).
  • Вы должны иметь возможность поддерживать базы от 2 до 16.
  • Преобразованное 32-разрядное целое число со знаком должно быть возвращено.
  • Эта функция должна обрабатывать подписанные данные.
  • Вы не можете использовать какие-либо строковые функции или библиотеки.

.

int32_t my_atoi(uint8_t *ptr, uint8_t digits, uint32_t base){
    int32_t sgnd=0, rslt=0;
    for(int i=0; i<digits; i++){
        if(*(ptr)=='-'){*ptr='0';sgnd=1;}
        else if(*(ptr+i)>'9'){rslt+=(*(ptr+i)-'7');}
        else{rslt+=(*(ptr+i)-'0');}
        if(!*(ptr+i+1)){break;}
        rslt*=base;
    }
    if(sgnd){rslt=-rslt;}
    return rslt;
}

Ответ 10

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

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

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

  return strdup(buffer);
}

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

Ответ 11

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

#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 байт, чем вам нужно, но вам будет достаточно.

Ответ 12

main()
{
  int i=1234;
  char stmp[10];
#if _MSC_VER
  puts(_itoa(i,stmp,10));
#else
  puts((sprintf(stmp,"%d",i),stmp));
#endif
  return 0;
}