在 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 从不为您自动管理动态构造的字符串 — 这始终是的责任。

以下是您可能会使用的四种不同技术的概述。您可以提出有关其中任何不清楚的其他问题。

  1. 运行 通过你的字符串构造过程两次。进行一次以收集所有子字符串的长度,然后调用 malloc 分配计算大小的缓冲区,然后进行第二次以实际构建字符串。

  2. 使用 malloc 分配一个较小的(或空的)初始缓冲区,然后,每次要向其添加新的子字符串时,检查缓冲区的大小,如果有必要使用 realloc 将其变大。 (在这种情况下,我总是使用 三个 变量:(1) 指向缓冲区的指针,(2) 分配的缓冲区大小,(3) 当前缓冲区中的字符数。目标是始终保持 (2) ≥ (3).)

  3. 分配一个动态增长的“memstream”并使用fprintf 或类似的方法“打印”它。这是一种理想的技术,尽管 memstream 不是标准的并且并非在所有平台上都受支持,而且动态分配的 memstream 更加奇特且不常见。 (自己写也是可以的,但是工作量很大。)你可以使用 fmemopen 打开一个固定大小的 memstream(虽然这不是你想要的),你可以打开圣杯,一个动态的- 使用 open_memstream 分配 memstream(这是你想要的),如果你有的话。两者都记录在 this man page 上。 (此技术类似于 C++ 中的 stringstream。)

  4. “乞求宽恕胜于请求许可”的技巧。您可以分配一个您非常确定足够大的缓冲区,然后盲目地将所有子字符串填充到其中,然后在最后调用 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);

请注意,为简洁起见,此实现省略了对 reallocsnprintf 的检查。它还重复格式字符串和参数。以下函数解决了这些缺点:

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);