当进行如下整数转换时:
char a[256];
sprintf(a, "%d", 132);
确定变量 a 应该多大的最佳方法是什么?我认为手动设置它是可以的(因为我已经在很多地方看到了这样的做法),但应该设置多大呢?32位系统上最大的整数值是多少,有没有一些巧妙的方法可以动态计算它?
一些人认为这种方法过于复杂,对于将整数转换为字符串,我可能更倾向于赞同他们的观点。但是当找不到合理的字符串大小限制时,我见过这种方法被使用,并且我自己也使用过。
int size = snprintf(NULL, 0, "%d", 132);
char * a = malloc(size + 1);
sprintf(a, "%d", 132);
下面我会解释这里发生了什么。
snprintf
函数时,前两个参数告诉它将结果写入NULL
指针的0个字符中。这样做时,snprintf
不会实际写入任何字符,只会返回本应写入的字符数。这正是我们想要的。char
指针所需的内存。确保需要的大小加1(因为有一个结尾的\0
终止字符)。char
指针上,我们可以安全地使用sprintf
将整数写入char
指针。当然,如果您愿意,您可以让它更简洁。
char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1);
sprintf(a, "%d", 132);
除非这是一个“快速而肮脏”的程序,否则您总是要确保释放使用malloc
调用的内存。这就是使用C进行动态处理变得复杂的地方。然而,在我看来,如果您不想在大多数情况下只使用很小一部分时分配巨大的char
指针,则我认为这不是坏方法。
alloca
而不是malloc
。如果生成的代码仍然太臃肿,可以为其制作一个宏。 - thejhalloca
函数的可移植性如何?它显然不符合ANSI C标准。 - Daniel Standageint size = ...; char a[size+1]; sprintf(...
。 - Tommy使用C++11/C99中的vsnprintf函数,可以使Daniel Standage的解决方案适用于任意数量的参数。
int bufferSize(const char* format, ...) {
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args);
va_end(args);
return result + 1; // safe byte for \0
}
根据c99标准第7.19.6.12节的规定:
vsnprintf函数返回要写入(不包括终止null字符)的字符数,假设n足够大,或者在编码错误时返回负值。
int
类型中可能的最大位数为CHAR_BIT * sizeof(int)
,而一个十进制数字至少“值”3位,因此对于任意int
所需的空间的上限为(CHAR_BIT * sizeof(int) / 3) + 3
。+3是由于在除法运算时向下取整导致,其中一个用于符号位,一个用于结束符。
如果你说“在32位系统上”,意思是你知道int
是32位,则需要12个字节。其中10个字节用于存储数字,一个用于存储符号,一个用于结束符。
对于你特定的情况,即要转换的int
为132
,则需要4个字节。打鼓声。
如果可以使用有合理边界的固定大小缓冲区,则它们是更简单的选项。我并不谦虚地认为上面提到的限制是合理的(32位int
为13个字节而不是12个字节,64位int
为23个字节而不是21个字节)。但对于复杂的情况,在C99中您只需调用snprintf
来获取大小,然后malloc
相应的内存。对于这种简单的情况这样做有些过头了。
malloc
是荒谬的。它通过添加必须检查的失败情况来使您的代码过于复杂化--如果它失败了,那么你该怎么办?!?只需像您解释的那样使用正确大小的缓冲区即可。 - R.. GitHub STOP HELPING ICEstd::string
无关。 - Steve Jessopprintf
函数族的确切输出取决于语言环境。例如,某些语言环境可能设置“千位分隔符”。 - Brett Hale%d
不使用千位分隔符。%'d
会使用,但这不是问题的关键。 - Steve JessopVC++具有_scprintf
函数,专门用于查找所需字符数。
char *text;
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2);
log10(value)返回在基数为10的情况下存储正非零值所需的数字位数(减1)。对于小于一的数字,它会有点偏离轨道,因此我们指定abs(),并为零编写特殊逻辑(三元运算符,test?truecase:falsecase)。为了存储负数的符号(val <0),添加一个空格,为了弥补log10的差异再加上一个,并且为了使总和达到2个,再添加一个空终止符,这样就可以计算出给定数字所需的确切存储空间,而不必调用snprintf()或等效函数两次来完成任务。此外,它所使用的内存通常比INT_MAX所需的内存更少。
但是,如果您需要非常快速地打印大量数字,请分配INT_MAX缓冲区,然后重复打印到该缓冲区中。较少的内存抖动更好。
还要注意,您可能实际上并不需要(double)而不是(float)。我没有检查。像那样反复转换也可能是个问题。在所有这些方面,您的经验可能会有所不同。但对我来说非常有效。
INT_MIN
。那么abs(val)
是INT_MIN
,应用于它的log10
返回NaN,并将NaN转换为int
是未定义的行为。此外,如果您要打印64位整数,则其中最大的整数不能准确地表示为double
。 - Pascal Cuoq很好,你关注了缓冲区大小的问题。为了在代码中应用这个想法,我会使用snprintf。
snprintf( a, 256, "%d", 132 );
或者
snprintf( a, sizeof( a ), "%d", 132 ); // when a is array, not pointer
snprintf
只解决了一半的问题。如果您不确定缓冲区是否足够大,您需要使用snprintf
来测试所需的大小,或者确保您的代码在输出被截断时没有错误。Steve的答案更好。 - R.. GitHub STOP HELPING ICE首先,sprintf是个麻烦。如果可以的话,请使用snprintf,否则您可能会破坏内存并使应用程序崩溃。
至于缓冲区大小,它就像所有其他缓冲区一样-尽可能小,必要时尽可能大。在您的情况下,您有一个带符号整数,因此请取最大可能的大小,并随意添加一些安全填充。没有“标准大小”。
它也不取决于您运行的系统。如果您在堆栈上定义了缓冲区(就像在您的示例中),则取决于堆栈的大小。如果您自己创建了线程,则自己确定了堆栈大小,因此您知道限制。如果您期望递归或深度堆栈跟踪,则还需要特别小心。
sprintf
函数是没有危险的。对于数值输出,确定缓冲区的大小非常容易,可以参考 Steve 的回答。 - R.. GitHub STOP HELPING ICEsnprintf
无法帮助你。对数据进行不同步访问始终是未定义的行为。如果字符串大小在其下发生更改,snprintf
同样有可能会溢出。 - R.. GitHub STOP HELPING ICEsnprintf()
至少能防止程序崩溃。 - tc.snprintf
很可能存在 TOCTOU 竞争,首先检查 strlen
,然后执行 strcpy
或类似操作。除非你已经阅读了实现源代码,否则你不知道。如果你的程序如此糟糕以至于进行未同步的数据访问(这是最糟糕的 UB 形式之一,因为它“大多数时间似乎工作正常”),那么在这里使用 snprintf
不会对你的程序的错误产生任何影响。 - R.. GitHub STOP HELPING ICE
str
;-p - Steve Jessopasprintf
的函数,它会内部调用malloc
来分配所需的内存空间。 - utopianheaven