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


346

我正在使用C语言工作,因此我不得不串联一些东西。

现在我有这个:

message = strcat("TEXT ", var);

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));

现在,如果您有C方面的经验,我相信您会在尝试运行它时遇到分段错误。那么我该如何解决呢?


6
我建议您使用strlcat而不是strcat!gratisoft.us/todd/papers/strlcpy.html
activout.se

3
我想重复那个建议。Strcat导致漏洞可以缓冲溢出漏洞。有人可以给您的程序数据使其执行任意代码。
布赖恩

Answers:


386

在C语言中,“字符串”只是纯char数组。因此,您不能将它们与其他“字符串”直接连接。

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

char *strcat(char *dest, const char *src);

这是来自cplusplus.com示例

char str[80];
strcpy(str, "these ");
strcat(str, "strings ");
strcat(str, "are ");
strcat(str, "concatenated.");

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

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

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

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

strcat(strcat(str, foo), bar);

因此,您的问题可以通过以下方式解决:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);

66
请以粗体显示“请注意...”吗?压力不够大。滥用strcat,strcpy和sprintf是不稳定/不安全的软件的核心。
基座

12
警告:按照编写的程序,此代码将在您的代码中留下巨大的漏洞,供缓冲区溢出漏洞利用。
布赖恩

11
在上面的示例中,不可能有缓冲区溢出漏洞利用。是的,我同意,一般而言,对于不确定的foo和bar字符串长度,我不会使用上面的示例。
Brian R. Bondy

13
@psihodelia:也不要忘记,汤匙比叉子好得多!因此,请务必始终使用汤匙!
布赖恩·邦迪

20
对于第二个@dolmen,乔尔·斯波斯基(Joel Spolsky)就此问题撰写了相当详尽的文章。应该是必读的。;-)
peter.slizik 2012年

247

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

char buf[256];
snprintf(buf, sizeof buf, "%s%s%s%s", str1, str2, str3, str4);

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


3
Checkers,他在谈论sizeof参数“ buf”的括号。如果参数是表达式,则不需要它们。但是我不明白你为什么被低估。我认为您的答案是最好的,即使它是c99。(也许是因为他们不同意!拉莫斯!)+1
Johannes Schaub-litb

4
sizeof()仅在这里适用于char buf [...]。不用于char * buf = malloc(...)。数组和指针之间没有多少区别,但这就是其中之一!
Ree先生

2
另外,他正在尝试执行串联。使用串联snprintf()是一个很大的否定。
莱昂纳多·埃雷拉

5
@MrRee:指针和数组之间的差异是巨大而完整的!这是你如何使用它们并不总是不同。同样,指针和动态分配实际上是正交的概念。
Lightness Races in Orbit

34
我最讨厌的东西之一就是像@unwind这样的人,他们坚持sizeof(x)和之间没有意义的区别sizeof x。带括号的符号始终有效,非带括号的符号有时仅适用,因此请始终使用带括号的符号。这是要记住的简单规则,并且很安全。这引起了一种宗教争论-我曾参加过与反对者的讨论-但是“总是使用括号”的简单性胜过不使用括号的任何优点(当然是IMNSHO)。这是为了平衡。
乔纳森·莱夫勒2014年

24

亲朋好友,请使用str n cpy(),str n cat()或s n printf()。
超过缓冲区空间将浪费内存中的所有其他内容!
(请记住要留出空格以结尾的空“ \ 0”字符!)


3
您不仅应该记住要为NULL字符留出空间,还需要记住添加 NULL字符。strncpy和strncat不会为您这样做。
Graeme Perrow,

嗯?strncpy()和strncat()确保添加终止字符。实际上,它们增加了太多。至少只要缓冲区中有剩余空间,这就是这些调用的巨大陷阱。不建议。
放松

3
@unwind,我认为Graeme的要点是,如果缓冲区太小,strncpy或strncat将不会添加终止符'\ 0'。
quinmars

2
snprintf很好,strncpy / strncat是最糟糕的建议,strlcpy / strlcat更好。
罗伯特·格兰伯

9
不要使用strncpy()。这不是的“更安全”版本strcpy()。目标字符数组可能不必要地被多余的'\0'字符填充,或者更糟的是,它可能会不加终止(即,不是字符串)。(它设计用于一个已经很少使用的数据结构,一个字符数组填充到零个或多个'\0'字符的末尾。)
Keith Thompson

22

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

#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 */ 
                ;

15

如果您不提前知道要连接多少个字符串,那么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);
}

这将在一个无限循环结束的时候num_words>INT_MAX,也许你应该使用size_ti
12431234123412341234123

5

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

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

4

正如人们指出的那样,字符串处理有了很大的改进。因此,您可能想学习如何使用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;
}

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


<string.h>是C ++风格。你要"string.h"。您还会计算strlen(s1)两次,这是不需要的。 s3应该很totalLenght+1长。
Mooing Duck 2011年

4
@MooingDuck:"string.h"废话。
2011年

我已经有一段时间没有使用C风格的字符串了。随时发布固定版本。
尼尔斯

4
@MooingDuck:不正确。 #include <string.h>是正确的C。对标准和系统标题(包括<string.h>)使用尖括号,在程序的标题中使用引号引起来。(#include "string.h"如果您没有该名称的头文件,但<string.h>无论如何都可以使用。)
Keith Thompson

请注意,这取决于C99特定的功能:混合声明和语句以及可变长度数组(VLA)。还请注意,VLA不提供检测或处理分配失败的机制;如果没有足够的空间分配VLA,则程序的行为是不确定的。
基思·汤普森

4

尝试修改字符串文字是未定义的行为,如下所示:

strcat ("Hello, ", name);

将尝试做。它将尝试将name字符串添加到字符串文字的末尾,该文字"Hello, "未正确定义。

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

char message[1000];
strcpy (message, "TEXT ");
strcat (message, var);

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

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

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.

4
如果var / foo / bar的字符数超过1000,会发生什么情况?> :)
地球

1
然后,您将获得缓冲区溢出,可以添加代码以进行事先检查(例如,使用strlen)。但是,代码段的目的是在不增加过多代码的情况下显示某些东西是如何工作的。否则,我将检查长度,是否var / foo / bar为null,等等
。– paxdiablo

7
@paxdiablo:但是,您甚至没有提到它,它回答了一个似乎需要提及的问题。那使你的回答很危险。您也没有解释为什么此代码比OP的原始代码更好,除了一个神话,即它“与原始代码具有相同的结果”(那是什么意思?原始代码已损坏!),所以答案就在这里。也是不完整的
Lightness Races in Orbit

希望已经解决了您的问题,@PreferenceBean,尽管它的时间不如理想的时候要快:-)让我知道您是否仍然对答案有疑问,我会做进一步的改进。
paxdiablo '16

3

strcat()的第一个参数需要能够为串联的字符串保留足够的空间。因此,为缓冲区分配足够的空间以接收结果。

char bigEnough[64] = "";

strcat(bigEnough, "TEXT");
strcat(bigEnough, foo);

/* and so on */

strcat()将第二个参数与第一个参数连接起来,并将结果存储在第一个参数中,返回的char *就是第一个参数,仅是为了您的方便。

您不会得到一个新分配的字符串,该字符串的第一个参数和第二个参数是连接在一起的,我猜您会根据您的代码来预期。


3

最好的方法是在没有缓冲区大小限制的情况下使用asprintf()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}

2
您应该返回char *,没有const char *。返回值将需要传递给free
Per Johansson,

不幸的asprintf是,这只是一个GNU扩展。
Calmarius13年

3

如果您有使用C的经验,您会注意到字符串只是char数组,其中最后一个字符为空字符。

现在这很不方便,因为您必须找到最后一个字符才能添加一些内容。 strcat将为您做到这一点。

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

现在让我们来看一下您的代码:

message = strcat("TEXT " + var);

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

那通常是行不通的。同样,修改“ TEXT”数组也将不起作用,因为通常将其放置在恒定段中。

message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));

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

我建议改为执行以下操作:

sprintf(message2, "TEXT %s TEXT %s", foo, bar);

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

现在重要的一点是:

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


该类型的"TEXT"char[5]没有 const char*char*在大多数情况下,它都会衰减。出于向后兼容的原因,字符串文字不是const,但是尝试对其进行修改将导致未定义的行为。(在C ++中,字符串文字为const。)
Keith Thompson

2

您可以编写自己的函数,该函数执行的功能相同,strcat()但不会改变任何内容:

#define MAX_STRING_LENGTH 1000
char *strcat_const(const char *str1,const char *str2){
    static char buffer[MAX_STRING_LENGTH];
    strncpy(buffer,str1,MAX_STRING_LENGTH);
    if(strlen(str1) < MAX_STRING_LENGTH){
        strncat(buffer,str2,MAX_STRING_LENGTH - strlen(buffer));
    }
    buffer[MAX_STRING_LENGTH - 1] = '\0';
    return buffer;
}

int main(int argc,char *argv[]){
    printf("%s",strcat_const("Hello ","world"));    //Prints "Hello world"
    return 0;
}

如果两个字符串的长度都超过1000个字符,则它将字符串切成1000个字符。您可以更改值MAX_STRING_LENGTH以适合您的需求。


我预见到缓冲区溢出,看到您已分配strlen(str1) + strlen(str2),但您写了strlen(str1) + strlen(str2) + 1字符。那么,您真的可以编写自己的函数吗?
利维

哇!您永远不会释放记忆,讨厌,讨厌!return buffer; free(buffer);
Liviu

顺便说一句,sizeof(char) == 1(此外,还有其他更细微的错误...)您现在可以看到为什么不必编写自己的函数了吗?
Liviu

@Liviu我确实释放了该行的内存free(buffer);
唐老鸭

1
free(buffer);之后return buffer;永远不会执行,请在调试器中查看它;)我现在看到了:是的,您必须释放该main函数中的内存
Liviu

1

假设您使用的是char [fixed_size]而不是char *,则可以使用单个广告素材宏通过一次<<cout<<like命令(“%s the相交的%s \ n”,“ than”,“ printf样式格式”)。如果您使用的是嵌入式系统,则此方法还将允许您省去malloc和*printf诸如此类的大型函数snprintf()(这也使Dietlibc也不必抱怨* printf)

#include <unistd.h> //for the write example
//note: you should check if offset==sizeof(buf) after use
#define strcpyALL(buf, offset, ...) do{ \
    char *bp=(char*)(buf+offset); /*so we can add to the end of a string*/ \
    const char *s, \
    *a[] = { __VA_ARGS__,NULL}, \
    **ss=a; \
    while((s=*ss++)) \
         while((*s)&&(++offset<(int)sizeof(buf))) \
            *bp++=*s++; \
    if (offset!=sizeof(buf))*bp=0; \
}while(0)

char buf[256];
int len=0;

strcpyALL(buf,len,
    "The config file is in:\n\t",getenv("HOME"),"/.config/",argv[0],"/config.rc\n"
);
if (len<sizeof(buf))
    write(1,buf,len); //outputs our message to stdout
else
    write(2,"error\n",6);

//but we can keep adding on because we kept track of the length
//this allows printf-like buffering to minimize number of syscalls to write
//set len back to 0 if you don't want this behavior
strcpyALL(buf,len,"Thanks for using ",argv[0],"!\n");
if (len<sizeof(buf))
    write(1,buf,len); //outputs both messages
else
    write(2,"error\n",6);
  • 注意1,您通常不会像这样使用argv [0]-只是一个示例
  • 注意2,您可以使用任何输出char *的函数,包括非标准函数(如itoa()),用于将整数转换为字符串类型。
  • 注意3,如果您已经在程序中的任何位置使用了printf,则没有理由不使用snprintf(),因为编译后的代码会更大(但内联且速度明显更快)。

1
int main()
{
    char input[100];
    gets(input);

    char str[101];
    strcpy(str, " ");
    strcat(str, input);

    char *p = str;

    while(*p) {
       if(*p == ' ' && isalpha(*(p+1)) != 0)
           printf("%c",*(p+1));
       p++;
    }

    return 0;
}


0

这是我的解决方案

#include <stdlib.h>
#include <stdarg.h>

char *strconcat(int num_args, ...) {
    int strsize = 0;
    va_list ap;
    va_start(ap, num_args);
    for (int i = 0; i < num_args; i++) 
        strsize += strlen(va_arg(ap, char*));

    char *res = malloc(strsize+1);
    strsize = 0;
    va_start(ap, num_args);
    for (int i = 0; i < num_args; i++) {
        char *s = va_arg(ap, char*);
        strcpy(res+strsize, s);
        strsize += strlen(s);
    }
    va_end(ap);
    res[strsize] = '\0';

    return res;
}

但您需要指定要连接的字符串数

char *str = strconcat(3, "testing ", "this ", "thing");

0

试试类似的东西:

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

int main(int argc, const char * argv[])
{
  // Insert code here...
  char firstname[100], secondname[100];
  printf("Enter First Name: ");
  fgets(firstname, 100, stdin);
  printf("Enter Second Name: ");
  fgets(secondname,100,stdin);
  firstname[strlen(firstname)-1]= '\0';
  printf("fullname is %s %s", firstname, secondname);

  return 0;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.