需要这样的情况很少见:可能是一些微控制器代码,通过一些串行协议传输值。在这种情况下,使用任何printf() 系列函数都可能会增加最终二进制文件的大小。
(在典型的 C 开发环境中,C 库是动态加载的,避免标准 C 库函数绝对没有任何好处。它不会减少程序大小。)
所以,如果我需要这样的代码,我可能会写一个头文件,
#if defined(INTTYPE) && defined (UINTTYPE) && defined (FUNCNAME)
#ifndef DECIMAL_DIGITS_IN
#define DECIMAL_DIGITS_IN(x) ((CHAR_BIT * sizeof (x) * 28) / 93 + 2)
#endif
char *FUNCNAME(const INTTYPE value)
{
static char buffer[DECIMAL_DIGITS_IN(value) + 1];
char *p = buffer + sizeof buffer;
UINTTYPE left = (value < 0) ? -value : value;
*(--p) = '\0';
do {
*(--p) = '0' + (left % 10);
left /= 10;
} while (left > 0);
if (value < 0)
*(--p) = '-';
return p;
}
#undef FUNCNAME
#undef INTTYPE
#undef UINTTYPE
#endif
对于我需要的每种类型,我都会使用
#define FUNCNAME int2str
#define INTTYPE int
#define UINTTYPE unsigned int
#include "above.h"
在更普通的代码中,最好的方法是使用snprintf() 来避免缓冲区溢出,缓冲区大小是“估计的”。例如,
unsigned int x;
char buffer[256];
int len;
len = snprintf(buffer, sizeof buffer, "Message with a number %u", x);
if (len < 0 || (size_t)len >= sizeof buffer - 1) {
/* Abort! The buffer was (almost certainly) too small! */
} else {
/* Success; we have the string in buffer[]. */
}
buffer[] 是否比必要的大几十甚至几百字节,在典型程序中完全无关。只需让它足够大,并在错误情况下输出一条错误消息,告知哪个缓冲区(文件和函数)不够长,因此在不太可能的情况下很容易修复它。
正如dbush 所提到的,asprintf() GNU 扩展是一个可行的替代方案。它返回一个动态分配的字符串。
在 GNU 系统之外——这也是我建议 OP 考虑的——人们可以实现自己的 asprintf(),使用
vsnprintf()(在 C99 和更高版本的 C 库以及 POSIX.1 C 库中可用)。
我更喜欢类似于 POSIX.1 getline() 的变体,即将指向动态分配缓冲区的指针和该缓冲区的大小作为额外参数,并在必要时调整该缓冲区的大小:
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
size_t dynamic_printf(char **dataptr, size_t *sizeptr, const char *format, ...)
{
va_arg args;
char *data;
size_t size;
int len;
if (!dataptr || !sizeptr || !format) {
errno = EINVAL;
return 0;
}
if (!*sizeptr) {
*dataptr = NULL;
*sizeptr = 0;
}
data = *dataptr;
size = *sizeptr;
va_start(args, format);
len = vsnprintf(data, size, format, args);
va_end(args);
if (len < 0) {
errno = EINVAL;
return 0;
} else
if ((size_t)len < size) {
errno = 0;
return (size_t)len;
}
/* Need to reallocate the buffer. */
size = (size_t)len + 1;
data = realloc(data, size);
if (!data) {
errno = ENOMEM;
return 0;
}
*dataptr = data;
*sizeptr = size;
va_start(args, format);
len = vsnprintf(data, size, format, args);
va_end(args);
if (len != (int)(size - 1)) {
errno = EINVAL;
return 0;
}
errno = 0;
return (size_t)len;
}
这个想法是您可以在多个 dynamic_printf() 调用中重复使用相同的动态缓冲区:
char *data = NULL;
size_t size = 0;
size_t len;
/* Some kind of loop for example */
len = dynamic_printf(&data, &size, "This is something I need in a buffer");
if (errno) {
/* Abort! Reason is strerror(errno) */
} else {
/* data is non-NULL, and has len chars in it. */
}
/* Strings are no longer used, so free the buffer */
free(data);
data = NULL;
size = 0;
请注意,在调用之间运行free(data); data = NULL; size = 0; 是完全安全的。 free(NULL) 什么都不做,如果缓冲区指针是 NULL 并且大小为零,则该函数将动态分配一个新缓冲区。
在最坏的情况下(当缓冲区不够长时),函数会“打印”两次字符串。在我看来,这是完全可以接受的。