Como concatenar cadeias const / literais em C?

346

Estou trabalhando em C e preciso concatenar algumas coisas.

Agora eu tenho isso:

message = strcat("TEXT ", var);

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

Agora, se você tem experiência em C, tenho certeza de que você percebe que isso gera uma falha de segmentação ao tentar executá-lo. Então, como faço para resolver isso?

The.Anti.9
fonte
6
Gostaria de sugerir que você use strlcat em vez de strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se
3
Eu gostaria de retroceder essa sugestão. O Strcat causa vulnerabilidade ao buffer de explorações de estouro. Alguém pode fornecer dados ao programa que fazem com que ele execute código arbitrário.
21711 Brian

Respostas:

386

Em C, "strings" são apenas charmatrizes simples . Portanto, você não pode concatená-los diretamente com outras "strings".

Você pode usar a strcatfunção, que anexa a string apontada por srcaté o final da string apontada por dest:

char *strcat(char *dest, const char *src);

Aqui está um exemplo de cplusplus.com :

char str[80];
strcpy(str, "these ");
strcat(str, "strings ");
strcat(str, "are ");
strcat(str, "concatenated.");

Para o primeiro parâmetro, você precisa fornecer o próprio buffer de destino. O buffer de destino deve ser um buffer de matriz de caracteres. Por exemplo:char buffer[1024];

Verifique se o primeiro parâmetro possui espaço suficiente para armazenar o que você está tentando copiar nele. Se disponível, é mais seguro usar funções como: strcpy_se strcat_sonde você precisa explicitamente especificar o tamanho do buffer de destino.

Nota : Um literal de cadeia não pode ser usado como um buffer, pois é uma constante. Portanto, você sempre precisa alocar uma matriz de caracteres para o buffer.

O valor de retorno de strcatpode simplesmente ser ignorado; ele simplesmente retorna o mesmo ponteiro que foi passado como o primeiro argumento. Ele existe por conveniência e permite encadear as chamadas em uma linha de código:

strcat(strcat(str, foo), bar);

Portanto, seu problema pode ser resolvido da seguinte maneira:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);
Brian R. Bondy
fonte
66
Você colocaria "Tenha muito cuidado para que ..." em negrito, por favor? Isso não pode ser estressado o suficiente. O uso indevido de strcat, strcpy e sprintf é o coração do software instável / inseguro.
Plinth
12
Aviso: conforme escrito, esse código deixará um buraco gigante no seu código para explorações de estouro de buffer.
21711 Brian
11
Não há exploração de buffer overflow possível no exemplo acima. E sim, concordo em geral que não usaria o exemplo acima para comprimentos indeterminados de stro e bar.
Brian R. Bondy
13
@psihodelia: Também não esqueça que colheres são muito melhores que garfos! então sempre use uma colher!
Brian R. Bondy
20
Para o segundo @dolmen, Joel Spolsky escreveu um artigo bastante elaborado sobre o assunto. Deve ser uma leitura obrigatória. ;-)
peter.slizik
247

Evite usar strcatno código C. A maneira mais limpa e, mais importante, a mais segura é usar snprintf:

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

Alguns comentaristas levantaram uma questão de que o número de argumentos pode não corresponder à string de formato e o código ainda será compilado, mas a maioria dos compiladores já emitirá um aviso, se for o caso.

Alex B
fonte
3
Damas, ele estava falando sobre os parênteses em torno de "buf" do tamanho do argumento. eles não são necessários se o argumento for uma expressão. Mas eu não entendo por que você está com voto negativo. acho que sua resposta é a melhor de todas, mesmo que seja c99. (talvez por isso eles discordem! lamers!) +1
Johannes Schaub - litb
4
sizeof () só funciona aqui para char buf [...]. NÃO para char * buf = malloc (...). Não há muitas diferenças entre matrizes e ponteiros, mas essa é uma delas!
21868 Mr.Ree
2
Além disso, ele está tentando executar concatenação. Concatenar usando snprintf()é um GRANDE não, não.
Leonardo Herrera
5
@ MrRee: As diferenças entre ponteiros e matrizes são vastas e completas! É na maneira como você os usa que nem sempre difere. Além disso, ponteiros e alocação dinâmica são realmente conceitos ortogonais.
Lightness Races in Orbit
34
Um dos meus ódios de estimação são pessoas como @unwind que insistem na distinção sem sentido entre sizeof(x)e sizeof x. A notação entre parênteses sempre funciona e a notação não-parênteses funciona apenas às vezes; portanto, sempre use a notação entre parênteses; é uma regra simples de lembrar e é segura. Isso entra em um argumento religioso - eu estive envolvido em discussões com aqueles que objetaram antes - mas a simplicidade de 'sempre usar parênteses' supera qualquer mérito de não usá-los (IMNSHO, é claro). Isso é apresentado para o equilíbrio.
precisa
24

Pessoal, use str n cpy (), str n cat () ou s n printf ().
Exceder o espaço no buffer será uma lixeira no que mais se seguir na memória!
(E lembre-se de permitir espaço para o caractere nulo '\ 0' à direita!)

Mr.Ree
fonte
3
Não apenas lembre-se de permitir espaço para o caractere NULL, mas lembre-se de adicionar o caractere NULL. strncpy e strncat não fazem isso por você.
Graeme Perrow
Uh? strncpy () e strncat () certamente adicionam o caractere final. De fato, eles adicionam muitos. Pelo menos enquanto houver espaço no buffer, o que é uma grande armadilha com essas chamadas. Não recomendado.
21338 desenrolar
3
@unwind, acho que o objetivo do Graeme é que, se o buffer for muito pequeno, o strncpy ou o strncat não adicionarão o \ \ 0 'final.
quinmars 21/11/08
2
snprintf é bom, strncpy / strncat é a pior recomendação possível, strlcpy / strlcat é muito melhor.
Robert Gamble
9
Não use strncpy(). É não uma versão "mais seguro" do strcpy(). A matriz de caracteres de destino pode ser preenchida desnecessariamente com '\0'caracteres extras ou, pior ainda, pode ser deixada sem terminação (ou seja, não é uma string). (Ele foi projetado para uso com uma estrutura de dados que raramente mais usado, uma matriz de caracteres acolchoada para o final com zero ou mais '\0'caracteres.)
Keith Thompson
22

Strings também podem ser concatenadas em tempo de compilação.

#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
fonte
15

Também malloc e realloc são úteis se você não souber antecipadamente quantas seqüências de caracteres estão sendo concatenadas.

#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);
}
Reed Hedges
fonte
Isso vai acabar em um loop infinito quando num_words>INT_MAX, talvez você deve usar size_tparai
12431234123412341234123
5

Não esqueça de inicializar o buffer de saída. O primeiro argumento para strcat deve ser uma sequência terminada nula com espaço extra suficiente alocado para a sequência resultante:

char out[1024] = ""; // must be initialized
strcat( out, null_terminated_string ); 
// null_terminated_string has less than 1023 chars
David Rodríguez - dribeas
fonte
4

Como as pessoas apontaram, o manuseio de cordas melhorou muito. Portanto, você pode aprender como usar a biblioteca de strings C ++ em vez de strings no estilo C. No entanto, aqui está uma solução em C puro

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

Não tenho certeza se está correto / seguro, mas no momento não consegui encontrar uma maneira melhor de fazer isso no ANSI C.

Nils
fonte
<string.h>é o estilo C ++. Você quer "string.h". Você também calcula strlen(s1)duas vezes, o que não é necessário. s3deve ser totalLenght+1longo.
Mooing Duck
4
@MooingDuck: "string.h"é um absurdo.
S7
Eu não uso cordas de estilo C há um tempo. Sinta-se livre para publicar uma versão fixa.
Nils
4
@MooingDuck: Isso está incorreto. #include <string.h>está correto C. Use colchetes angulares para cabeçalhos padrão e do sistema (incluindo <string.h>), aspas para cabeçalhos que fazem parte do seu programa. ( #include "string.h"Vai acontecer com o trabalho se você não tiver seu próprio arquivo de cabeçalho com esse nome, mas o uso <string.h>de qualquer maneira.)
Keith Thompson
Observe que isso depende dos recursos específicos do C99: mistura de declarações e instruções e matrizes de comprimento variável (VLAs). Observe também que os VLAs não fornecem mecanismo para detectar ou manipular falhas de alocação; se não houver espaço suficiente para alocar um VLA, o comportamento do seu programa é indefinido.
Keith Thompson
4

Não é um comportamento indefinido tentar modificar literais de strings, que é algo como:

strcat ("Hello, ", name);

tentará fazer. Ele tentará aderir à namestring no final da literal da string "Hello, ", que não está bem definida.

Tente algo assim. Consegue o que você parece estar tentando fazer:

char message[1000];
strcpy (message, "TEXT ");
strcat (message, var);

Isto cria uma zona tampão que é permitido ser modificado e, em seguida, cópias tanto a cadeia literal e outro texto. Apenas tenha cuidado com estouros de buffer. Se você controlar os dados de entrada (ou verificar antes), é bom usar buffers de comprimento fixo como eu.

Caso contrário, você deve usar estratégias de mitigação, como alocar memória suficiente do heap para garantir que você possa lidar com isso. Em outras palavras, algo como:

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
fonte
4
O que acontece se var / foo / bar tiver mais de 1000 caracteres? > :)
Geo
11
Em seguida, você receberá um estouro de buffer, que poderá adicionar código para verificar antecipadamente (digamos, com strlen). Mas o objetivo de um trecho de código é mostrar como algo funciona sem poluí-lo com muito código extra. Caso contrário, eu estaria verificando os comprimentos, se var / foo / bar era nulo, etc. #
214
7
@ paxdiablo: Mas você nem mencionou, em uma resposta a uma pergunta em que parece precisar ser mencionada. Isso torna sua resposta perigosa . Você também não explicou por que esse código é melhor que o código original do OP, exceto pelo mito de que "alcança o mesmo resultado que o original" (então qual seria o sentido? O original estava quebrado !), Então a resposta também está incompleto .
Lightness Races in Orbit
Espero ter resolvido suas preocupações, @PreferenceBean, embora de maneira menos oportuna do que o ideal :-) Deixe-me saber se você ainda tem um problema com a resposta, e eu a melhorarei ainda mais.
22416
3

O primeiro argumento de strcat () precisa ser capaz de manter espaço suficiente para a sequência concatenada. Portanto, aloque um buffer com espaço suficiente para receber o resultado.

char bigEnough[64] = "";

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

/* and so on */

strcat () concatenará o segundo argumento com o primeiro argumento e armazenará o resultado no primeiro argumento, o caractere retornado * é simplesmente esse primeiro argumento e apenas para sua conveniência.

Você não recebe uma string alocada recentemente com o primeiro e o segundo argumento concatenados, o que eu acho que você esperava com base no seu código.

Pieter
fonte
3

A melhor maneira de fazer isso sem ter um tamanho de buffer limitado é usando asprintf ()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}
Nico Cvitak
fonte
2
Você deveria retornar char *, não const char *. O valor de retorno precisará ser passado para free.
Per Johansson
Infelizmente asprintfé apenas uma extensão GNU.
Calmarius
3

Se você possui experiência em C, notará que as strings são apenas matrizes de caracteres onde o último caractere é um caractere nulo.

Agora isso é bastante inconveniente, pois você precisa encontrar o último caractere para acrescentar algo. strcatfará isso por você.

Portanto, o strcat pesquisa no primeiro argumento um caractere nulo. Em seguida, substituirá isso pelo conteúdo do segundo argumento (até que termine em um nulo).

Agora vamos analisar seu código:

message = strcat("TEXT " + var);

Aqui você está adicionando algo ao ponteiro no texto "TEXT" (o tipo de "TEXT" é const char *. Um ponteiro.).

Isso geralmente não vai funcionar. A modificação da matriz "TEXT" também não funcionará, pois geralmente é colocada em um segmento constante.

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

Isso pode funcionar melhor, exceto que você está novamente tentando modificar textos estáticos. O strcat não está alocando nova memória para o resultado.

Eu proporia fazer algo assim:

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

Leia a documentação sprintfpara verificar suas opções.

E agora um ponto importante:

Verifique se o buffer possui espaço suficiente para conter o texto E o caractere nulo. Existem algumas funções que podem ajudá-lo, por exemplo, strncat e versões especiais do printf que alocam o buffer para você. Não garantir que o tamanho do buffer leve a corrupção de memória e erros exploráveis ​​remotamente.

Ralf
fonte
O tipo de "TEXT"é char[5], não const char* . Decai char*na maioria dos contextos. Por motivos de compatibilidade com versões anteriores, os literais de sequência não são const, mas tentar modificá-los resulta em um comportamento indefinido. (Em C ++, literais de cadeia de caracteres são const.)
Keith Thompson
2

Você pode escrever sua própria função que faz a mesma coisa, strcat()mas que não muda nada:

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

Se as duas cadeias juntas tiverem mais de 1000 caracteres, ela será cortada em 1.000 caracteres. Você pode alterar o valor de MAX_STRING_LENGTHacordo com suas necessidades.

Pato Donald
fonte
Prevejo estouro de buffer, vejo você alocado strlen(str1) + strlen(str2), mas você escreve strlen(str1) + strlen(str2) + 1caracteres. Então você pode realmente escrever sua própria função?
Liviu
Uau! Você nunca libera a memória, desagradável, desagradável! return buffer; free(buffer);
Liviu
Aliás, sizeof(char) == 1(além disso, existem outros erros mais sutis ...) Você pode ver agora por que não precisa escrever sua própria função?
Liviu
@Liviu Eu libero a memória na linha free(buffer);.
Donald Duck
11
free(buffer);depois return buffer;nunca é executado, vê-lo em um depurador;) Vejo agora: sim, você tem que liberar a memória na mainfunção
Liviu
1

Supondo que você tenha um caractere [tamanho_fixo] em vez de um caractere *, você pode usar uma única macro criativa para fazer tudo de uma vez com uma <<cout<<likeordem ("% s the% s \ n", \ n "," than "," printf formato de estilo "). Se você estiver trabalhando com sistemas embarcados, esse método também permitirá que você deixe de fora o malloc e a grande *printffamília de funções como snprintf()(Isso evita que o dietlibc se queixe de * printf também)

#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);
  • Nota 1, você normalmente não usaria argv [0] assim - apenas um exemplo
  • Nota 2, você pode usar qualquer função que produza um caractere *, incluindo funções não padrão como itoa () para converter números inteiros em tipos de sequência.
  • Nota 3, se você já estiver usando printf em qualquer lugar do seu programa, não há razão para não usar snprintf (), pois o código compilado seria maior (mas embutido e significativamente mais rápido)
technosaurus
fonte
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;
}
Miljan Rakita
fonte
1

Você está tentando copiar uma sequência para um endereço que está estaticamente alocado. Você precisa entrar em um buffer.

Especificamente:

...recorte...

destino

Pointer to the destination array, which should contain a C string, and be large enough to contain the concatenated resulting string.

...recorte...

http://www.cplusplus.com/reference/clibrary/cstring/strcat.html

Há um exemplo aqui também.

Todd
fonte
0

Esta foi a minha solução

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

mas você precisa especificar quantas cordas você vai concatenar

char *str = strconcat(3, "testing ", "this ", "thing");
Naheel
fonte
0

Tente algo semelhante a este:

#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
fonte