字符串操作和内存分配 - C

6

我正在学习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代码时需要考虑的其他常见事项有哪些?

请参阅https://dev59.com/RE7Sa4cB1Zd3GeqP45gl。 - PeterK
1
(1) 是的,正如您在第3点中提到的那样。 (2) 因为gcc还支持ISO C90之外的其他标准。 (5) http://www.google.com/search?q=writing+portable+C+code 动态分配可变长度数组总是更好的,因为您可以检查(m/c)alloc的返回值以确定分配是否成功。 - itisravi
@itisravi - 你应该回答问题,而不是在评论中回答。由于评论长度限制,你对VLAs与动态分配的建议有点简略。我同意你的观点,也知道你的意思,但因为评论太短了,所以不太清楚。这就是为什么我们需要回答。;) - Chris Lutz
太大的VLA(依赖于平台)也可能导致堆栈溢出。对于您的第二个变量,这看起来是正确的,但我要么使用calloc进行分配,要么通过将combined [0] =' \ 0'分配来避免memset。这对于第一个strcat正常工作已足够。 - Jens Gustedt
不需要强制转换返回 void * 的库函数。只需使用 char *combined = malloc(length + 1);。此外,您需要检查 malloc() 的返回值。顺便说一下,在这种情况下,snprintf() 更好,我个人认为。 - guest
如果你只需要使用 printf,那么你的代码非常低效。只需使用三个 %s 格式说明符。我知道这可能只是一个示例,但我见过可怕的代码(例如在 svn 客户端中),它确切地做到了这一点 - 在想要显示它们到 stdout 的所有信息字符串时,跳过了一个巨大的迷宫,动态分配和连接它们。 - R.. GitHub STOP HELPING ICE
4个回答

8

这很好运作。但是当我使用cc -Wall -pedantic -g foo.c -o foo进行编译时,我开始收到警告消息,例如ISO C90 forbids variable length array ‘combined’。

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

MSVC不能编译此代码。将代码更改为

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

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

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

那么,在我看来,您不应在代码中使用VLAs。


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

如果合并的字符串可以放入堆栈存储器中,则使用堆栈存储器,只有当无法放入时才分配内存。如果大部分字符串组合成的内容少于限制,这通常会导致显着的速度提升。

非常好的回答。非常感谢。我将使用该字符串执行除了打印之外的一些操作。这些被省略以便易于解释。 - Navaneeth K N

3

可变长度数组不是最初的ISO C标准的一部分(也被称为“C89”,“C90”或“ANSI C”)。然而,它们是最新的ISO C标准(称为“C99”)的一部分。

GCC可以以多种模式编译您的代码,包括“严格的C90”,“带有GNU C扩展的C90”和“C99”(虽然它并没有完全实现C99,但对于大多数实际用途来说,已经足够接近了)。

默认情况下,GCC使用“带有GNU C扩展的C90”,这就是为什么您的代码可以编译而没有警告。使用-pedantic参数将要求它按照相关标准(在本例中为C90)发出所有必需的警告,并且这种警告在您的代码中是必须的。如果您使用-std=c99 -pedantic参数,告诉它针对C99基准标准进行编译并发出所有必需的警告,则您的代码可以成功编译。

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


0
一个非常常见的解决这些问题的习语是让调用方管理内存。因此,您不是自己分配内存(无论是在堆栈上使用可变长度数组还是通过使用malloc等进行分配),而是期望调用方提供内存。请考虑以下内容:
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接