【问题标题】:Declare string in C without giving size在 C 中声明字符串而不给出大小
【发布时间】:2021-10-21 03:29:29
【问题描述】:

我想在一个字符串中连接多个句子。目前我的缓冲区大小固定为 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);  

【问题讨论】:

  • 使用malloc 获取指向任意大小的字符缓冲区的指针。 char* buffer = malloc(sizeof(char)*MY_SIZE);
  • 如果需要增长从malloc()获取的数组,使用realloc()
  • 这能回答你的问题吗? Is it possible to modify a string of char in C?
  • snprintf()(C99 及更高版本:注意不符合标准的 Windows 实现)报告避免缓冲区溢出所需的大小。
  • 我的错:snprintf() 在 Windows 实现上曾经是(2019 年之前??)不符合标准。

标签: c string malloc concatenation realloc


【解决方案1】:

这是 C 的一个基本方面。C 永远不会为您自动管理动态构造的字符串——这始终是您的责任。

这里概述了您可能会使用的四种不同技术。您可以就其中任何不清楚的问题提出其他问题。

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

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

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

  4. “请求宽恕胜过请求许可”的技巧。您可以分配一个您确定足够大的缓冲区,然后盲目地将所有子字符串填充到其中,然后在最后调用strlen,如果您猜错并且字符串比缓冲区长你分配,打印一个大的可怕嘈杂的错误消息并中止。这是一种生硬且有风险的技术,而不是您在生产程序中使用的技术。当您溢出缓冲区时,您可能会以某种方式损坏程序,导致程序在有机会执行其迟来的检查并可能退出步骤之前崩溃。如果您完全使用了这种技术,那么如果您使用malloc 分配缓冲区,那么它会相当“安全”(也就是说,在检查之前过早崩溃的可能性要小得多),而不是将其声明为普通的固定 -大小数组(无论是静态的还是本地的)。

就个人而言,我已经使用了所有这四个。在世界其他地方,数字 1 和 2 几乎每个人都在使用。比较它们:数字 1 简单且容易一些,但代码复制量令人不舒服(因此如果以后添加新字符串可能会很脆弱);数字 2 更健壮,但显然需要您对 realloc 的工作方式感到满意(如果您的缓冲区中有任何辅助指针需要重定位,那么这种技术可能不那么健壮)每次调用realloc)。

数字 3 是一种“异国情调”的技术:理论上几乎是理想的,但肯定更复杂,需要一些额外的支持,因为没有像 open_memstream 这样的标准。 而第 4 项显然是一种冒险且本质上可靠的技术,如果有的话,你只会在一次性或原型代码中使用它,而不是在生产中。

【讨论】:

  • 我不确定我是否会将其描述为“限制”。它可能是 C 的一个基本方面,或一个基本特征,但它不是限制。 lisp 的一个基本限制是它不能比轻型消息传输更快。 scala 的一个基本限制是它无法在没有硬件的情况下运行以执行指令。
  • @WilliamPursell 公平点。我的意思是,您不能像在 C++ 或 BASIC 中那样将字符串作为一流的类型。你是对的:“方面”是一个更好的词。
  • 我使用obstack 来增加缓冲区,例如我从服务器接收的 JSON 文档。
  • @Cheatah 从未听说过这种特定的实现。谢谢!
【解决方案2】:

您可以使用snprintf 函数,第一个参数为NULL,第二个参数为0,获取格式化字符串的大小。然后您可以动态分配空间并再次调用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);

【讨论】:

  • 请为此创建一个函数。不要将格式字符串写两次,它违反了 DRY,当您忘记更改另一个时,您可能会忘记更改一个。并且不要忘记检查错误值(检查是否为len=&gt;0 并检查是否为!!buffer
猜你喜欢
  • 1970-01-01
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多