Cでconst / literal文字列を連結するにはどうすればよいですか?


346

私は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は、バッファオーバーフローエクスプロイトに脆弱性を引き起こします。誰かがあなたのプログラムに任意のコードを実行させるデータを与えることができます。
ブライアン、

回答:


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_sそしてstrcat_s、あなたは明示的に宛先バッファのサイズを指定しておく必要があります。

:文字列リテラルは定数なので、バッファとして使用できません。したがって、バッファには常にchar配列を割り当てる必要があります。

の戻り値はstrcat単に無視することができ、最初の引数として渡されたのと同じポインタを返すだけです。便宜上そこにあり、呼び出しを1行のコードにチェーンすることができます。

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の文字列の長さが未定の場合は上記の例を使用しないことに同意します。
ブライアンR.ボンディ

13
@psihodelia:スプーンはフォークよりもはるかに優れていることも忘れないでください!ですから、必ずスプーンを使ってください!
ブライアンR.ボンディ

20
2番目の@dolmenに、Joel Spolskyがこの問題についてかなり精巧な記事を書いてます。必読です。;-)
peter.slizik 2012

247

strcatCコードでの使用は避けてください。最もクリーンで、最も重要なのは、最も安全な方法は以下を使用することsnprintfです。

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

一部のコメンターは、引数の数がフォーマット文字列と一致しない可能性があり、コードは引き続きコンパイルされるという問題を提起しましたが、これが当てはまる場合、ほとんどのコンパイラーはすでに警告を発行しています。


3
チェッカーズ、彼はsizeof引数の「buf」の周りの括弧について話していました。引数が式の場合は必要ありません。しかし、あなたが反対票を投じられた理由がわかりません。それはc99ですが、あなたの答えはすべての中で最高です。(たぶん彼らが同意しないためです!ラマー!)+1
ヨハネスシャウブ-litb 2008年

4
sizeof()は、char buf [...]に対してのみ機能します。char * buf = malloc(...)ではありません。配列とポインタの間には多くの違いはありませんが、これはそれらの1つです!
Mr.Ree、2008年

2
また、彼は連結を実行しようとしています。を使用して連結することsnprintf()は非常に重要です。
レオナルドエレーラ

5
@MrRee:ポインターと配列の違いは広大で完全です!常に異なるわけではないのは、それらの使い方です。また、ポインタと動的割り当ては、互いに直交する概念です。
オービットのライトネスレース

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 2008年

2
snprintfは良いです、strncpy / strncatは可能な限り最悪の推奨です、strlcpy / strlcatははるかに優れています。
ロバートギャンブル、

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
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)、2回計算する必要はありません。 長いs3はずtotalLenght+1です。
Mooing Duck

4
@MooingDuck:"string.h"ナンセンスです。
sbi 2011

しばらくCスタイルの文字列を使用していません。修正版を投稿してください。
Nils

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文字を超えるとどうなりますか?> :)
Geo

1
次に、バッファオーバーフローが発生します。これを事前にチェックするコードを追加できます(たとえば、strlenを使用)。ただし、コードスニペットの目的は、余分なコードを多用してコードを汚染することなく、その動作を示すことです。そうでなければ私はなど、VAR / fooの/バーがnullであったかどうか、長さをチェックすることだろう
paxdiablo

7
@paxdiablo:ただし、言及する必要があると思われる質問への回答では、言及することすらしませんでした。それはあなたの答えを危険にします。このコードは、それは「あなたのオリジナルと同じ結果を達成」という神話を除いて、より良いOPの元のコードよりも、なぜあなたも(?元がされたポイントをどのようになるか、次に説明しなかった壊れた答えので、!)また、不完全です。
オービットのライトネスレース

@PreferenceBeanで問題を解決できれば幸いですが、理想よりはタイムリーではありませんが:-)回答にまだ問題があるかどうかをお知らせください。さらに改善します。
paxdiablo 2016

3

strcat()の最初の引数は、連結された文字列のために十分なスペースを保持できる必要があります。したがって、結果を受け取るのに十分なスペースのあるバッファを割り当てます。

char bigEnough[64] = "";

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

/* and so on */

strcat()は、2番目の引数を最初の引数と連結し、結果を最初の引数に格納します。返されるchar *は、単にこの最初の引数であり、ユーザーの便宜のためにのみ使用されます。

最初に割り当てられた引数と2番目の引数が連結された、新しく割り当てられた文字列は取得されません。これは、コードに基づいて予想したとおりです。


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ます。
ヨハンソン氏による

残念ながら、これasprintfはGNU拡張機能にすぎません。
Calmarius 2013年

3

Cの経験があれば、文字列は最後の文字がnull文字であるchar配列にすぎないことに気付くでしょう。

何かを追加するために最後の文字を見つける必要があるので、これは非常に不便です。strcatあなたのためにそれを行います。

したがって、strcatは最初の引数を検索してヌル文字を探します。次に、これを2番目の引数のコンテンツで置き換えます(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。割り当てられているようですが、文字を書き込んでいます。それで、あなたは本当にあなた自身の関数を書くことができますか?
Liviu

うわー!あなたは決して記憶を解放しない、厄介な、厄介な!return buffer; free(buffer);
Liviu

ところで、sizeof(char) == 1(他に、他にも微妙なエラーがあります...)独自の関数を記述する必要がない理由がわかりますか?
Liviu

@Liviu行でメモリを解放しますfree(buffer);
ドナルドダック

1
free(buffer);return buffer;実行されなかった後は、デバッガーで確認してください;)わかりました:はい、main関数のメモリを解放する必要があります
Liviu

1

char *ではなくchar [fixed_size]があると仮定すると、単一のクリエイティブマクロを使用して、<<cout<<like順序付けですべてを一度に実行できます(「むしろ%sはバラバラの%s \ n」、「より」、「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.