Як з'єднати con / retral рядки в C?


347

Я працюю в 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 викликає вразливість до використання переповнення буфера. Хтось може надати вашій програмі дані, через які вона виконує довільний код.
Брайан

Відповіді:


388

У 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може бути просто проігноровано, воно просто повертає той самий покажчик, що був переданий як перший аргумент. Він є для зручності і дозволяє ланцюжком дзвінків в один рядок коду:

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.
Брайан Р. Бонді

13
@psihodelia: Також не забувайте, що ложки набагато краще, ніж виделки! тому обов'язково завжди використовуйте ложку!
Брайан Р. Бонді

20
До другого @dolmen, Джоел Спольський написав досить складну статтю з цього питання. Повинно бути обов’язковим читання. ;-)
peter.slizik

247

Уникайте використання strcatкоду С. Найчистіший і, головне, найбезпечніший спосіб - це використовувати snprintf:

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

Деякі коментатори порушили питання про те, що кількість аргументів може не відповідати рядку формату і код все одно буде компілюватися, але більшість компіляторів вже видають попередження, якщо це так.


3
У шашках він говорив про дужки навколо "buf" аргументу sizeof. вони не потрібні, якщо аргумент є виразом. Але я не розумію, чому тебе заперечують. я вважаю, що ваша відповідь найкраща з усіх, хоча це c99. (можливо, через це вони не згодні! ламери!) +1
Йоханнес Шауб - ліб

4
розмір () працює тут лише для char buf [...]. НЕ для char * buf = malloc (...). Існує не так багато відмінностей між масивами та вказівниками, але це одна з них!
Mr.Ree

2
Також він намагається виконувати конкатенацію. Об’єднання за допомогою snprintf()- це БІЛЬКИЙ ні.
Леонардо Еррера

5
@MrRee: Різниці між вказівниками та масивами величезні та повні! Це в тому, як ви їх використовуєте , не завжди відрізняється. Також покажчики та динамічне розподіл - це дійсно ортогональні поняття.
Гонки легкості на орбіті

34
Один з моїх улюблених мозолів такі люди , як @unwind, які наполягають на безглуздий відмінності між sizeof(x)і sizeof x. Зазначені в дужках позначення завжди працюють, а невизначені позначення працюють лише іноді, тому завжди використовуйте позначення в дужках; це просте правило пам’ятати і є безпечним. Це перетворюється на релігійний аргумент - я брав участь у дискусіях з тими, хто заперечував раніше, - але простота "завжди використовувати дужки" переважає будь-яку заслугу їх використання (звичайно, IMNSHO). Це представлено для балансу.
Джонатан Леффлер

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'.
квінмари

2
snprintf хороший, strncpy / strncat - найгірша можлива рекомендація, strlcpy / strlcat - набагато краще.
Роберт Гембл

9
Не використовуйте strncpy(). Це не "безпечніша" версія strcpy(). Цільовий масив символів може бути маркованим додатковими '\0'символами, або ще гірше, він може залишатися невирішеним (тобто не рядком). (Він був розроблений для використання із структурою даних, яка вже рідко використовується, масив символів забитий до кінця з нулем або більше '\0'символів.)
Кіт Томпсон

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 повинен бути нульовим завершеним рядком з достатньою кількістю місця, виділеного для результуючої рядки:

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

4

Як зазначали люди, обробка струн значно покращилася. Тому ви, можливо, захочете дізнатися, як використовувати бібліотеку рядків 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

Я деякий час не використовував рядки в стилі С. Сміливо публікуйте фіксовану версію.
Нілс

4
@MooingDuck: Це неправильно. #include <string.h>правильно. Використовуйте кутові дужки для стандартних та системних заголовків (включаючи <string.h>), лапки для заголовків, що входять до вашої програми. ( #include "string.h"трапиться, якщо у вас немає власного файлу заголовка під цим іменем, але <string.h>все одно використовувати .)
Кіт Томпсон,

Зауважте, що це залежить від особливостей 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 було недійсним тощо.
paxdiablo

7
@paxdiablo: Але ви навіть не згадали про це у відповіді на запитання, де, здавалося б, потрібно згадати. Це робить вашу відповідь небезпечною . Ви також не пояснили, чому цей код кращий за оригінальний код ОП, за винятком міфу про те, що він "досягає того ж результату, що і ваш оригінал" (тоді в чому справа? Оригінал був зламаний !), Тож відповідь також неповна .
Гонки легкості по орбіті

Сподіваємось, вирішили ваші проблеми, @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.
Пер Йоханссон

На жаль, asprintfце лише розширення GNU.
Кальмарій

3

Якщо у вас є досвід роботи на C, ви помітите, що рядки - це лише масиви char, де останній символ є нульовим символом.

Тепер це зовсім незручно, оскільки вам потрібно знайти останнього символу, щоб щось додати. 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.)
Кіт Томпсон,

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);
Лівіу

BTW, sizeof(char) == 1(Крім того, є інші більш тонкі помилки ...) Чи можете ви тепер зрозуміти, чому вам не потрібно писати власну функцію?
Лівіу

@Liviu Я звільняю пам'ять на лінії free(buffer);.
Дональд Дак

1
free(buffer);після того, return buffer;як ніколи не виконується, бачте його в налагоджувальнику;) Я бачу зараз: так, ви повинні звільнити пам'ять у mainфункції
Liviu

1

Якщо припустити, що ви маєте char [fix_size], а не char *, ви можете використовувати один творчий макрос, щоб зробити це все одночасно із <<cout<<likeзамовленням ("швидше% s роз'єднаний% s \ n", "ніж", printf формат стилю "). Якщо ви працюєте з вбудованими системами, цей метод також дозволить вам залишити недолік і велику групу *printfфункцій, таких як snprintf()(Це не дозволяє дієтибc скаржитися на * 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;
}

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.