C에서 const / literal 문자열을 어떻게 연결합니까?


348

나는 C에서 일하고 있으며 몇 가지를 연결해야합니다.

지금 나는 이것을 가지고있다 :

message = strcat("TEXT ", var);

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

이제 C에 경험이 있다면 실행하려고 할 때 세분화 오류가 발생한다는 것을 알고 있습니다. 어떻게 해결할 수 있습니까?


6
strcat 대신 strlcat을 사용하는 것이 좋습니다. gratisoft.us/todd/papers/strlcpy.html
activout.se

3
그 제안을 철회하고 싶습니다. Strcat은 오버플로 악용을 버퍼링하는 취약점을 유발합니다. 누군가가 임의의 코드를 실행하도록하는 프로그램 데이터를 제공 할 수 있습니다.
브라이언

답변:


389

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 buffer[1024];

첫 번째 매개 변수에 복사하려는 내용을 저장할 공간이 충분한 지 확인하십시오 . 사용 가능한 경우 다음 strcpy_s과 같은 기능을 사용하는 것이 안전 strcat_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의 오용은 불안정하고 안전하지 않은 소프트웨어의 핵심입니다.
plinth

12
경고 : 작성된 바와 같이,이 코드는 버퍼 오버플로 악용을 위해 코드에 큰 공백을 남깁니다.
브라이언

11
위의 예에서는 가능한 버퍼 오버플로 악용이 없습니다. 그리고 예, 나는 일반적으로 foo와 bar의 결정되지 않은 문자열 길이에 대해 위의 예제를 사용하지 않을 것에 동의합니다.
브라이언 알 본디

13
@ psihodelia : 또한 숟가락이 포크보다 훨씬 낫다는 것을 잊지 마십시오! 항상 숟가락을 사용하십시오!
Brian R. Bondy

20
두 번째 @dolmen에게 Joel Spolsky는 이 문제에 대해 매우 정교한 기사 를 작성 했습니다. 반드시 읽어야합니다. ;-)
peter.slizik

247

strcatC 코드에서는 사용하지 마십시오 . 가장 깨끗하고 가장 중요한 방법은 다음을 사용하는 것입니다 snprintf.

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

일부 의견 제시 자들은 인수의 개수가 형식 문자열과 일치하지 않을 수 있으며 코드가 여전히 컴파일 될 것이라는 문제를 제기했지만 대부분의 컴파일러는 이러한 경우 경고를 표시합니다.


3
체커, 그는 sizeof 논쟁의 "buf"에 대한 괄호에 대해 이야기하고 있었다. 인수가 표현식 인 경우 필요하지 않습니다. 하지만 난 왜 당신이 다운 보트인지 이해가 안 돼요 나는 그것이 c99이지만 귀하의 답변이 가장 좋다고 생각합니다. +1
Johannes Schaub-litb

4
sizeof ()는 여기서 char buf [...]에 대해서만 작동합니다. char * buf = malloc (...)에는 해당되지 않습니다. 배열과 포인터 사이에는 많은 차이점이 없지만 이것이 그중 하나입니다!
Mr.Ree

2
또한 그는 연결을 시도하고 있습니다. 사용하여 연결하는 snprintf()것은 큰 아니오 아니오입니다.
Leonardo Herrera

5
@MrRee : 포인터와 배열의 차이는 엄청납니다! 항상 다른 것은 아니지만 사용 방법에 있습니다 . 또한 포인터와 동적 할당은 실제로 직교하는 개념입니다.
궤도에서 가벼움 경주

34
내 애완 동물 짜증나게 중 하나는 사이의 무의미한 구분 주장 @unwind 같은 사람이다 sizeof(x)하고 sizeof x. 괄호 안에있는 표기법은 항상 작동하고 괄호 안에없는 표기법은 때때로 작동하기 때문에 항상 괄호 안에있는 표기법을 사용하십시오. 기억해야 할 간단한 규칙이며 안전합니다. 이것은 이전에 반대했던 사람들과 토론에 참여했던 종교적인 논쟁에 빠지지만 '항상 괄호 사용'의 단순성은 그것들을 사용하지 않는 것보다 장점이 있습니다 (물론 IMNSHO). 이것은 균형을 위해 제시됩니다.
Jonathan Leffler 2014 년

24

사람들은 str n cpy (), str n cat () 또는 s n printf ()를 사용하십시오.
버퍼 공간을 초과하면 메모리에서 뒤 따르는 모든 것이 버려집니다!
(그리고 후행 null '\ 0'문자를위한 공간을 확보해야합니다!)


3
NULL 문자를위한 공간을 허용해야 할뿐만 아니라 NULL 문자를 추가 해야합니다. strncpy와 strncat은 당신을 위해 그것을하지 않습니다.
Graeme Perrow

어? strncpy () 및 strncat ()은 반드시 종료 문자를 추가하십시오. 실제로, 그들은 너무 많이 추가합니다. 적어도 버퍼에 공간이 남아있는 한, 이러한 호출에 큰 함정입니다. 권장하지 않습니다.

3
@ unwind, Graeme의 요점은 버퍼가 너무 작 으면 strncpy 또는 strncat이 종료 '\ 0'을 추가 하지 않는다는 것 입니다.
quinmars

2
snprintf가 좋고 strncpy / strncat이 최악의 권장 사항이며 strlcpy / strlcat이 훨씬 좋습니다.
Robert Gamble

9
를 사용하지 마십시오 strncpy(). 그건 하지 의 "안전"버전 strcpy(). 대상 문자 배열이 불필요하게 추가 '\0'문자로 채워지 거나 더 나빠질 수 있습니다 (문자열이 아님). (이제 더 이상 거의 사용되지 않는 데이터 구조, 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_t위한i
12,431,234,123,412,341,234,123

5

출력 버퍼를 초기화하는 것을 잊지 마십시오. strcat에 대한 첫 번째 인수는 결과 문자열에 충분한 공간이 할당 된 널 종료 문자열이어야합니다.

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

4
@MooingDuck : "string.h"말도 안됩니다.
sbi

한동안 C 스타일 문자열을 사용하지 않았습니다. 고정 버전을 게시하십시오.
Nils

4
@MooingDuck : 맞지 않습니다. #include <string.h>C와 표준 헤더에는 꺾쇠 괄호를 사용하고 프로그램에 포함 된 헤더 <string.h>에는 따옴표를 사용하십시오. ( #include "string.h"해당 이름의 헤더 파일이 없지만 <string.h>어쨌든 사용하면 작동 합니다.)
Keith Thompson

이는 C99 관련 기능, 즉 선언 및 명령문 혼합, 가변 길이 배열 (VLA)에 따라 다릅니다. VLA는 할당 실패를 감지하거나 처리하는 메커니즘을 제공하지 않습니다. VLA를 할당 할 공간이 충분하지 않으면 프로그램의 동작이 정의되지 않습니다.
Keith Thompson

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자를 초과하면 어떻게됩니까? > :)
Geo

1
그런 다음 버퍼 오버플로가 발생하여 미리 확인할 코드를 추가 할 수 있습니다 (예 : strlen). 그러나 코드 스 니펫의 목적은 너무 많은 추가 코드로 오염시키지 않고 어떻게 작동하는지 보여줍니다. 그렇지 않으면 var / foo / bar가 null인지 등의 길이를 확인합니다.
paxdiablo

7
@ paxdiablo : 그러나 언급해야 할 질문에 대한 답변으로 언급하지 않았습니다. 그것은 당신의 대답을 위험 하게 만듭니다 . 또한이 코드가 OP의 원본 코드보다 나은 이유를 설명하지 않았습니다. "원본과 동일한 결과를 얻습니다"라는 신화를 제외하고 (원점이 무엇입니까? 원본이 손상되었습니다 !) 또한 불완전하다 .
궤도에서 가벼움 경주

@PreferenceBean, 당신의 관심사를 이상적으로 적시하지는 않았지만 희망적으로 해결 해 봤지만 :-) 여전히 대답에 문제가 있는지 알려 주시면 더 개선하겠습니다.
paxdiablo

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합니다.
당 Johansson

불행히도 asprintfGNU 확장입니다.
Calmarius

3

C에 경험이 있다면 문자열은 마지막 문자가 널 문자 인 문자 배열 일뿐입니다.

이제 무언가를 추가하기 위해 마지막 문자를 찾아야하기 때문에 매우 불편합니다. strcat당신을 위해 그렇게 할 것입니다.

따라서 strcat은 첫 번째 인수를 통해 널 문자를 검색합니다. 그런 다음 이것을 두 번째 인수의 내용으로 대체합니다 (널로 끝나기 전까지).

이제 코드를 살펴 보겠습니다 :

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문자 를 씁니다 . 그래서 당신은 정말로 당신 자신의 함수를 작성할 수 있습니까?
Liviu

와! 당신은 결코 추억을 비우지 않습니다. return buffer; free(buffer);
Liviu

BTW, sizeof(char) == 1(또 다른 미묘한 오류가 있습니다 ...) 왜 자신의 함수를 작성할 필요가 없는지 알 수 있습니까?
Liviu

@Liviu 나는 라인에서 메모리를 비운다 free(buffer);.
Donald Duck

1
free(buffer);후에 return buffer;실행되지 않습니다, 디버거에서 그것을 참조하십시오;) 지금 본다 : 네, 당신은 main함수 에서 메모리를 해제해야 합니다
Liviu

1

char *가 아닌 char [fixed_size]가 있다고 가정하면 하나의 창의적인 매크로를 사용하여 <<cout<<like순서대로 한 번에 모두 수행 할 수 있습니다 ( "% 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에서는 정수를 문자열 유형으로 변환하기 위해 itoa ()와 같은 비표준 함수를 포함하여 char *를 출력하는 모든 함수를 사용할 수 있습니다.
  • 참고 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;
}

1

정적으로 할당 된 주소로 문자열을 복사하려고합니다. 당신은 버퍼에 고양이가 필요합니다.

구체적으로 특별히:

...한조각...

목적지

Pointer to the destination array, which should contain a C string, and be large enough to contain the concatenated resulting string.

...한조각...

http://www.cplusplus.com/reference/clibrary/cstring/strcat.html

여기에도 예가 있습니다.


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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.