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

252

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

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

message = strcat("TEXT ", var);

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

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

  • 6
    Я хотел бы предложить вам использовать strlcat вместо strcat! gratisoft.us/todd/papers/strlcpy.html
  • 3
    Я хотел бы повторить это предложение. Strcat приводит к уязвимости для буфера эксплойтов переполнения. Кто-то может дать вашей программе данные, которые заставят ее выполнить произвольный код.
Теги:
string
concatenation

17 ответов

288
Лучший ответ

В 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);
  • 57
    Пожалуйста, выделите жирным шрифтом слово «будь очень осторожен ...». Это не может быть подчеркнуто достаточно. Неправильное использование strcat, strcpy и sprintf является сердцем нестабильного / небезопасного программного обеспечения.
  • 1
    «статическая строка» и «выделить свой собственный буфер» могут вводить в заблуждение, поскольку первый может интерпретироваться как «статический буфер строк», а второй - как динамическое распределение. +1, если вы измените на «Вы не можете использовать строковый литерал в качестве буфера, вы всегда должны использовать свой собственный буфер» или подобное.
Показать ещё 12 комментариев
221

Избегайте использования strcat в коде C. Самый чистый и, самое главное, самый безопасный способ - использовать snprintf:

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

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

  • 0
    ... но без лишних и запутанных скобок для оператора sizeof. Они нужны только тогда, когда вы хотите размер реального типа, а не объекта. Извините.
  • 0
    Это, пожалуй, лучший способ узнать размер буфера, если только вы не получили указатель. Если у вас есть указатель, вы будете передавать длину буфера, на который он указывает.
Показать ещё 15 комментариев
20

Люди, используйте str n cpy(), str n cat() или s n printf().
Превышение вашего буферного пространства приведет к сбою всего, что следует в памяти!
(И не забудьте оставить пробел для символа нулевого символа '\ 0'!)

  • 3
    Не только вы должны помнить, чтобы оставить место для символа NULL, вы также должны помнить, чтобы добавить символ NULL. strncpy и strncat не делают этого для вас.
  • 0
    Э-э? strncpy () и strncat () обязательно добавляют завершающий символ. На самом деле, они добавляют слишком много. По крайней мере, до тех пор, пока в буфере остается место, что является огромной ловушкой для этих вызовов. Не рекомендуется.
Показать ещё 7 комментариев
17

Также 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);
}
  • 0
    Это закончится бесконечным циклом, когда num_words>INT_MAX , возможно, вам следует использовать size_t для i
11

Строки также могут быть объединены во время компиляции.

#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 */ 
                ;
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.

  • 0
    <string.h> - это стиль C ++. Вы хотите "string.h" . Вы также рассчитываете strlen(s1) дважды, что не нужно. s3 должно быть длиной в totalLenght+1 .
  • 1
    Какого черта этот беспорядок?
Показать ещё 9 комментариев
4

Первый аргумент 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 .
  • 0
    К сожалению, asprintf является только расширением GNU.
3

Поведение undefined пытается изменить строковые литералы, что-то вроде:

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 нулевым и т. Д.
Показать ещё 3 комментария
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 в соответствии с вашими потребностями.

  • 0
    Я предвижу переполнение буфера, я вижу, вы выделили strlen(str1) + strlen(str2) , но вы пишете strlen(str1) + strlen(str2) + 1 символов. Так вы действительно можете написать свою собственную функцию?
  • 0
    Вот Это Да! Ты никогда не освобождаешь память, противный, противный! return buffer; free(buffer);
Показать ещё 14 комментариев
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

Предполагая, что у вас есть char [fixed_size], а не char *, вы можете использовать один, творческий макрос, чтобы сделать все это сразу с упорядочением <<cout<<like ( "скорее,% s с разнесенными% s\n", "than", "формат формата printf" ). Если вы работаете со встроенными системами, этот метод также позволит вам отключить malloc и большое семейство функций *printf, таких как snprintf() (Это не позволяет жалости о том, что printl тоже).

#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

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

В частности:

... чик...

назначения

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

Здесь также есть пример.

1

Если у вас есть опыт работы на C, вы заметите, что строки - это только char массивы, где последний символ является нулевым символом.

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

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

Теперь откройте свой код:

message = strcat("TEXT " + var);

Здесь вы добавляете что-то к указателю на текст "ТЕКСТ" (тип "ТЕКСТ" - это const char *. Указатель.).

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

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

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

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

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

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

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

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

  • 0
    Тип "TEXT" это char[5] , а не const char* . В большинстве случаев он распадается на char* . Для обратной совместимости, строковые литералы не const , но попытка изменить их результаты к неопределенному поведению. (В C ++, строковые литералы const .)
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");
-1

Попробуйте что-то похожее на это:

#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;
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню