Как объединить константные / литеральные строки в C?

349

Я работаю в C, и мне нужно объединить несколько вещей.

Прямо сейчас у меня есть это:

message = strcat("TEXT ", var);

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

Теперь, если у вас есть опыт работы с Си, я уверен, что вы понимаете, что это дает вам ошибку сегментации, когда вы пытаетесь запустить ее. Так как мне обойти это?

The.Anti.9
источник
6
Я хотел бы предложить вам использовать strlcat вместо strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se
3
Я хотел бы повторить это предложение. Strcat приводит к уязвимости, связанной с переполнением буфера. Кто-то может дать вашей программе данные, которые заставят ее выполнить произвольный код.
Брайан

Ответы:

390

В 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где вам явно нужно указать размер буфера назначения.

Примечание . Строковый литерал нельзя использовать в качестве буфера, поскольку он является константой. Таким образом, вы всегда должны выделять массив символов для буфера.

Возвращаемое значение 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в C-коде. Самый чистый и, самое главное, самый безопасный способ - это использовать snprintf:

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

Некоторые комментаторы поднимали вопрос о том, что число аргументов может не соответствовать строке формата, и код все равно будет компилироваться, но большинство компиляторов уже выдают предупреждение, если это так.

Алекс Б
источник
3
Шашки, он говорил о скобках вокруг «buf» аргумента sizeof. они не требуются, если аргумент является выражением. Но я не понимаю, почему вы проголосовали. Я думаю, что ваш ответ лучше всего, хотя это C99. (может быть из-за этого они не согласны! Ламеры!) +1
Йоханнес Шауб -
4
sizeof () работает только для char buf [...]. НЕ для char * buf = malloc (...). Между массивами и указателями не так много различий, но это один из них!
Мистер Ри
2
Также он пытается выполнить конкатенацию. Конкатенация с использованием snprintf()БОЛЬШОЙ нет нет.
Леонардо Эррера
5
@MrRee: Различия между указателями и массивами огромны и полны! То, как вы их используете , не всегда отличается. Кроме того, указатели и динамическое распределение являются действительно ортогональными понятиями.
Гонки легкости на орбите
34
Одна из моих любимых мозолей - люди вроде @unwind, которые настаивают на бессмысленном различии между sizeof(x)и sizeof x. Запись в скобках всегда работает, а запись без скобок работает только иногда, поэтому всегда используйте запись в скобках; это простое правило для запоминания и безопасно. Это становится религиозным аргументом - я принимал участие в обсуждениях с теми, кто возражал раньше - но простота «всегда использовать круглые скобки» перевешивает любые преимущества их неиспользования (IMNSHO, конечно). Это представлено для баланса.
Джонатан Леффлер
24

Люди, использование ул п CPY (), ул н кошки (), или S п Е ().
Превышение вашего буферного пространства приведет к удалению всего, что следует в памяти!
(И не забудьте оставить место для завершающего нулевого символа '\ 0'!)

Mr.Ree
источник
3
Вы должны помнить не только о том, что нужно оставить пробел для символа NULL, но и об добавлении символа NULL. strncpy и strncat не делают этого для вас.
Грэм Перроу
Э - э? strncpy () и strncat () обязательно добавляют завершающий символ. На самом деле, они добавляют слишком много. По крайней мере, до тех пор, пока в буфере остается место, что является огромной ловушкой для этих вызовов. Не рекомендуется.
расслабиться
3
@unwind, я думаю, что смысл Graeme в том, что если буфер слишком мал, strncpy или strncat не добавят завершающий '\ 0'.
Quinmars
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 */ 
                ;
dbagnara
источник
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. Однако вот решение в чистом 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.

Nils
источник
<string.h>это стиль C ++. Вы хотите "string.h". Вы также рассчитываете strlen(s1)дважды, что не нужно. s3должно быть totalLenght+1долго.
Mooing Duck
4
@MooingDuck: "string.h"ерунда.
SBI
Я не использовал строки в стиле C некоторое время. Не стесняйтесь размещать исправленную версию.
Нильс
4
@ MooingDuck: это неправильно. #include <string.h>правильно C. Используйте угловые скобки для стандартных и системных заголовков (включая <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.
paxdiablo
источник
4
Что произойдет, если var / foo / bar содержит более 1000 символов? > :)
Geo
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 () объединит второй аргумент с первым аргументом и сохранит результат в первом аргументе, возвращенный символ * - это просто первый аргумент, и только для вашего удобства.

Вы не получите вновь распределенную строку с объединенным первым и вторым аргументом, что, как я полагаю, вы ожидаете, основываясь на вашем коде.

Pieter
источник
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, вы заметите, что строки - это только массивы символов, где последний символ является нулевым символом.

Теперь это довольно неудобно, так как вам нужно найти последний символ, чтобы добавить что-то. strcatсделаю это для вас.

Таким образом, strcat ищет в первом аргументе нулевой символ. Затем он заменит это на содержимое второго аргумента (до тех пор, пока оно не закончится нулевым значением).

Теперь давайте пройдемся по вашему коду:

message = strcat("TEXT " + var);

Здесь вы добавляете что-то к указателю на текст «TEXT» (тип «TEXT» - const char *. A pointer.).

Это обычно не работает. Также изменение массива «TEXT» не будет работать, так как он обычно помещается в постоянный сегмент.

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

Это может работать лучше, за исключением того, что вы снова пытаетесь изменить статические тексты. strcat не выделяет новую память для результата.

Я бы предложил сделать что-то вроде этого:

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

Прочитайте документацию sprintf чтобы проверить его параметры.

А теперь важный момент:

Убедитесь, что в буфере достаточно места для текста и нулевого символа. Есть пара функций, которые могут вам помочь, например, strncat и специальные версии printf, которые выделяют вам буфер. Несоблюдение размера буфера приведет к повреждению памяти и удаленному использованию ошибок.

Ralf
источник
Тип "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);
Ливиу
Кстати, sizeof(char) == 1(кроме того, есть и другие более тонкие ошибки ...) Теперь вы понимаете, почему вам не нужно писать свою собственную функцию?
Ливиу
@Liviu Я освобождаю память на линии free(buffer);.
Дональд Дак
1
free(buffer);после того, return buffer;как никогда не выполняется, увидеть это в отладчике;) Теперь я вижу: да, вы должны освободить память в mainфункции
Ливиу
1

Предполагая, что у вас есть char [fixed_size], а не char *, вы можете использовать один творческий макрос, чтобы сделать все это одновременно с <<cout<<likeупорядочением ("скорее% s несвязанный% 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 (), для преобразования целых чисел в строковые типы.
  • Примечание 3, если вы уже используете printf где-либо в своей программе, нет причин не использовать snprintf (), поскольку скомпилированный код будет больше (но встроен и значительно быстрее)
technosaurus
источник
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");
Naheel
источник
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;
}
jksante
источник