将未知长度的格式化数据写入字符串(C编程)
以下C函数:
int sprintf ( char * str, const char * format, ... );
将格式化数据写入字符串。 作为str传递的数组的大小应该足以包含整个格式化的字符串。 但是,如果提前知道格式化字符串的长度怎么办? 如何使用此function(或类似其他function)写入格式化数据,其长度未知?
例如:
#include int main () { char buffer [13]; int n, a=5, b=3; n=sprintf (buffer, "%d plus %d is %d", a, b, a+b); printf ("[%s] is a %d char long string\n",buffer,n); return 0; }
缓冲区需要为13或更大才能使其正常工作。 如果字符串长度未知,并且缓冲区(例如)已设置为5,则此操作无效。 我需要能够为碰巧比缓冲区大的字符串动态分配或重新分配缓冲区的东西。
你想要的是这两个函数之一:* snprintf
( http://libslack.org/manpages/snprintf.3.html )。 它将输出缓冲区的长度作为其第二个参数,如果缓冲区对于结果而言太小,它将返回所需的字符数,允许您重新分配更大的缓冲区。 * asprintf
。 它需要一个char **
参数并分配足够的内存来保存输出,只要有足够多的连续虚拟内存可用。 如果你在程序退出之前已经完成了它并且可能需要内存用于其他东西,你必须free
地调用它从内存中删除它。
使用snprintf
大多数人会告诉你使用snprintf()
因为它不会超出缓冲区的末尾。
这是好建议。 通常的“设计模式”是声明一个临时固定大小的缓冲区,该缓冲区大于字符串可能的大小和snprintf()
。 如果字符串需要保存一段时间,则可以测量其长度, malloc(3)
另一个,并将临时缓冲区strcpy(3)
到半永久性malloc()
缓冲区。
两遍方法
还有另一种方式。
C99指定如果缓冲区为NULL,则不写入任何字节,但返回将写入的实际长度。 这允许你做一个虚拟的第一遍,malloc()一个缓冲区,然后snprintf()
到那个缓冲区。 (理论上你可以使用普通的sprintf()
,因为长度现在已知,但我不会。)
无论如何,如果我的程序必须在过去的每个操作系统上运行,我不确定我会依赖所有这些,但对于大多数人来说这是一个合理的模式。
根据DigitalRoss的建议,我把它作为一个练习给自己扔了。 如果你有代表(我没有),请随意将其与他的答案合并。
#include #include #include #include char* createRandstr(void); int main(void) { char* mystr; char* randomString; size_t len; /* Makes a random string of A's */ randomString = createRandstr(); /* 1st pass gets needed size */ len = (size_t)snprintf(NULL, 0, "random string -->%s<--\n", randomString); mystr = malloc(len); /* Safely write to mystr with known length 'len' */ snprintf(mystr, len, "random string -->%s<--\n", randomString); puts(mystr); free(mystr); free(randomString); return 0; } char* createRandstr(void) { char* randstr; size_t randlen; unsigned int i; srand((unsigned int)time((time_t*)NULL)); /* Seed rand() */ randlen = (size_t)rand() % 50; /* Limit to max of 49 chars */ randstr = malloc(randlen); for (i=0; i < randlen; i++) { randstr[i] = 'A'; } randstr[randlen - 1] = '\0'; return randstr; }
首先,使用snprintf
而不是sprintf
。 snprintf
采用额外的参数,即要写入的最大字节数(包括尾随'\0'
)。 snprintf
然后返回写入的字节数,不包括尾随的'\0'
。 所以你可以分配一个你认为足够大的缓冲区并尝试调用。 如果它到达缓冲区的末尾,释放该缓冲区并创建一个新的,更大的缓冲区并再试一次。 冲洗并重复。
您可以使用snprintf或asprintf 。 snprintf涉及检查大小并在必要时resize,而asprintf只是为您分配正确的大小。