在 C 中声明字符串而不给出大小
Declare string in C without giving size
我想将多个句子连接成一个字符串。目前我的缓冲区大小固定为 100,但我不知道要连接的句子总数,将来这个大小可能不够用。如何在不定义字符串大小的情况下定义字符串?
char buffer[100];
int offset = sprintf (buffer, "%d plus %d is %d", 5, 3, 5+3);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);
offset += sprintf (buffer + offset, " even more");
printf ("[%s]",buffer);
这是 C 的一个基本方面。C 从不为您自动管理动态构造的字符串 — 这始终是您的责任。
以下是您可能会使用的四种不同技术的概述。您可以提出有关其中任何不清楚的其他问题。
运行 通过你的字符串构造过程两次。进行一次以收集所有子字符串的长度,然后调用 malloc
分配计算大小的缓冲区,然后进行第二次以实际构建字符串。
使用 malloc
分配一个较小的(或空的)初始缓冲区,然后,每次要向其添加新的子字符串时,检查缓冲区的大小,如果有必要使用 realloc
将其变大。 (在这种情况下,我总是使用 三个 变量:(1) 指向缓冲区的指针,(2) 分配的缓冲区大小,(3) 当前缓冲区中的字符数。目标是始终保持 (2) ≥ (3).)
分配一个动态增长的“memstream”并使用fprintf
或类似的方法“打印”它。这是一种理想的技术,尽管 memstream 不是标准的并且并非在所有平台上都受支持,而且动态分配的 memstream 更加奇特且不常见。 (自己写也是可以的,但是工作量很大。)你可以使用 fmemopen
打开一个固定大小的 memstream(虽然这不是你想要的),你可以打开圣杯,一个动态的- 使用 open_memstream
分配 memstream(这是你想要的),如果你有的话。两者都记录在 this man page 上。 (此技术类似于 C++ 中的 stringstream
。)
“乞求宽恕胜于请求许可”的技巧。您可以分配一个您非常确定足够大的缓冲区,然后盲目地将所有子字符串填充到其中,然后在最后调用 strlen
,如果您猜错了并且字符串长于您分配的缓冲区,打印一条可怕的嘈杂错误消息并中止。这是一种直截了当且有风险的技术,而不是您会在生产程序中使用的技术。当您溢出缓冲区时,您可能会以某种方式损坏某些东西,导致程序在它有机会执行其迟来的检查和可能退出步骤之前崩溃。如果你完全使用这种技术,那么如果你使用 malloc
分配缓冲区,它会比你将其声明为普通的更“安全”(也就是说,在检查之前过早崩溃的可能性要小得多),固定大小的数组(无论是静态的还是本地的)。
就我个人而言,这四个我都用过。在世界其他地方,数字 1 和 2 几乎每个人都常用。比较它们:数字 1 简单且更容易一些,但代码复制量令人不舒服(因此,如果以后添加新字符串,可能会很脆弱);数字 2 更健壮,但显然需要你熟悉 realloc
的工作方式(如果你有任何辅助指针进入你的缓冲区,这种技术可能不那么健壮每次调用 realloc
时都会重定位)。
数字 3 是一种“奇特”技术:理论上几乎是理想的,但肯定更复杂并且需要一些额外的支持,因为没有像 open_memstream
这样的标准。
4 号显然是一种有风险且本质上 不 可靠的技术,您只能在一次性或原型代码中使用这种技术,而绝不会在生产中使用。
您可以使用第一个参数为 NULL,第二个参数为 0 的 snprintf
函数来获取格式化字符串的大小。然后,您可以动态分配 space 并再次调用 snprintf
以实际构建字符串。
char *buffer = NULL;
int len, offset = 0;
len = snprintf (NULL, 0, "%d plus %d is %d", 5, 3, 5+3);
buffer = realloc(buffer, offset + len + 1);
offset = sprintf (buffer + offset, "%d plus %d is %d", 5, 3, 5+3);
len = snprintf (NULL, 0, " and %d minus %d is %d", 6, 3, 6-3);
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);
len = snprintf (NULL, 0, " even more");
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " even more");
printf ("[%s]",buffer);
请注意,为简洁起见,此实现省略了对 realloc
和 snprintf
的检查。它还重复格式字符串和参数。以下函数解决了这些缺点:
int append_buffer(char **buffer, int *offset, const char *format, ...)
{
va_list args;
int len;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args);
if (len < 0) {
perror("vsnprintf failed");
return 0;
}
va_end(args);
char *tmp = realloc(*buffer, *offset + len + 1);
if (!tmp) {
perror("realloc failed");
return 0;
}
*buffer = tmp;
va_start(args, format);
*offset = vsprintf(*buffer + *offset, format, args);
if (len < 0) {
perror("vsnprintf failed");
return 0;
}
va_end(args);
return 1;
}
然后你可以这样调用:
char *buffer = NULL;
int offset = 0;
int rval;
rval = append_buffer(&buffer, &offset, "%d plus %d is %d", 5, 3, 5+3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " and %d minus %d is %d", 6, 3, 6-3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " even more");
if (!rval) return 1;
printf ("[%s]",buffer);
free(buffer);
我想将多个句子连接成一个字符串。目前我的缓冲区大小固定为 100,但我不知道要连接的句子总数,将来这个大小可能不够用。如何在不定义字符串大小的情况下定义字符串?
char buffer[100];
int offset = sprintf (buffer, "%d plus %d is %d", 5, 3, 5+3);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);
offset += sprintf (buffer + offset, " even more");
printf ("[%s]",buffer);
这是 C 的一个基本方面。C 从不为您自动管理动态构造的字符串 — 这始终是您的责任。
以下是您可能会使用的四种不同技术的概述。您可以提出有关其中任何不清楚的其他问题。
运行 通过你的字符串构造过程两次。进行一次以收集所有子字符串的长度,然后调用
malloc
分配计算大小的缓冲区,然后进行第二次以实际构建字符串。使用
malloc
分配一个较小的(或空的)初始缓冲区,然后,每次要向其添加新的子字符串时,检查缓冲区的大小,如果有必要使用realloc
将其变大。 (在这种情况下,我总是使用 三个 变量:(1) 指向缓冲区的指针,(2) 分配的缓冲区大小,(3) 当前缓冲区中的字符数。目标是始终保持 (2) ≥ (3).)分配一个动态增长的“memstream”并使用
fprintf
或类似的方法“打印”它。这是一种理想的技术,尽管 memstream 不是标准的并且并非在所有平台上都受支持,而且动态分配的 memstream 更加奇特且不常见。 (自己写也是可以的,但是工作量很大。)你可以使用fmemopen
打开一个固定大小的 memstream(虽然这不是你想要的),你可以打开圣杯,一个动态的- 使用open_memstream
分配 memstream(这是你想要的),如果你有的话。两者都记录在 this man page 上。 (此技术类似于 C++ 中的stringstream
。)“乞求宽恕胜于请求许可”的技巧。您可以分配一个您非常确定足够大的缓冲区,然后盲目地将所有子字符串填充到其中,然后在最后调用
strlen
,如果您猜错了并且字符串长于您分配的缓冲区,打印一条可怕的嘈杂错误消息并中止。这是一种直截了当且有风险的技术,而不是您会在生产程序中使用的技术。当您溢出缓冲区时,您可能会以某种方式损坏某些东西,导致程序在它有机会执行其迟来的检查和可能退出步骤之前崩溃。如果你完全使用这种技术,那么如果你使用malloc
分配缓冲区,它会比你将其声明为普通的更“安全”(也就是说,在检查之前过早崩溃的可能性要小得多),固定大小的数组(无论是静态的还是本地的)。
就我个人而言,这四个我都用过。在世界其他地方,数字 1 和 2 几乎每个人都常用。比较它们:数字 1 简单且更容易一些,但代码复制量令人不舒服(因此,如果以后添加新字符串,可能会很脆弱);数字 2 更健壮,但显然需要你熟悉 realloc
的工作方式(如果你有任何辅助指针进入你的缓冲区,这种技术可能不那么健壮每次调用 realloc
时都会重定位)。
数字 3 是一种“奇特”技术:理论上几乎是理想的,但肯定更复杂并且需要一些额外的支持,因为没有像 open_memstream
这样的标准。
4 号显然是一种有风险且本质上 不 可靠的技术,您只能在一次性或原型代码中使用这种技术,而绝不会在生产中使用。
您可以使用第一个参数为 NULL,第二个参数为 0 的 snprintf
函数来获取格式化字符串的大小。然后,您可以动态分配 space 并再次调用 snprintf
以实际构建字符串。
char *buffer = NULL;
int len, offset = 0;
len = snprintf (NULL, 0, "%d plus %d is %d", 5, 3, 5+3);
buffer = realloc(buffer, offset + len + 1);
offset = sprintf (buffer + offset, "%d plus %d is %d", 5, 3, 5+3);
len = snprintf (NULL, 0, " and %d minus %d is %d", 6, 3, 6-3);
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);
len = snprintf (NULL, 0, " even more");
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " even more");
printf ("[%s]",buffer);
请注意,为简洁起见,此实现省略了对 realloc
和 snprintf
的检查。它还重复格式字符串和参数。以下函数解决了这些缺点:
int append_buffer(char **buffer, int *offset, const char *format, ...)
{
va_list args;
int len;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args);
if (len < 0) {
perror("vsnprintf failed");
return 0;
}
va_end(args);
char *tmp = realloc(*buffer, *offset + len + 1);
if (!tmp) {
perror("realloc failed");
return 0;
}
*buffer = tmp;
va_start(args, format);
*offset = vsprintf(*buffer + *offset, format, args);
if (len < 0) {
perror("vsnprintf failed");
return 0;
}
va_end(args);
return 1;
}
然后你可以这样调用:
char *buffer = NULL;
int offset = 0;
int rval;
rval = append_buffer(&buffer, &offset, "%d plus %d is %d", 5, 3, 5+3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " and %d minus %d is %d", 6, 3, 6-3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " even more");
if (!rval) return 1;
printf ("[%s]",buffer);
free(buffer);