Я работаю на C, и мне нужно объединить несколько вещей.
Прямо сейчас у меня есть это:
message = strcat("TEXT ", var);
message2 = strcat(strcat("TEXT ", foo), strcat(" TEXT ", bar));
Теперь, если у вас есть опыт работы с C, я уверен, вы понимаете, что это вызывает ошибку сегментации, когда вы пытаетесь ее запустить. Итак, как мне обойти это?
Я хотел бы повторить это предложение. Strcat вызывает уязвимость для эксплойтов переполнения буфера. Кто-то может передать вашей программе данные, которые заставят ее выполнить произвольный код.





В C «строки» - это просто массивы char. Следовательно, вы не можете напрямую связать их с другими «строками».
Вы можете использовать функцию strcat, которая добавляет строку, на которую указывает src, в конец строки, на которую указывает dest:
char *strcat(char *dest, const char *src);
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);
Не могли бы вы выделить жирным шрифтом "Будьте очень осторожны ..."? Это невозможно переоценить. Неправильное использование strcat, strcpy и sprintf составляет основу нестабильного / небезопасного программного обеспечения.
«статическая строка» и «выделить свой собственный буфер» могут вводить в заблуждение, поскольку первое можно интерпретировать как «статический строковый буфер», а второе - как указание на динамическое выделение. +1, если вы измените значение на «Вы никогда не можете использовать строковый литерал в качестве буфера, вы всегда должны использовать свой собственный буфер» или подобное.
Предупреждение: Как написано, этот код оставит в вашем коде огромную зияющую дыру для эксплойтов переполнения буфера.
В приведенном выше примере невозможно использовать переполнение буфера. И да, я в целом согласен, я бы не стал использовать приведенный выше пример для неопределенных длин строк foo и bar.
@psihodelia: Также не забывайте, что ложки намного лучше вилок! так что обязательно всегда используйте ложку!
Не используйте strcat несколько раз! strcat должен проверить все предыдущие байты (поиск '\0'), которые вы уже объединили. Это бесполезная обработка.
Второй @dolmen, Джоэл Спольски написал по этому поводу довольно сложная статья. Должно быть обязательное чтение. ;-)
На самом деле строковые литералы можно объединить, просто поместив их рядом. например const char *str = "these" "strings" "are" "concatenated";.
Не следует ли инициализировать начальный буфер символов нулями: char str[80] = {0};? Таким образом вы получите красивую строку с завершающим нулем.
Попытка изменить строковые литералы является неопределенным поведением, что примерно так:
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.
Что произойдет, если в var / foo / bar больше 1000 символов? > :)
Тогда вы получите переполнение буфера, которое вы можете добавить код для предварительной проверки (скажем, с помощью strlen). Но цель фрагмента кода - показать, как что-то работает, не загрязняя его слишком большим количеством дополнительного кода. В противном случае я бы проверял длину, было ли значение var / foo / bar null и т. д.
@paxdiablo: Но вы даже не упомянули об этом, отвечая на вопрос, где, по-видимому, это нужно упомянуть. Это делает ваш ответ опасный. Вы также не объяснили, почему этот код лучше, чем исходный код OP, за исключением мифа о том, что он «дает тот же результат, что и ваш оригинал» (тогда в чем будет смысл? Оригинал был сломанный!), Поэтому ответ также является неполный.
Надеюсь, @PreferenceBean решила ваши проблемы, хотя и менее своевременно, чем в идеале :-) Дайте мне знать, если у вас все еще есть проблемы с ответом, и я улучшу его.
Первый аргумент strcat () должен иметь достаточно места для объединенной строки. Итак, выделите буфер с достаточным пространством для получения результата.
char bigEnough[64] = "";
strcat(bigEnough, "TEXT");
strcat(bigEnough, foo);
/* and so on */
strcat () объединит второй аргумент с первым аргументом и сохранит результат в первом аргументе, возвращаемый char * - это просто этот первый аргумент и только для вашего удобства.
Вы не получаете вновь выделенную строку с объединенными первым и вторым аргументами, чего, как я полагаю, вы ожидали, основываясь на своем коде.
Вы пытаетесь скопировать строку в адрес, который назначен статически. Вам нужно попасть в буфер.
Конкретно:
... отрезать ...
назначения
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
Здесь тоже есть пример.
Избегайте использования strcat в коде C. Самый чистый и, главное, безопасный способ - использовать snprintf:
char buf[256];
snprintf(buf, sizeof buf, "%s%s%s%s", str1, str2, str3, str4);
Некоторые комментаторы подняли вопрос о том, что количество аргументов может не соответствовать строке формата, и код все равно будет компилироваться, но большинство компиляторов уже выдают предупреждение, если это так.
... но без ненужных и сбивающих с толку скобок для оператора sizeof. Они нужны только тогда, когда вам нужен размер фактического типа, а не объекта. Извините, питомец раздражен.
Возможно, это лучший способ узнать размер буфера, если вам не указан указатель. Если у вас есть указатель, вы передадите длину буфера, на который он указывает.
Шашки, он имел в виду круглые скобки вокруг "buf" аргумента sizeof. они не требуются, если аргумент является выражением. Но я не понимаю, почему вам отказали. Я думаю, что ваш ответ лучший из всех, хотя это c99. (может из-за этого не согласны! ламеры!) +1
О, понимание прочитанного -1. Но в любом случае я поддерживаю свой ответ.
sizeof () работает здесь только для char buf [...]. НЕ для char * buf = malloc (...). Между массивами и указателями не так много различий, но это одно из них!
Да, отсюда и мой комментарий о динамически выделяемых буферах.
Также он пытается выполнить конкатенацию. Конкатенация с использованием snprintf() - это БОЛЬШОЙ нет.
@ Леонардо, ты можешь сказать, почему?
Что ж, есть некоторый риск, что вы добавите еще один, скажем, «str5» и забудете дополнительный «% s» в строке формата - или наоборот. Вылетит только у вас (если вам повезет) во время выполнения. Кроме того, это кажется излишним, учитывая альтернативы безопасно для "strcat".
@ Christian.K ах, но достойный компилятор предупредит вас, если вы это сделаете.
@MrRee: Различия между указателями и массивами огромны и полны! Не всегда разница в том, как вы их использовать. Кроме того, указатели и динамическое размещение - действительно ортогональные концепции.
какой тип str1 должен быть у str2? Могу ли я использовать std::string или разрешен только char*?
@javapowered %s ожидает только char*.
Одна из моих любимых головокружений - это такие люди, как @unwind, которые настаивают на бессмысленном различии между sizeof(x) и sizeof x. Обозначения в скобках всегда работают, а нотации без скобок работают только иногда, поэтому всегда используйте обозначения в скобках; это простое правило, которое следует запомнить, и оно безопасно. Это переходит в религиозный аргумент - я был вовлечен в дискуссии с теми, кто возражал и раньше, - но простота «всегда использовать круглые скобки» перевешивает любые достоинства отказа от их использования (IMNSHO, конечно). Это представлено для баланса.
согласовано. не забываем, что sizeof следует интерпретировать как вызов функции. И что sizeof (x + 1) и sizeof (x) + 1 - очень разные вещи.
1- Лучший ответ, аккуратно !. 2- Для меня sprintf работает без sizeof в качестве второго аргумента. Нашел int sprintf(char *str, const char *format, ...) в tutorialspoint.com/c_standard_library/c_function_sprintf.htm
Если у вас есть опыт работы с C, вы заметите, что строки представляют собой только массивы символов, в которых последний символ является нулевым символом.
Это довольно неудобно, так как вам нужно найти последний символ, чтобы что-то добавить. 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, которые выделяют буфер для вас. Необеспечение размера буфера приведет к повреждению памяти и ошибкам, которые можно использовать удаленно.
Тип "TEXT" - char[5], нетconst char*. Он распадается на char* в большинстве случаев. По причинам обратной совместимости строковые литералы не являются const, но попытка их изменить приводит к неопределенному поведению. (В C++ строковыми литералами являются const.)
Друзья, используйте strпcpy (), strпcat () или sпprintf ().
Превышение вашего буферного пространства приведет к удалению всего остального в памяти!
(И не забудьте оставить место для завершающего нулевого символа '\ 0'!)
Вы не только должны помнить о том, чтобы оставить место для символа NULL, вам нужно не забыть добавлять для символа NULL. strncpy и strncat не делают этого за вас.
Эм-м-м? strncpy () и strncat () обязательно добавляют завершающий символ. На самом деле они добавляют слишком много. По крайней мере, до тех пор, пока в буфере остается место, что является огромной ловушкой для этих вызовов. Не рекомендуется.
@unwind, я думаю, что смысл Грэма в том, что если буфер слишком мал, strncpy или strncat добавят нет завершающий '\ 0'.
если n <strlen () + 1, ноль не добавляется. если n == strlen () + 1, добавляется один нуль. если n> strlen () + 1, добавляется много нулей. Это называется ПРОГРАММИРОВАНИЕ.
Еще лучше, предпочитайте OpenBSD strlcpy, strlcat (легко переносить в ваш собственный проект).
Да, я тоже предпочитаю strl *. Но в этом случае я думаю, что ваше предложение snprintf - лучший способ.
snprintf - это хорошо, strncpy / strncat - наихудшая из возможных рекомендаций, strlcpy / strlcat - намного лучше.
Не используйте strncpy(). Это нет - «более безопасная» версия strcpy(). Целевой массив символов может быть без нужды дополнен дополнительными символами '\0' или, что еще хуже, может остаться незавершенным (то есть не строкой). (Он был разработан для использования со структурой данных, которая больше не используется, символьным массивом, дополненным до конца нулями или более символами '\0'.)
В strncpy и strncat этот n - это максимальное количество символов для копирования, а не размер целевого буфера. Это и незавершенные строки в случае усечения, а также различная обработка усечения на разных платформах также делают его опасным. Пожалуйста, рассмотрите strlcpy и strlcat. Смотрите также мою небольшую статью на эту тему: gergap.wordpress.com/2013/04/05/…
Не забудьте инициализировать выходной буфер. Первым аргументом strcat должна быть строка с завершающим нулем и достаточным дополнительным пространством, выделенным для результирующей строки:
char out[1024] = ""; // must be initialized
strcat( out, null_terminated_string );
// null_terminated_string has less than 1023 chars
Также 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
Как отмечали люди, обработка строк значительно улучшилась. Так что вы можете узнать, как использовать строковую библиотеку 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 должен быть длинным.
@MooingDuck: "string.h" - ерунда.
Я какое-то время не использовал струны в стиле C. Не стесняйтесь размещать исправленную версию.
@MooingDuck: Это неправильно. #include <string.h> является правильным C. Используйте угловые скобки для стандартных и системных заголовков (включая <string.h>), кавычки для заголовков, которые являются частью вашей программы. (#include "string.h" будет работать, если у вас нет собственного файла заголовка с таким именем, но все равно используйте <string.h>.)
Обратите внимание, что это зависит от специфичных для C99 функций: объявлений и операторов смешивания и массивов переменной длины (VLA). Также обратите внимание, что VLA не предоставляют механизма для обнаружения или обработки сбоев распределения; если недостаточно места для выделения VLA, поведение вашей программы не определено.
Выложил исправленную версию, пожалуйста, пересмотрите свое голосование.
Вы можете безопасно использовать strcat, а не strncat, поскольку вы уже гарантировали, что цель достаточно велика.
@KeithThompson согласен, пока перешел только на strcpy.
@KeithThompson: Теперь он заслуживает одобрения, превратив мой -1 в +1. (Этот ответ был «вдохновлен» дискуссией об обработке строк C и C++ в чате C++. Я думаю, Нильс, этим фиаско вы довольно эффективно продемонстрировали точку зрения своих оппонентов. :^>)
Лучший способ сделать это без ограниченного размера буфера - использовать asprintf ()
char* concat(const char* str1, const char* str2)
{
char* result;
asprintf(&result, "%s%s", str1, str2);
return result;
}
Вы должны вернуть char *, а не const char *. Возвращаемое значение нужно будет передать в free.
К сожалению, asprintf - это всего лишь расширение GNU.
Попробуйте что-нибудь подобное:
#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;
}
Предполагая, что у вас есть char [fixed_size], а не char *, вы можете использовать один творческий макрос, чтобы сделать все сразу с упорядочением <<cout<<like («скорее% s несвязный% s \ n», «чем», » формат стиля printf "). Если вы работаете со встроенными системами, этот метод также позволит вам исключить malloc и большое семейство функций *printf, например, snprintf() (это также не позволяет диетической библиотеке жаловаться на * 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);
Строки также могут быть объединены во время компиляции.
#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 */
;
Вы можете написать свою собственную функцию, которая делает то же самое, что и 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);.
@ Дак, вы освобождаете память после выхода из функции ... так что вы не освобождаете ее
char* mycat = strcat_const("Hello ","world"); free(mycat); Есть разница, правда?
@Liviu Это что-нибудь меняет? В этом примере я думаю, что он выполнит printf("%s",strcat_const("Hello ","world"));, а затем освободит память, поэтому, если вы добавите больше в функцию main(), она освободит память раньше. Скажите, если я ошибаюсь.
free(buffer); после того, как return buffer; никогда не запускается, посмотрите это в отладчике;) Теперь я понимаю: да, вам нужно освободить память в функции mainЯ хочу сказать, что вы можете играть с огнем (переписывая известные функции) в своем собственном проекте, но никогда в реальной жизни.
@Liviu Я отредактировал его, чтобы не было утечки памяти, но он поддерживает только ограниченное количество символов.
Что делать, если strlen(str1) >= MAX_STRING_LENGTH?
«Никакой нулевой символ не добавляется неявно в конце места назначения, если исходный код длиннее, чем num». cplusplus.com/reference/cstring/strncpy
buffer[strlen(buffer)] = '\0'; Есть что-то странное в этой строчке? Угадайте, как работает strlen;)! Я вернусь в понедельник, хороших выходных!
Думаю, должно работать, пересмотрю, когда вернусь! Вы можете добавить MAX_STRING_LENGTH в качестве параметра к strcat_const (конечно, с другим именем).
Это катастрофа :(, вы возвращаете адрес переменной стека (переменная buffer определена только внутри strcat_const). Я думаю, вам следует придерживаться решения с динамическим распределением.
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;
}
Это было мое решение
#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");
Я хотел бы предложить вам использовать strlcat вместо strcat! gratisoft.us/todd/papers/strlcpy.html