【问题标题】:String manipulation & memory allocation - C字符串操作和内存分配 - C
【发布时间】:2010-07-07 08:19:28
【问题描述】:

我正在学习C。我有一个方法,它需要3个字符串并将它们组合起来做一些操作。以下是我使用 GCC 编译器的第一个实现。

void foo(const char *p1, const char *p2, const char *p3)
{
    size_t length = strlen(p1) + strlen(p2) + strlen(p3);
    char combined[length + 1];
    memset(combined, 0, length + 1);
    strcat(combined, p1);
    strcat(combined, p2);
    strcat(combined, p3);
    printf("Result : %s", combined);
}

int main()
{
    foo("hello ", "world ", "how");
    return 0;
}

这很好用。但是当我使用cc -Wall -pedantic -g foo.c -o foo 编译它时,我开始收到ISO C90 forbids variable length array ‘combined’ 之类的警告。 MSVC 未编译此代码。修改了类似的代码

void foo(const char *p1, const char *p2, const char *p3)
{
    size_t length = strlen(p1) + strlen(p2) + strlen(p3);
    char *combined = (char *) malloc(length + 1);
    memset(combined, 0, length + 1);
    strcat(combined, p1);
    strcat(combined, p2);
    strcat(combined, p3);
    printf("Result : %s", combined);
    free(combined);
}

问题

  1. 这是正确的实现吗?
  2. 如果可变长度数组不是标准的一部分,为什么 GCC 实现它?如果预计代码只能在 GCC 上编译,那么使用变量数组会比使用 malloc 更好吗?
  3. 我认为经验法则是,如果在编译时知道所需的内存,则使用数组,否则使用 malloc 分配所需的内存。这是正确的吗?
  4. 我的代码预计可以在 GCC 和 MSVC 上编译。我通常会在 GCC 上进行开发。那么确保最大可移植性的编译器标志是什么?目前我正在使用-Wall -pedantic。我也应该使用-ansi 吗? MSVC 中可用的等效标志是什么?
  5. 编写可移植 C 代码时还需要考虑哪些其他常见事项?

【问题讨论】:

  • (1)是的,正如您在第 3 点中提到的那样 (2)因为 gcc 还支持除 ISO C90 之外的其他标准。 (5)google.com/search?q=writing+portable+C+code动态分配VLA总是比较好,因为你可以查看(m/c)alloc的返回值,看看分配是否成功
  • @itisravi - 您应该回答,而不是在评论中回答。由于评论长度限制,您对 VLA 与动态分配的建议有点稀疏。我同意,我知道你在说什么,但目前还不清楚,因为 cmets 太短了,无法回答。这就是为什么我们有答案。 ;)
  • VLA 太大(取决于平台)也可能导致堆栈溢出。对于您的第二个变体,这看起来是正确的,但我要么使用calloc 进行分配,要么通过分配combined[0] = '\0' 来避免memset。这足以让第一个 strcat 正常工作。
  • 不需要转换返回void *的库函数。只需使用char *combined = malloc(length + 1);。另外,您想检查malloc() 的返回。顺便说一句,snprintf() 在这种情况下会更好,imo。

标签: c string portability


【解决方案1】:

这很好用。但是当我使用 cc -Wall -pedantic -g foo.c -o foo 编译它时,我开始收到诸如 ISO C90 禁止可变长度数组“组合”之类的警告。

尝试使用-std=c99 选项(gcc)进行编译。

MSVC 未编译此代码。修改了类似的代码

如果可变长度数组不是标准的一部分,为什么 GCC 会实现它?

VLA 是 ISO C99 的一部分(gcc 和 g++(作为扩展)支持 VLA)。 MSVC 仍然只支持 C89。

我的代码预计可以在 GCC 和 MSVC 上编译。

那么恕我直言,您不应该在代码中使用 VLA。

【讨论】:

    【解决方案2】:
    1. 是的,是的。那里没有具体的违反标准的行为。 memset 是浪费时间,但是因为无论如何它都会被覆盖(将你的第一个 strcat 变成 strcpy)。您应该始终检查malloc 是否返回NULL。 无论如何!
    2. C89/90 不是当前标准,C99 是。而 C1x 并不遥远。 GCC 紧跟前沿。
    3. 仅当您不需要它们在函数结束后继续存在时才使用本地数组。否则malloc 是您最好的选择,特别是如果您想返回组合字符串。
    4. 我认为 gcc 有 -std=c89 标志或类似的东西。无论如何,MSVC 并不总是遵循标准:-)
    5. 经常在两个平台上编译和测试它。这是唯一确定的方法。

    我会选择:

    void foo (const char *p1, const char *p2, const char *p3) {
        size_t length = strlen(p1) + strlen(p2) + strlen(p3);
        char *combined = (char *) malloc(length + 1);
        if (combined == NULL) {
            printf("Result : <unknown since I could't get any memory>\n");
        } else {
            strcpy(combined, p1);
            strcat(combined, p2);
            strcat(combined, p3);
            printf("Result : %s", combined);
            free(combined);
        }
    }
    

    或者,因为您实际上并没有对字符串做任何事情,除了打印它:

    void foo (const char *p1, const char *p2, const char *p3) {
        printf("Result : %s%s%s", p1, p2, p3);
    }
    

    :-)

    我见过的另一种策略是“仅在必要时分配”策略:

    void foo (const char *p1, const char *p2, const char *p3) {
        char str1k[1024];
        char *combined;
        size_t length = strlen (p1) + strlen (p2) + strlen (p3) + 1;
        if (length <= sizeof(str1k))
            combined = str1k;
        else
            combined = malloc (length);
        if (combined == NULL) {
            printf ("Result : <unknown since I couldn't get any memory>\n");
        } else {
            strcpy (combined, p1);
            strcat (combined, p2);
            strcat (combined, p3);
            printf ("Result : %s", combined);
        }
        if (combined != str1k)
            free (combined);
    }
    

    如果组合字符串适合,则使用堆栈存储,如果不适合则仅分配内存。如果大量字符串组合成小于限制,这通常可以显着提高速度。

    【讨论】:

    • 优秀的答案。非常感谢你。我将使用该字符串执行一些操作,而不仅仅是打印。为了便于解释,省略了这些。
    【解决方案3】:

    可变长度数组不是第一个 ISO C 标准(不同地称为“C89”、“C90”或“ANSI C”)的一部分。但是,它们最新 ISO C 标准(称为“C99”)的一部分。

    GCC 可以在多种模式下编译您的代码,包括“strict C90”、“C90-with-GNU-C-extensions”和“C99”(尽管它没有完全实现 C99,它对于大多数实际用途来说已经足够接近了)。

    默认情况下,GCC 使用“C90-with-GNU-C-extensions”,这就是为什么您的代码可以毫无怨言地编译的原因。使用-pedantic 告诉它按照相关标准(在本例中为C90)发出所有必需的警告,并且您的代码需要这样的警告。如果你给 GCC 一个 -std=c99 -pedantic 标志,告诉它根据 C99 基本标准编译并发出所有必需的警告,你的代码编译得很好。

    如果您想确保您的代码与基本 C90 标准兼容,请使用 -std=c90 -pedantic(或 -ansi -pedantic:在编译 C 代码时,-ansi-std=c90 的同义词)。请注意,MSVC 不支持 C99。

    【讨论】:

      【解决方案4】:

      解决这些问题的一个非常常见的习惯用法是让调用者管理内存。因此,您希望调用者提供内存,而不是自己分配内存(使用堆栈上的可变长度数组或malloc'ing 或其他方式)。考虑一下:

      int foo(const char *p1, const char *p2, const char *p3, char *buf, size_t bufsize)
      {
          size_t requiredSize = strlen(p1) + strlen(p2) + strlen(p3) + 1;
          if (!buf)
              return requiredSize;
          if (requiredSize > bufsize)
              return -1;
          buf[0] = '\0';
          strcat(buf, p1);
          strcat(buf, p2);
          strcat(buf, p3);
          return requiredSize;
      }
      
      int main()
      {
        /* simple case: caller knows that the buffer is large enough. */
        char buf[ 1024 ];
        foo( "Hello", "World", "Bar", buf, sizeof(buf) );
        printf("Result : %s\n", buf);
      
        /* complicated case: caller wants to allocate buffer of just the right size */
        size_t bufsize = foo( "Hello", "World", "Bar", NULL, 0 );
        char *buf2 = (char *)malloc(bufsize);
        foo( "Hello", "World", "Bar", buf2, bufsize );
        free( buf2 );
      }
      

      这种方法的优点是foo 永远不会泄漏。除此之外,调用者可以使用一个简单的基于堆栈的数组,以防它适用于他。如果他想知道确切的大小,他可以调用foo 并将NULL 作为第四个参数传递。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-17
        • 1970-01-01
        • 2011-03-22
        • 1970-01-01
        • 1970-01-01
        • 2021-07-27
        • 1970-01-01
        相关资源
        最近更新 更多