【发布时间】: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