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.