实现良好的"itoa()"功能的正确方法是什么?

Nic*_* C. 9 c string char

我想知道我执行"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[]) {
 ...
Run Code Online (Sandbox Code Playgroud)

小智 11

// 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);
Run Code Online (Sandbox Code Playgroud)

  • 你对'v/= radix`比'v = v/radix`更快的主张是不正确的.任何编译器都应为两个表单生成相同的代码.我在gcc中测试了它,并禁用了优化. (6认同)
  • 在 `return len;` 之前添加 `*sp = '\0';`,这样 `sp` 将成为一个正确的以 null 结尾的 ASCII 字符串,您可以在 `strcpy` 等函数中使用它。 (2认同)

Ste*_*sop 7

唯一的实际错误是您没有检查mallocnull 的返回值.

这个名称itoa已经被用于非标准的功能,但并非罕见.它不分配内存,而是写入调用者提供的缓冲区:

char *itoa(int value, char * str, int base);
Run Code Online (Sandbox Code Playgroud)

如果你不想依赖你的平台,我仍然建议遵循这种模式.在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您有理由特别关注性能(例如,如果您正在实现stdlib样式库,包括itoa),或者如果您实现的是sprintf不支持的库,那么您可能会考虑不调用sprintf.但是如果你想要一个基数为10的字符串,那么你的第一直觉是正确的.%d格式说明符绝对没有"不正确" .

以下是itoa仅对基数10 的可能实现:

char *itobase10(char *buf, int value) {
    sprintf(buf, "%d", value);
    return buf;
}
Run Code Online (Sandbox Code Playgroud)

这是一个将snprintf风格的方法结合到缓冲区长度的方法:

int itobase10n(char *buf, size_t sz, int value) {
    return snprintf(buf, sz, "%d", value);
}
Run Code Online (Sandbox Code Playgroud)


chu*_*ica 6

一个好的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);
}
Run Code Online (Sandbox Code Playgroud)