【问题标题】:Optimal Base-10 only itoa() function? [closed]仅基于 10 的最佳 itoa() 函数? [关闭]
【发布时间】:2014-02-25 10:21:04
【问题描述】:

在 20 多年的 C 语言编程中,我曾经使用过一次不是 10 的基数,所以当我发现我信赖的 MSVC 的 _itoa() 在另一个环境中丢失时,我开始编写一个只做基数 10 的程序,并将目标缓冲区参数,指向函数返回的存储空间,位于左侧,而不是右侧,就像 C 标准库中的所有字符串函数一样。我相信这段代码也是线程安全的。

有更快的方法吗?

我也打算询问正确性,但我相信包含的测试代码证明它有效,即使对于 LONG_MIN 的特定情况,即 (-1 * LONG_MAX) -1,这会导致代码失败,直到我改变了策略,记下了符号,然后将有符号的 int 复制到了无符号的 int 中。然后,我以无符号整数的形式完成了函数中的所有核心工作——它在 75% 的时间内也很高兴地运行了。

char * _i32toa(char *const rtn, int32_t i)    {
    if (NULL == rtn) return NULL;

    // declare local buffer, and write to it back-to-front
    char buff[12];
    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = buff + sizeof(buff)-1;
    *p-- = 0;    // nul-terminate buffer

    // deal with negative numbers while using an unsigned integer
    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    // core code here...
    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + '0';
    }
    *p = ui + '0';

    if ('-' == minus_sign) *--p = minus_sign;

    // knowing how much storage we needed, copy chars from buff to rtn...
    memcpy(rtn, p, sizeof(buff)-(p - buff));

    return rtn;
}

// ------------------------------------------------------------------------------------------------
#define LOOP_KNT (SHRT_MAX * 1024)
// ------------------------------------------------------------------------------------------------
int main(void)    {
    time_t start = clock();

    int32_t t = 123456, i;
    char *buff = (char *)malloc(256);

    for (i = (SHRT_MIN *1024); i < LOOP_KNT; i++)    {
        _i32toa(buff, i);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        _itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        ___itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    printf("\nString from integer %i is %s\n", t, _i32toa(buff, t));
    printf("\nString from integer %i is %s\n", -0, _i32toa(buff, -0));
    printf("\nString from integer %i is %s\n", -1, _i32toa(buff, -1));
    printf("\nString from integer %i is %s\n", LONG_MIN, _i32toa(buff, LONG_MIN));

    start = clock();
    for (int i = LONG_MIN; i < LONG_MAX; i++) {
        if (i != atoi(_i32toa(buff, (int32_t)i))) {
            printf("\nError for %i", i);
        }
        if (!i) printf("\nAt zero");
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    getchar();
    return 0;
}

性能是 Visual Studio 2013 中非 C 标准的 _itoa() 的 2-4 倍,是 sprintf() 的 10-15 倍。

该方法有些新颖,并且取决于知道完整字符串所需的缓冲区大小 - 分配它自己的字符串缓冲区的函数 buff[] 解决了这个问题,同时使其成为线程安全的。

知道缓冲区的末尾在哪里,可以让字符串的字符从后往前写,解决倒序问题。调用函数不需要以任何方式准备 *rtn,因为获取 memcpy() 到 *ptr 的工作字符串已经以空值结尾。

TVMIA 为您提供反馈。缺乏一个好的 _atoi() 函数是一个足够持久的问题,它值得一个好的解决方案。让我们做一个。

PS:在我的 i7 Hazwell 机器上运行 MSVS C++ 64 位并进行了全面优化,从 LONG_MIN 到 LONG_MAX 的完整循环平均每次转换需要 116 个时钟,对于往返,只有 28 个时钟对于 _itoa()。与 Ben Voigt 的代码相比,这超过了每秒 725 兆字节的字符串。我想我赢了本!

【问题讨论】:

  • 关于您问题的 C++ 版本的更快想法:stackoverflow.com/questions/4351371/…
  • @KarolyHorvath:当然是这样,函数的每次调用都有自己的自动buff 变量。
  • 我会让他们把缓冲区的长度传递给你,这样你就可以检查你没有溢出缓冲区。此外,我只会返回一个指向缓冲区的指针,而不是执行 memcpy()。
  • @brianbeuning buff 无法在函数中存活,所以这是不行的。我考虑了长度检查,但这确实是调用函数应该做出的决定,因为我们不想让 _i32toa() 在不需要它的地方承担繁重的工作。此外,如果他们搞砸了分配适当的存储,为什么相信他们会得到正确的长度?不过,我确实仔细考虑了它,并且有了来源,您当然可以在需要的地方添加它。
  • 使用 '0' 而不是 48,因为它读起来更好,并为读者提供有关您正在做什么的更多信息。

标签: c++ c performance string-formatting


【解决方案1】:

您可以通过直接写入调用方的内存区域来消除memcpy
您应该让调用者传递缓冲区的大小。

另一个瓶颈是分裂,但我不知道如何解决这个问题。

编辑1:缓冲区指针的正确初始化

char * _i32toa(char *const rtn, unsigned int buff_size, int32_t i)  
{
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + buff_size - 1;
    // As before, without memcpy.
    return rtn;
}

【讨论】:

  • auto buff[] 和 memcpy() 允许我在几乎所有情况下都知道函数末尾的 p != buff[] 写入 buff 的后面。我通过 memcpy() 调整部分使用的缓冲区,该缓冲区在字符串的第一个字节和缓冲区的前面之间存在间隙 - 将 auto buff[] 放入调用者字符串缓冲区中的正确位置。要么就是这样,要么你必须知道在开始编写字符串之前需要多少字节。
  • 如果调用者传递了缓冲区的大小,你就知道后面在哪里,然后从那里开始。
  • 这告诉你他可能很长的缓冲区的背面在哪里,但它没有告诉你从哪里开始以相反的顺序在该缓冲区中写入字符,所以你最终会写入最高有效位在他的缓冲区的头部。
  • 查看我的编辑 1,其中显示了 p 的初始化。
  • 只有当 buff_size 是最终的 strlen(buff[]) 时才会起作用。
【解决方案2】:

去掉 auto char 数组,让它们传递大小,这样你就可以检查溢出了。

#define I32TOA( buff, val ) _i32toa( (buff), sizeof(buff), (val) )

char * _i32toa(char *const rtn, size_t size, int32_t i)    {
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + size-1;
    *p-- = 0;    // nul-terminate buffer
    assert( p >= rtn );

    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + 48;
        assert( p >= rtn );
    }
    *p = ui + 48;

    if ('-' == minus_sign) {
        *--p = minus_sign;
        assert( p >= rtn );
    }

    return p;
}

【讨论】:

  • 假设他在调用函数中分配了一个缓冲区,以及一个指向它的指针,比如 char foo[], *p=foo;如果他将 p 作为 arg 传递,则返回的指针不再等于 p,或 &foo[0];这种方法会产生同样的问题,迫使您始终更新传递给 realloc() 的任何指针,IE:指针值已更改。
  • 那为什么还要返回一个指针呢?
  • 返回的指针告诉用户你构建的字符串在他的缓冲区中的位置。顺便说一句,您检查缓冲区大小的宏获取的是指针的大小,而不是缓冲区的大小,除非在特殊情况下,缓冲区是在与调用函数相同的范围内声明的固定大小的缓冲区。我将代码更改为调用 malloc() 来帮助说明这一点。
猜你喜欢
  • 1970-01-01
  • 2014-08-08
  • 2020-09-14
  • 2017-11-18
  • 1970-01-01
  • 1970-01-01
  • 2011-01-12
  • 2018-06-28
  • 2011-01-30
相关资源
最近更新 更多