【问题标题】:Memory allocation for a string字符串的内存分配
【发布时间】:2021-07-27 11:44:58
【问题描述】:

只需运行以下代码,我就会得到Bus error: 10(在运行时):

char *x;
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);

Looking around看了几篇帖子,问题好像是我想修改一个字符串文字,但是在上面的代码中,我想,我不是。 此外,如果我尝试使用mallocx 分配一些内存,一切正常,而且如果我分配的内存少于存储整个字符串所需的内存,即使我分配0,它也可以工作。

char *x = malloc(0);
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);
printf("%s\n", x); // the quick brown fox jumps over the lazy dog

我也知道这在某种程度上与内存分配有关,但我想指定 x 而不告诉编译器它将占用多少内存,因为 y 可能是一个非常长的字符串,甚至是一个数字...

我应该如何做我想做的事,只使用字符指针(而不是字符数组),而不必知道x 的最终长度,例如无需计算x 的最大大小,然后分配内存?

【问题讨论】:

  • 覆盖未分配的内存会导致未定义的行为。“未定义的行为”包括“显然工作正常”
  • char *y = "the quick"; 将字符串文字分配给指针 y -- 没有尝试修改。 char *x = malloc(0); 调用未定义的行为,因为要分配的大小必须大于 0。当您尝试sprintf(x, "%s brown fox jumps over the lazy dog", y); BOOM SegFault 时,您没有有效分配的内存。
  • @Jabberwocky 我应该如何在不使用字符数组或指定x 的最终大小的情况下将这两个字符串连接在一起?
  • @Giuppox 在下面看到我的回答

标签: c printf malloc


【解决方案1】:

sprintf 存储字符串,包括指定到由其第一个参数指定的数组中的转换。程序员有责任为转换后的字符串传递一个足够大的数组,包括它的最终终止符。

在您的第一个示例中,通过未初始化的指针写入具有未定义的行为,如您所见,这可能会导致崩溃。

在第二个示例中,malloc(0) 可能返回 NULL 或一个不应用于写入的有效指针,因为它指向大小为 0 的数组。在这两种情况下,sprintf 都会调用未定义的行为,在这种情况下,您的机器在您测试时会产生预期的行为,因为无效写入未被检测到,但可能会导致仅在以后可见的副作用。

这是修改后的版本:

char x[80];
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);
printf("%s\n", x); // the quick brown fox jumps over the lazy dog

使用snprintf() 并传递目标数组的实际大小以避免未定义的行为(如果它恰好太短)会更安全。字符串将被截断以适合数组,但返回值将是转换生成的总字节数:

char x[25];
char *y = "the quick";
int n = snprintf(x, sizeof x, "%s brown fox jumps over the lazy dog", y);
printf("%s, needs %d bytes\n", x, n + 1); // the quick brown fox jump, needs 44 bytes

您可以通过使用空指针和零大小调用snprintf 来确定所需的大小:

size_t minimum_size = 1 + snprintf(NULL, 0, "%s brown fox jumps over the lazy dog", y);

一些系统还有一个替代函数asprintf,它会自动分配所需的内存,但这是一个未广泛使用的 GNU 扩展。

【讨论】:

    【解决方案2】:

    坏消息是,如果不计算缓冲区的最终大小,您将无法做到这一点。

    好消息是您可以让snprintf 为您计算。

    const char *y = "the quick";
    const char *fmt = "%s brown fox jumps over the lazy dog";
    
    int sizeneeded = snprintf(NULL, 0, fmt, y) + 1;
    char *dest = malloc(sizeneeded);
    snprintf(dest, sizeneeded, fmt, y);
    printf("%s\n", dest); // the quick brown fox jumps over the lazy dog
    

    免责声明:为简洁起见,此处未检查 malloc 的返回值是否为 NULL

    【讨论】:

    • 可能是我机器的问题。但是使用 gcc 编译代码不会打印预期句子的最后一个字符(the quick brown fox jumps over the lazy do,没有g
    • @Giuppox 不,这是我的代码。它应该是 snprintf(dest, sizeneeded + 1, fmt, y); 而不是 snprintf(dest, sizeneeded, fmt, y);。我更正了答案。
    【解决方案3】:

    ...问题似乎是我正在尝试修改字符串文字,但在上面的代码中,我想,我不是。

    正确,您没有修改 ant 字符串文字。 y 指向一个字符串文字,但没有代码通过y 对字符串进行修改。

    问题(在第一个代码 sn-p 中)是 x 未初始化。

    malloc 解决了这个问题,但出现了一个新问题。在你写的时候,你分配的内存太少了。

    ...问题似乎是我正在尝试修改字符串文字,但在上面的代码中,我想,我不是。

    现在怎么可能?

    发生的情况是您写入数组外部的内存(越界访问)。 C 标准将其定义为“未定义的行为”。

    “未定义行为”表示任何事情都可能发生。例如,允许程序崩溃。但是该程序也可能(似乎)像您预期的那样工作。这发生在你身上......你的代码是错误的 - 有未定义的行为 - 但幸运(或者更不幸)它似乎工作了。

    【讨论】:

      【解决方案4】:

      问题是在您尝试使用0 分配后,x 没有指向有效的内存块。要将这两个字符串成功地连接到一个新分配的块中,您需要知道同时保存这两个字符串所需的字符总数。获得总数的一种方便方法是使用:

      snprintf (NULL, 0, "format-string", vars, ...);
      

      这将返回组合字符串所需的字符总数。然后分配 total + 1 为 nul 终止字符提供空间。你可以这样做:

      #include <stdio.h>
      #include <stdlib.h>
      
      int main (void) {
          
          char *x;
          char *y = "the quick";
          
          /* use snprintf (NULL, 0, "format", vars...) to get no. of chars needed */
          size_t needed = snprintf (NULL, 0, "%s brown fox jumps over the lazy dog", y);
          
          x = malloc (needed + 1);    /* allocate for x, +1 for the nul-terminating char */
          if (!x) {                   /* validate EVERY allocation */
              perror ("malloc-x");
              return 1;
          }
          
          /* now use sprintf to joins string in newly allocated block */
          sprintf (x, "%s brown fox jumps over the lazy dog", y);
          
          puts (x);       /* output result */
          free (x);       /* don't forget to free what you allocate */
      }
      

      使用/输出示例

      $ ./bin/snprintfNULL0
      the quick brown fox jumps over the lazy dog
      

      内存使用/错误检查*

      在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

      您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。

      对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

      $ valgrind ./bin/snprintfNULL0
      ==31830== Memcheck, a memory error detector
      ==31830== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
      ==31830== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
      ==31830== Command: ./bin/snprintfNULL0
      ==31830==
      the quick brown fox jumps over the lazy dog
      ==31830==
      ==31830== HEAP SUMMARY:
      ==31830==     in use at exit: 0 bytes in 0 blocks
      ==31830==   total heap usage: 2 allocs, 2 frees, 1,069 bytes allocated
      ==31830==
      ==31830== All heap blocks were freed -- no leaks are possible
      ==31830==
      ==31830== For counts of detected and suppressed errors, rerun with: -v
      ==31830== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
      

      始终确认您已释放已分配的所有内存并且没有内存错误。

      看看这个,如果你有任何问题,请告诉我。

      【讨论】:

      • 您好,感谢您的回答。在你的例子中,如果 malloc 失败并且 x 为 NULL,我们不应该释放它吗?
      • 不需要,如果malloc() 失败,没有分配任何东西,所以没有任何东西可以释放:)
      猜你喜欢
      • 2011-03-22
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      • 1970-01-01
      • 2022-12-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多