如何在C中连接const/literal字符串?

The*_*i.9 324 c string concatenation

我在C工作,我必须连接一些东西.

现在我有这个:

message = strcat("TEXT ", var);

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));
Run Code Online (Sandbox Code Playgroud)

现在,如果您有C语言经验,我确信您在尝试运行时会发现这会给您带来分段错误.那我该如何解决呢?

Bri*_*ndy 359

在C中,"字符串"只是普通char数组.因此,您无法直接将它们与其他"字符串"连接起来.

您可以使用该strcat函数,该函数将指向的字符串附加到指向的字符串src的末尾dest:

char *strcat(char *dest, const char *src);
Run Code Online (Sandbox Code Playgroud)

以下是cplusplus.com示例:

char str[80];
strcpy(str, "these ");
strcat(str, "strings ");
strcat(str, "are ");
strcat(str, "concatenated.");
Run Code Online (Sandbox Code Playgroud)

对于第一个参数,您需要提供目标缓冲区本身.目标缓冲区必须是char数组缓冲区.例如:char buffer[1024];

确保第一个参数有足够的空间来存储您尝试复制到其中的内容.如果您可以使用,则使用以下函数会更安全:strcpy_s并且strcat_s您必须明确指定目标缓冲区的大小.

注意:字符串文字不能用作缓冲区,因为它是常量.因此,您始终必须为缓冲区分配char数组.

strcat可以简单地忽略返回值,它只返回与第一个参数传入的指针相同的指针.它是为了方便,允许您将调用链接到一行代码:

strcat(strcat(str, foo), bar);
Run Code Online (Sandbox Code Playgroud)

所以你的问题可以解决如下:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);
Run Code Online (Sandbox Code Playgroud)

  • 你能用粗体"请小心......"吗?这不够强调.滥用strcat,strcpy和sprintf是不稳定/不安全软件的核心. (62认同)
  • 对于第二个@dolmen,Joel Spolsky已就此问题撰写了[相当精细的文章](http://www.joelonsoftware.com/articles/fog0000000319.html).应该是强制性阅读.;-) (19认同)
  • 警告:正如所写,这段代码将在代码中留下一个巨大的漏洞,用于缓冲区溢出攻击. (12认同)
  • @psihodelia:也不要忘记勺子比叉子好多了!所以一定要用汤匙! (12认同)
  • 在上面的例子中没有可能的缓冲区溢出漏洞利用.是的,我同意一般我不会使用上面的例子来确定foo和bar的未定字符串长度. (9认同)
  • 不要多次使用`strcat`!`strcat`必须检查你已连接的所有前面的字节(搜索''\ 0'`).这是无用的处理. (2认同)

Ale*_*x B 237

避免strcat在C代码中使用.最干净,最重要的是,最安全的方法是使用snprintf:

char buf[256];
snprintf(buf, sizeof buf, "%s%s%s%s", str1, str2, str3, str4);
Run Code Online (Sandbox Code Playgroud)

一些评论者提出了一个问题,即参数的数量可能与格式字符串不匹配,代码仍然会编译,但大多数编译器已经发出警告,如果是这种情况.

  • 我的一个小小的烦恼是像@unwind这样的人坚持`sizeof(x)`和`sizeof x`之间毫无意义的区别.带括号的表示法总是有效,而无表达式的表示法有时只能起作用,所以总是使用带括号的表示法; 这是一个简单的规则,要记住并且是安全的.这引起了宗教争论 - 我参与了与之前反对的人的讨论 - 但'总是使用括号'的简单性超过了不使用它们的任何优点(当然,IMNSHO).这是为了平衡. (33认同)
  • sizeof()仅适用于char buf [...].不是char*buf = malloc(...).数组和指针之间没有太多差异,但这是其中之一! (4认同)
  • @MrRee:指针和数组之间的差异是巨大而完整的!这是你如何_use_他们并不总是不同.此外,指针和动态分配实际上是正交概念. (4认同)
  • 跳棋,他正在谈论围绕着"buf"的大小论点的括号.如果参数是表达式,则不需要它们.但我不明白为什么你被投票.我认为你的答案是最好的,即使它是c99.(也许是因为他们不同意!拉默斯!)+ 1 (3认同)
  • 此外,他正在尝试进行连接.使用`snprintf()`连接是一个大不行的. (2认同)
  • @Leonardo,你能告诉我为什么吗? (2认同)

Mr.*_*Ree 24

伙计们,使用str n cpy(),str n cat()或s n printf().
超过你的缓冲空间会破坏内存中的其他内容!
(并且记得为尾随的空'\ 0'字符留出空间!)

  • 不要使用`strncpy()`.它不是*strcpy()`的"更安全"版本.目标字符数组可能会被不必要地填充额外的''\ 0'字符,或者更糟糕的是,它可能未被终止(即,不是字符串).(它被设计用于一个很少使用的数据结构,一个字符数组用零或多个''\ 0'`字符填充到最后.) (9认同)
  • 您不仅要记得为NULL字符留出空间,还需要记住*添加*NULL字符.strncpy和strncat不会为你做那件事. (3认同)
  • @unwind,我认为Graeme的意思是如果缓冲区太小,strncpy或strncat将_not_添加终止'\ 0'. (3认同)
  • snprintf很好,strncpy/strncat是最糟糕的建议,strlcpy/strlcat要好得多. (2认同)

小智 17

字符串也可以在编译时连接.

#define SCHEMA "test"
#define TABLE  "data"

const char *table = SCHEMA "." TABLE ; // note no + or . or anything
const char *qry =               // include comments in a string
    " SELECT * "                // get all fields
    " FROM " SCHEMA "." TABLE   /* the table */
    " WHERE x = 1 "             /* the filter */ 
                ;
Run Code Online (Sandbox Code Playgroud)


Ree*_*ges 16

如果您事先不知道连接多少个字符串,malloc和realloc也很有用.

#include <stdio.h>
#include <string.h>

void example(const char *header, const char **words, size_t num_words)
{
    size_t message_len = strlen(header) + 1; /* + 1 for terminating NULL */
    char *message = (char*) malloc(message_len);
    strncat(message, header, message_len);

    for(int i = 0; i < num_words; ++i)
    {
       message_len += 1 + strlen(words[i]); /* 1 + for separator ';' */
       message = (char*) realloc(message, message_len);
       strncat(strncat(message, ";", message_len), words[i], message_len);
    }

    puts(message);

    free(message);
}
Run Code Online (Sandbox Code Playgroud)


pax*_*blo 5

尝试修改字符串文字是未定义的行为,类似于:

strcat ("Hello, ", name);
Run Code Online (Sandbox Code Playgroud)

会尝试做。它将尝试name将字符串附加到字符串文字的末尾"Hello, ",这不是很好的定义。

试试这个。它实现了您似乎想要做的事情:

char message[1000];
strcpy (message, "TEXT ");
strcat (message, var);
Run Code Online (Sandbox Code Playgroud)

这将创建一个缓冲区域允许修改,然后拷贝这两个字符串文字等文本。小心缓冲区溢出。如果您控制输入数据(或事先检查它),则可以像我一样使用固定长度的缓冲区。

否则,您应该使用缓解策略,例如从堆中分配足够的内存以确保您可以处理它。换句话说,类似于:

const static char TEXT[] = "TEXT ";

// Make *sure* you have enough space.

char *message = malloc (sizeof(TEXT) + strlen(var) + 1);
if (message == NULL)
     handleOutOfMemoryIntelligently();
strcpy (message, TEXT);
strcat (message, var);

// Need to free message at some point after you're done with it.
Run Code Online (Sandbox Code Playgroud)

  • @paxdiablo:但是在回答一个似乎需要提及的问题时,您甚至没有提到它。这使您的答案**危险**。您也没有解释为什么这段代码比 OP 的原始代码更好,除了“实现与原始代码相同的结果”的神话(那么重点是什么?原始代码是 _broken_!),所以答案也是**不完整**。 (7认同)
  • 如果 var/foo/bar 超过 1000 个字符会发生什么?&gt;:) (4认同)

小智 5

如果您有 C 方面的经验,您会注意到字符串只是最后一个字符是空字符的 char 数组。

现在这很不方便,因为您必须找到最后一个字符才能附加某些内容。strcat会为你做的。

所以 strcat 在第一个参数中搜索空字符。然后它将用第二个参数的内容替换它(直到它以空值结尾)。

现在让我们来看看你的代码:

message = strcat("TEXT " + var);
Run Code Online (Sandbox Code Playgroud)

在这里,您向指向文本“TEXT”的指针添加了一些内容(“TEXT”的类型是 const char*。一个指针。)。

那通常是行不通的。修改“TEXT”数组也不起作用,因为它通常放在一个常量段中。

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));
Run Code Online (Sandbox Code Playgroud)

这可能会更好,除非您再次尝试修改静态文本。strcat 没有为结果分配新的内存。

我建议做这样的事情:

sprintf(message2, "TEXT %s TEXT %s", foo, bar);
Run Code Online (Sandbox Code Playgroud)

阅读文档sprintf以检查它的选项。

现在重要的一点:

确保缓冲区有足够的空间来容纳文本和空字符。有几个函数可以帮助您,例如 strncat 和特殊版本的 printf 为您分配缓冲区。不确保缓冲区大小将导致内存损坏和可远程利用的错误。


Dav*_*eas 5

不要忘记初始化输出缓冲区.strcat的第一个参数必须是以空字符结尾的字符串,并为结果字符串分配足够的额外空间:

char out[1024] = ""; // must be initialized
strcat( out, null_terminated_string ); 
// null_terminated_string has less than 1023 chars
Run Code Online (Sandbox Code Playgroud)


Nil*_*ils 5

正如人们指出的那样,字符串处理有了很大改进。因此,您可能想学习如何使用 C++ 字符串库而不是 C 样式字符串。然而这里有一个纯C的解决方案

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void appendToHello(const char *s) {
    const char *const hello = "hello ";

    const size_t sLength     = strlen(s);
    const size_t helloLength = strlen(hello);
    const size_t totalLength = sLength + helloLength;

    char *const strBuf = malloc(totalLength + 1);
    if (strBuf == NULL) {
        fprintf(stderr, "malloc failed\n");
        exit(EXIT_FAILURE);
    }

    strcpy(strBuf, hello);
    strcpy(strBuf + helloLength, s);

    puts(strBuf);

    free(strBuf);

}

int main (void) {
    appendToHello("blah blah");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我不确定它是否正确/安全,但现在我找不到更好的方法在 ANSI C 中执行此操作。

  • @MooingDuck:这是不正确的。`#include &lt;string.h&gt;` 是正确的 C. 对标准头和系统头(包括 `&lt;string.h&gt;`)使用尖括号,对属于程序一部分的头使用引号。(如果您没有自己的同名头文件,`#include "string.h"` 将会起作用,但无论如何使用 `&lt;string.h&gt;`。) (7认同)
  • @MooingDuck:`“string.h”`是无稽之谈。 (6认同)

Nic*_*tak 5

没有限制缓冲区大小的最佳方法是使用 asprintf()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

  • 你应该返回 `char *`,而不是 `const char *`。返回值需要传递给 `free`。 (2认同)
  • 不幸的是 `asprintf` 只是一个 GNU 扩展。 (2认同)