在Linux中,itoa函数在哪里?

169

itoa()是一个非常方便的函数,用于将数字转换为字符串。Linux似乎没有itoa(),是否有等效函数或者我需要使用sprintf(str, "%d", num)


4
使用sprintf(str, "%d", num)有什么缺点?相比于itoa,它速度慢很多吗? - Oleg Vazhnev
5
@javapowered,首先,itoa 允许进行任意进制的转换,而 printf 格式化字符串不支持。 - vladr
@sprintf() 不是信号安全的。 - lunesco
有没有任何理由不使用标准库中的 gcvt() 函数? - C--
首先,gcvt不是C标准的一部分,而且POSIX.1-2008也将其从POSIX中删除了。 - rootkea
18个回答

125
编辑:抱歉,我应该记住这台机器是非标准的,为了学术目的,我插入了各种非标准的libc实现;-)
正如几位热心评论者所提到的,itoa()确实是非标准的,最好使用sprintf(target_string,"%d",source_int)或者(更好的是,因为它可以防止缓冲区溢出)snprintf(target_string, size_of_target_string_in_bytes, "%d", source_int)。我知道这不像itoa()那样简洁和酷,但至少你可以一次编写,到处运行(tm);-)
以下是旧的(已编辑过的)答案
你说得对,默认的gcc libc不包括itoa(),就像其他一些平台一样,因为它在技术上并不是标准的一部分。在这里可以找到更多信息。请注意,你必须
#include <stdlib.h>

当然你已经知道这个,因为你想在Linux上使用itoa()函数,而之前可能在其他平台上使用过它,但是... 从上面的链接中偷来的代码看起来是这样的:
示例
/* itoa example */
#include <stdio.h>
#include <stdlib.h>

int main ()
{
  int i;
  char buffer [33];
  printf ("Enter a number: ");
  scanf ("%d",&i);
  itoa (i,buffer,10);
  printf ("decimal: %s\n",buffer);
  itoa (i,buffer,16);
  printf ("hexadecimal: %s\n",buffer);
  itoa (i,buffer,2);
  printf ("binary: %s\n",buffer);
  return 0;
}

输出:

Enter a number: 1750
decimal: 1750
hexadecimal: 6d6
binary: 11011010110

1
嗯,我在Debian上编译时出现了“undefined reference to `itoa'”的错误。也许我的系统有问题。 - Adam Pierce
我在Ubuntu 8.04上得到了相同的结果。在stdio.h或stdlib.h中也找不到itoa的参考(这并不奇怪,因为它不是标准的一部分)。 - camh
修改后的内容:感谢大家的纠正!抱歉,我总是忘记这不是一个普通的Linux系统;-) - Matt J
这段代码在Ubuntu上使用gcc编译能通过吗?(在我的机器上无法编译) - Oleg Vazhnev
缓冲区溢出怎么会发生?我确信,对于一个32位的数字,字符串长度永远不会超过34个“TCHAR”(假设是负二进制数)。 - user877329
显示剩余4条评论

28
itoa 不是标准的C函数,您可以编写自己的实现。它出现在第一版的KernighanRitchie'sThe C Programming Language书中的第60页上。第二版的 The C Programming Language(“K&R2”)包含了以下代码实现 itoa,位于第64页。该书指出了此实现的若干问题,其中包括不能正确处理最小负数。
 /* itoa:  convert n to characters in s */
 void itoa(int n, char s[])
 {
     int i, sign;

     if ((sign = n) < 0)  /* record sign */
         n = -n;          /* make n positive */
     i = 0;
     do {       /* generate digits in reverse order */
         s[i++] = n % 10 + '0';   /* get next digit */
     } while ((n /= 10) > 0);     /* delete it */
     if (sign < 0)
         s[i++] = '-';
     s[i] = '\0';
     reverse(s);
}  

上面使用的 reverse 函数在前两页中实现:

 #include <string.h>

 /* reverse:  reverse string s in place */
 void reverse(char s[])
 {
     int i, j;
     char c;

     for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
         c = s[i];
         s[i] = s[j];
         s[j] = c;
     }
}  

好的,也许我们应该避免在循环内调用strlen()函数 - 它应该在循环开始前调用...第二个问题:我们需要两个计数器(i和j)吗?是否只需迭代i从0到<mid(len/2),并交换索引i和len-1-i下的元素即可? - moremover
@moremover;这个实现来自于K&R的《C程序设计语言》,不是我的。 - haccks
是的...在循环之前调用了strlen()函数...一开始没有注意到... - moremover
是的。它只被调用一次来初始化 j - haccks
处理最小的负数可以通过取无符号绝对值来轻松解决。unsigned abs = n < 0 ? (0U - n) : n。此外,您可以从缓冲区末尾开始向后存储,或者存储到一个小的临时缓冲区中,而不是作为第二步进行反转,因此您只需要使用memmovememcpy。(缓冲区大小的上限约为log10(INT_MAX),例如最长的有符号64位数字是19个十进制数字加上“-”。但我不知道如何将INT_MAX或INT_MIN制作成编译时常量表达式,硬编码的char buf [24]很丑陋。) - Peter Cordes

12

如果你经常调用它,那么“只使用snprintf”这个建议可能会让人感到恼怒。所以这是你需要的:

const char *my_itoa_buf(char *buf, size_t len, int num)
{
  static char loc_buf[sizeof(int) * CHAR_BITS]; /* not thread safe */

  if (!buf)
  {
    buf = loc_buf;
    len = sizeof(loc_buf);
  }

  if (snprintf(buf, len, "%d", num) == -1)
    return ""; /* or whatever */

  return buf;
}

const char *my_itoa(int num)
{ return my_itoa_buf(NULL, 0, num); }

19
它不仅不是线程安全的,而且整体上并不安全: void some_func(char* a, char* b); some_func(itoa(123), itoa(456));你猜这个函数会接收到什么? - jcoder
此外,在函数返回类型上使用 const 限定符是无效的——如果您打开编译器警告,就会知道这一点 :) - cat
9
@cat 这里没有任何常量限定的返回类型。const char * 是一个非常量指向常量的指针,这很有道理且是正确的。 - Chortos-2
1
@Chortos-2 这很有趣,你当然是完全正确的 - 我没有意识到 const int f (void) { ...const int* f (void) { ... 之间在语义上的差异,但现在通过编译器尝试过后,这很有道理。 - cat
静态缓冲区的大小完全没有意义,因为放入其中的字符串不是二进制的。在典型机器上,它将是32个字符,但(假设32位int)最长的字符串是“-2147483648”,只需要12个字符。也许这是一个安全、慷慨、上限呢? - unwind
等等,CHAR_BITS是什么? - BMPL

9

编辑:我刚发现了 std::to_string,它的功能与我下面的自己写的函数是一样的。它在C++11中被引入,并且可以在最新版本的gcc中使用,至少从4.5开始,如果您启用了c++0x扩展。


itoa不仅在gcc中不存在,而且不是使用起来最方便的函数,因为你需要给它一个缓冲区。我需要一些可以在表达式中使用的东西,所以我想出了这个函数:

std::string itos(int n)
{
   const int max_size = std::numeric_limits<int>::digits10 + 1 /*sign*/ + 1 /*0-terminator*/;
   char buffer[max_size] = {0};
   sprintf(buffer, "%d", n);
   return std::string(buffer);
}

通常而言,使用 snprintf 而非 sprintf 会更安全,但是该缓冲区已经精心设置使其免受溢出的影响。

可以参考下面的示例:http://ideone.com/mKmZVE


26
这个问题似乎与C有关,而C没有std::等内容。 - glglgl

6

正如Matt J所写,虽然有itoa,但它并不是标准的。如果您使用snprintf,您的代码将更具可移植性。


5

以下函数分配足够的内存来存储给定数字的字符串表示形式,然后使用标准的 sprintf 方法将字符串表示形式写入该区域。

char *itoa(long n)
{
    int len = n==0 ? 1 : floor(log10l(labs(n)))+1;
    if (n<0) len++; // room for negative sign '-'

    char    *buf = calloc(sizeof(char), len+1); // +1 for null
    snprintf(buf, len+1, "%ld", n);
    return   buf;
}

在不需要使用时,不要忘记释放已分配的内存:free

char *num_str = itoa(123456789L);
// ... 
free(num_str);

注意:由于snprintf会复制n-1个字节,所以我们必须调用snprintf(buf, len+1, "%ld", n)(而不仅仅是snprintf(buf, len, "%ld", n))。

4
将函数命名为itoa但赋予不同于通常实现的行为并不是一个好主意。这个函数本身是可以的,但请给它另外一个名称 :) 我建议使用snprintf来计算缓冲区长度,而不是浮点字符串;浮点数可能存在边界情况的不准确性。并且不要强制转换calloc - M.M
感谢您的建议。 - mmdemirbas
如果要使用长整型,应该使用“labs”。否则可能会截断。 - Schwern
使用类似于char buf[64]的固定大小的tmp缓冲区进行sprintf,以获取长度,然后使用malloc并将其复制到其中。由于您写入所有字节,因此无法从calloc中获得任何好处。非常短的字符串的额外复制比调用浮点log10更少。如果您有一个位扫描函数可以可靠地内联到一些有效的东西(例如x86上的bsr),则整数log2的快速近似可能会有用。 (替代方案:在您知道最终长度之后,malloc一个64字节的缓冲区,然后进行realloc。) - Peter Cordes

5

在Linux中,itoa函数在哪里?

在Linux中没有这样的函数。我使用以下代码代替。

/*
=============
itoa

Convert integer to string

PARAMS:
- value     A 64-bit number to convert
- str       Destination buffer; should be 66 characters long for radix2, 24 - radix8, 22 - radix10, 18 - radix16.
- radix     Radix must be in range -36 .. 36. Negative values used for signed numbers.
=============
*/

char* itoa (unsigned long long  value,  char str[],  int radix)
{
    char        buf [66];
    char*       dest = buf + sizeof(buf);
    boolean     sign = false;

    if (value == 0) {
        memcpy (str, "0", 2);
        return str;
    }

    if (radix < 0) {
        radix = -radix;
        if ( (long long) value < 0) {
            value = -value;
            sign = true;
        }
    }

    *--dest = '\0';

    switch (radix)
    {
    case 16:
        while (value) {
            * --dest = '0' + (value & 0xF);
            if (*dest > '9') *dest += 'A' - '9' - 1;
            value >>= 4;
        }
        break;
    case 10:
        while (value) {
            *--dest = '0' + (value % 10);
            value /= 10;
        }
        break;

    case 8:
        while (value) {
            *--dest = '0' + (value & 7);
            value >>= 3;
        }
        break;

    case 2:
        while (value) {
            *--dest = '0' + (value & 1);
            value >>= 1;
        }
        break;

    default:            // The slow version, but universal
        while (value) {
            *--dest = '0' + (value % radix);
            if (*dest > '9') *dest += 'A' - '9' - 1;
            value /= radix;
        }
        break;
    }

    if (sign) *--dest = '-';

    memcpy (str, dest, buf +sizeof(buf) - dest);
    return str;
}

你应该编辑你的答案,解释这段代码如何回答问题。 - C. Helling
代码有问题...负值无法工作。 - calandoa
calandoa,你能给出哪些不起作用的精确值(值、基数)吗? - rick-rick-rick
@rick-rick-rick(别忘了@符号,这样用户才能收到通知!):正进制和负值。顺便说一下,不要使用负进制,或者正确地使用它:https://en.wikipedia.org/wiki/Negative_base - calandoa
65不够作为缓冲区的大小吗?2^64 - 1 是64个二进制位再加上1个空终止符,总共是65个。 - undefined
@calandoa 负数基数很有趣,但是提供的文档明确指出,radix 的负值用于将 value 解析为有符号数。 - undefined

4

阅读那些以编程为生的人的代码会让你受益匪浅。

看看MySQL的人是如何做到的。源代码非常注释详尽,比那些随处可见的抄袭解决方案更能教给你更多。

MySQL对int2str的实现

我在此提供了所提到的实现; 链接仅供参考,应用于阅读完整的实现。

char *
int2str(long int val, char *dst, int radix, 
        int upcase)
{
  char buffer[65];
  char *p;
  long int new_val;
  char *dig_vec= upcase ? _dig_vec_upper : _dig_vec_lower;
  ulong uval= (ulong) val;

  if (radix < 0)
  {
    if (radix < -36 || radix > -2)
      return NullS;
    if (val < 0)
    {
      *dst++ = '-';
      /* Avoid integer overflow in (-val) for LLONG_MIN (BUG#31799). */
      uval = (ulong)0 - uval;
    }
    radix = -radix;
  }
  else if (radix > 36 || radix < 2)
    return NullS;

  /*
    The slightly contorted code which follows is due to the fact that
    few machines directly support unsigned long / and %.  Certainly
    the VAX C compiler generates a subroutine call.  In the interests
    of efficiency (hollow laugh) I let this happen for the first digit
    only; after that "val" will be in range so that signed integer
    division will do.  Sorry 'bout that.  CHECK THE CODE PRODUCED BY
    YOUR C COMPILER.  The first % and / should be unsigned, the second
    % and / signed, but C compilers tend to be extraordinarily
    sensitive to minor details of style.  This works on a VAX, that's
    all I claim for it.
  */
  p = &buffer[sizeof(buffer)-1];
  *p = '\0';
  new_val= uval / (ulong) radix;
  *--p = dig_vec[(uchar) (uval- (ulong) new_val*(ulong) radix)];
  val = new_val;
  while (val != 0)
  {
    ldiv_t res;
    res=ldiv(val,radix);
    *--p = dig_vec[res.rem];
    val= res.quot;
  }
  while ((*dst++ = *p++) != 0) ;
  return dst-1;
}

2
欢迎提供潜在解决方案的链接,但请添加链接周围的上下文,以便其他用户了解它是什么以及为什么存在。在引用重要链接的最相关部分时,请始终考虑到目标站点无法访问或永久离线的情况。请注意,作为“几乎只是指向外部网站的链接”是为什么会删除某些答案?的可能原因之一。 - Tunaki
5
这个代码片段有什么好处?未来的读者应该注意什么? - Martijn Pieters

3
我尝试了自己的itoa()实现,它似乎在二进制、八进制、十进制和十六进制中都能正常工作。
#define INT_LEN (10)
#define HEX_LEN (8)
#define BIN_LEN (32)
#define OCT_LEN (11)

static char *  my_itoa ( int value, char * str, int base )
{
    int i,n =2,tmp;
    char buf[BIN_LEN+1];


    switch(base)
    {
        case 16:
            for(i = 0;i<HEX_LEN;++i)
            {
                if(value/base>0)
                {
                    n++;
                }
            }
            snprintf(str, n, "%x" ,value);
            break;
        case 10:
            for(i = 0;i<INT_LEN;++i)
            {
                if(value/base>0)
                {
                    n++;
                }
            }
            snprintf(str, n, "%d" ,value);
            break;
        case 8:
            for(i = 0;i<OCT_LEN;++i)
            {
                if(value/base>0)
                {
                    n++;
                }
            }
            snprintf(str, n, "%o" ,value);
            break;
        case 2:
            for(i = 0,tmp = value;i<BIN_LEN;++i)
            {
                if(tmp/base>0)
                {
                    n++;
                }
                tmp/=base;
            }
            for(i = 1 ,tmp = value; i<n;++i)
            {
                if(tmp%2 != 0)
                {
                    buf[n-i-1] ='1';
                }
                else
                {
                    buf[n-i-1] ='0';
                }
                tmp/=base;
            }
            buf[n-1] = '\0';
            strcpy(str,buf);
            break;
        default:
            return NULL;
    }
    return str;
}

2
在Linux中,itoa函数在哪里?
由于itoa()在C语言中不是标准函数,因此存在各种具有不同函数签名的版本。在*nix中,常见的是char *itoa(int value, char *str, int base)。
如果在Linux中缺少该函数或代码不想限制可移植性,代码可以自己实现。
下面是一个版本,它不会出现INT_MIN问题,并处理问题缓冲区:NULL或不足的缓冲区返回NULL。
#include <stdlib.h>
#include <limits.h>
#include <string.h>

// Buffer sized for a decimal string of a `signed int`, 28/93 > log10(2)
#define SIGNED_PRINT_SIZE(object)  ((sizeof(object) * CHAR_BIT - 1)* 28 / 93 + 3)

char *itoa_x(int number, char *dest, size_t dest_size) {
  if (dest == NULL) {
    return NULL;
  }

  char buf[SIGNED_PRINT_SIZE(number)];
  char *p = &buf[sizeof buf - 1];

  // Work with negative absolute value
  int neg_num = number < 0 ? number : -number;

  // Form string
  *p = '\0';
  do {
    *--p = (char) ('0' - neg_num % 10);
    neg_num /= 10;
  } while (neg_num);
  if (number < 0) {
    *--p = '-';
  }

  // Copy string
  size_t src_size = (size_t) (&buf[sizeof buf] - p);
  if (src_size > dest_size) {
    // Not enough room
    return NULL;
  }
  return memcpy(dest, p, src_size);
}

以下是处理任意进制[2...36]的C99或更高版本:

char *itoa_x(int number, char *dest, size_t dest_size, int base) {
  if (dest == NULL || base < 2 || base > 36) {
    return NULL;
  }

  char buf[sizeof number * CHAR_BIT + 2]; // worst case: itoa(INT_MIN,,,2)
  char *p = &buf[sizeof buf - 1];

  // Work with negative absolute value to avoid UB of `abs(INT_MIN)`
  int neg_num = number < 0 ? number : -number;

  // Form string
  *p = '\0';
  do {
    *--p = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[-(neg_num % base)];
    neg_num /= base;
  } while (neg_num);
  if (number < 0) {
    *--p = '-';
  }

  // Copy string
  size_t src_size = (size_t) (&buf[sizeof buf] - p);
  if (src_size > dest_size) {
    // Not enough room
    return NULL;
  }
  return memcpy(dest, p, src_size);
}

针对符合 C89 及其以上标准的代码,将内部循环替换为

  div_t qr;
  do {
    qr = div(neg_num, base);
    *--p = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[-qr.rem];
    neg_num = qr.quot;
  } while (neg_num);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接