¿Cómo concateno cadenas const / literales en C?

346

Estoy trabajando en C, y tengo que concatenar algunas cosas.

En este momento tengo esto:

message = strcat("TEXT ", var);

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

Ahora, si tiene experiencia en C, estoy seguro de que se dará cuenta de que esto le da un error de segmentación cuando intenta ejecutarlo. Entonces, ¿cómo puedo solucionar eso?

The.Anti.9
fuente
66
¡Me gustaría sugerir que uses strlcat en lugar de strcat! gratisoft.us/todd/papers/strlcpy.html
activout.se
3
Me gustaría repetir esa sugerencia. Strcat causa vulnerabilidad a las vulnerabilidades de desbordamiento del búfer. Alguien puede proporcionarle a su programa datos que hagan que ejecute código arbitrario.
Brian

Respuestas:

386

En C, las "cadenas" son simplemente charmatrices simples . Por lo tanto, no puede concatenarlos directamente con otras "cadenas".

Puede usar la strcatfunción, que agrega la cadena apuntada por srcal final de la cadena apuntada por dest:

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

Aquí hay un ejemplo de cplusplus.com :

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

Para el primer parámetro, debe proporcionar el búfer de destino en sí. El búfer de destino debe ser un búfer de matriz de caracteres. P.ej:char buffer[1024];

Asegúrese de que el primer parámetro tenga suficiente espacio para almacenar lo que está intentando copiar. Si está disponible para usted, es más seguro usar funciones como: strcpy_sy strcat_sdonde tiene que especificar explícitamente el tamaño del búfer de destino.

Nota : Un literal de cadena no se puede usar como un búfer, ya que es una constante. Por lo tanto, siempre debe asignar una matriz de caracteres para el búfer.

El valor de retorno de strcatsimplemente se puede ignorar, simplemente devuelve el mismo puntero que se pasó como primer argumento. Está ahí para su comodidad y le permite encadenar las llamadas en una sola línea de código:

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

Entonces su problema podría resolverse de la siguiente manera:

char *foo = "foo";
char *bar = "bar";
char str[80];
strcpy(str, "TEXT ");
strcat(str, foo);
strcat(str, bar);
Brian R. Bondy
fuente
66
¿Poner "Tenga mucho cuidado de que ..." en negrita, por favor? Esto no puede ser lo suficientemente estresado. El mal uso de strcat, strcpy y sprintf son el corazón del software inestable / inseguro.
zócalo
12
Advertencia: tal como está escrito, este código dejará un agujero enorme y enorme en su código para las vulnerabilidades de desbordamiento del búfer.
Brian
11
No hay explotación de desbordamiento de búfer posible en el ejemplo anterior. Y sí, estoy de acuerdo en general que no usaría el ejemplo anterior para longitudes de cadena indeterminadas de foo y bar.
Brian R. Bondy
13
@psihodelia: ¡No olvides que las cucharas son mucho mejores que los tenedores! ¡así que asegúrese de usar siempre una cuchara!
Brian R. Bondy
20
Para segundo @dolmen, Joel Spolsky ha escrito un artículo bastante elaborado sobre el tema. Debe ser una lectura obligatoria. ;-)
peter.slizik
247

Evite usar strcaten el código C. La forma más limpia y, lo más importante, la más segura es usar snprintf:

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

Algunos comentaristas plantearon un problema de que el número de argumentos puede no coincidir con la cadena de formato y el código aún se compilará, pero la mayoría de los compiladores ya emiten una advertencia si este es el caso.

Alex B
fuente
3
Damas, él estaba hablando de los paréntesis alrededor de "buf" del tamaño del argumento. no son necesarios si el argumento es una expresión. Pero no entiendo por qué te rechazan. Creo que su respuesta es la mejor de todas, aunque sea c99. (¡quizás por eso no están de acuerdo! ¡Lamers!) +1
Johannes Schaub - litb
44
sizeof () solo funciona aquí para char buf [...]. NO para char * buf = malloc (...). No hay muchas diferencias entre las matrices y los punteros, ¡pero esta es una de ellas!
Mr.Ree
2
Además, está tratando de realizar una concatenación. Concatenar usando snprintf()es un GRAN no no.
Leonardo Herrera el
55
@MrRee: ¡Las diferencias entre punteros y matrices son enormes y completas! Está en la forma en que utiliza los que no siempre son diferentes. Además, los punteros y la asignación dinámica son conceptos realmente ortogonales.
Carreras de ligereza en órbita el
34
Una de mis manías son las personas como @unwind que insisten en la distinción inútil entre sizeof(x)y sizeof x. La notación entre paréntesis siempre funciona y la notación sin paréntesis solo funciona a veces, así que siempre use la notación entre paréntesis; Es una regla simple de recordar y es segura. Esto entra en un argumento religioso: he estado involucrado en discusiones con aquellos que se oponen antes, pero la simplicidad de 'siempre usar paréntesis' supera cualquier mérito de no usarlos (IMNSHO, por supuesto). Esto se presenta para el equilibrio.
Jonathan Leffler
24

Amigos, use str n cpy (), str n cat () o s n printf ().
¡Superar su espacio de búfer destruirá todo lo que sigue en la memoria!
(¡Y recuerde dejar espacio para el carácter nulo '\ 0' final!)

Mr.Ree
fuente
3
No solo debe recordar permitir espacio para el carácter NULL, sino que debe recordar agregar el carácter NULL. strncpy y strncat no hacen eso por ti.
Graeme Perrow el
Uh strncpy () y strncat () seguramente agregan el carácter de terminación. De hecho, agregan demasiados. Al menos mientras haya espacio en el búfer, que es una gran trampa con estas llamadas. No recomendado.
Descanse el
3
@unwind, creo que el punto de Graeme es que si el búfer es demasiado pequeño, strncpy o strncat no agregarán la terminación '\ 0'.
quinmars
2
snprintf es bueno, strncpy / strncat es la peor recomendación posible, strlcpy / strlcat es mucho mejor.
Robert Gamble
99
No utilice strncpy(). Es no una versión "segura" de strcpy(). La matriz de caracteres de destino puede rellenarse innecesariamente con '\0'caracteres adicionales o, lo que es peor, puede dejarse sin terminar (es decir, no una cadena). (Fue diseñado para su uso con una estructura de datos que rara vez se utiliza más, una matriz de caracteres rellena hasta el final con cero o más '\0'caracteres.)
Keith Thompson
22

Las cadenas también se pueden concatenar en tiempo de compilación.

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

También malloc y realloc son útiles si no sabe de antemano cuántas cadenas se concatenan.

#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
fuente
Esto va a terminar en un bucle sin fin cuando num_words>INT_MAX, tal vez debería utilizar size_tparai
12431234123412341234123
5

No olvides inicializar el búfer de salida. El primer argumento para strcat debe ser una cadena terminada en nulo con suficiente espacio extra asignado para la cadena resultante:

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

Como la gente señaló, el manejo de cuerdas mejoró mucho. Por lo tanto, es posible que desee aprender a usar la biblioteca de cadenas de C ++ en lugar de las cadenas de estilo C. Sin embargo, aquí hay una solución en 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;
}

No estoy seguro de si es correcto / seguro, pero en este momento no podría encontrar una mejor manera de hacerlo en ANSI C.

Nils
fuente
<string.h>es estilo C ++ Que desea "string.h". También calcula strlen(s1)dos veces, lo que no es necesario. s3debería ser totalLenght+1largo
Mooing Duck
44
@MooingDuck: no "string.h"tiene sentido.
sbi
No he usado cuerdas de estilo C por un tiempo. Siéntase libre de publicar una versión fija.
Nils
44
@MooingDuck: Eso es incorrecto. #include <string.h>es correcto C. Use corchetes angulares para encabezados estándar y del sistema (incluidos <string.h>), comillas para encabezados que forman parte de su programa. ( #include "string.h"funcionará si no tienes tu propio archivo de encabezado con ese nombre, pero úsalo de <string.h>todos modos)
Keith Thompson,
Tenga en cuenta que esto depende de las características específicas de C99: declaraciones y declaraciones de mezcla y matrices de longitud variable (VLA). Tenga en cuenta también que los VLA no proporcionan ningún mecanismo para detectar o manejar fallas de asignación; Si no hay suficiente espacio para asignar un VLA, el comportamiento de su programa no está definido.
Keith Thompson
4

Es un comportamiento indefinido intentar modificar los literales de cadena, que es algo así como:

strcat ("Hello, ", name);

intentará hacer. Intentará agregar la namecadena al final del literal de cadena "Hello, ", que no está bien definido.

Intenta algo así. Alcanza lo que parece estar tratando de hacer:

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

Esto crea un área de búfer que se puede modificar y luego copia tanto el literal de cadena como otro texto. Solo tenga cuidado con los desbordamientos del búfer. Si controla los datos de entrada (o los verifica de antemano), está bien usar buffers de longitud fija como yo.

De lo contrario, debe usar estrategias de mitigación como asignar suficiente memoria del montón para asegurarse de que puede manejarlo. En otras palabras, 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
fuente
44
¿Qué sucede si var / foo / bar tiene más de 1000 caracteres? > :)
Geo
1
Luego obtendrá un desbordamiento de búfer, que puede agregar código para verificar de antemano (por ejemplo, con strlen). Pero el propósito de un fragmento de código es mostrar cómo funciona algo sin contaminarlo con demasiado código adicional. De lo contrario estaría comprobando longitudes, si var / foo / bar era nula, etc.
paxdiablo
77
@paxdiablo: Pero ni siquiera lo mencionaste, en una respuesta a una pregunta en la que parece que es necesario mencionarlo. Eso hace que tu respuesta sea peligrosa . Tampoco explicaste por qué este código es mejor que el código original del OP, excepto por el mito de que "logra el mismo resultado que tu original" (¿entonces cuál sería el punto? ¡El original estaba roto !), Entonces la respuesta También está incompleto .
Carreras de ligereza en órbita el
Espero haber abordado sus inquietudes, @PreferenceBean, aunque de manera menos oportuna de lo ideal :-) Avíseme si todavía tiene un problema con la respuesta, y lo mejoraré aún más.
paxdiablo
3

El primer argumento de strcat () debe ser capaz de contener suficiente espacio para la cadena concatenada. Asigne un búfer con suficiente espacio para recibir el resultado.

char bigEnough[64] = "";

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

/* and so on */

strcat () concatenará el segundo argumento con el primer argumento, y almacenará el resultado en el primer argumento, el char * devuelto es simplemente este primer argumento, y solo para su conveniencia.

No obtiene una cadena recién asignada con el primer y segundo argumento concatenados, lo que supongo que esperaba en función de su código.

Pieter
fuente
3

La mejor manera de hacerlo sin tener un tamaño de búfer limitado es mediante el uso de asprintf ()

char* concat(const char* str1, const char* str2)
{
    char* result;
    asprintf(&result, "%s%s", str1, str2);
    return result;
}
Nico Cvitak
fuente
2
Deberías volver char *, no const char *. El valor de retorno deberá pasarse a free.
Por Johansson
Lamentablemente asprintfes solo una extensión de GNU.
Calmarius
3

Si tiene experiencia en C, notará que las cadenas son solo matrices de caracteres donde el último carácter es un carácter nulo.

Ahora eso es bastante incómodo, ya que tienes que encontrar el último personaje para agregar algo. strcatHará eso por ti.

Entonces strcat busca en el primer argumento un carácter nulo. Luego reemplazará esto con el contenido del segundo argumento (hasta que termine en un valor nulo).

Ahora veamos tu código:

message = strcat("TEXT " + var);

Aquí está agregando algo al puntero al texto "TEXTO" (el tipo de "TEXTO" es const char *. Un puntero).

Eso generalmente no funcionará. La modificación de la matriz "TEXTO" tampoco funcionará, ya que generalmente se coloca en un segmento constante.

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

Eso podría funcionar mejor, excepto que nuevamente está intentando modificar textos estáticos. strcat no está asignando nueva memoria para el resultado.

En su lugar, propondría hacer algo como esto:

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

Lea la documentación de sprintfpara verificar sus opciones.

Y ahora un punto importante:

Asegúrese de que el búfer tenga suficiente espacio para contener el texto Y el carácter nulo. Hay un par de funciones que pueden ayudarlo, por ejemplo, strncat y versiones especiales de printf que le asignan el búfer. No garantizar que el tamaño del búfer provocará daños en la memoria y errores explotables de forma remota.

Ralf
fuente
El tipo de "TEXT"es char[5], no const char* . Se descompone char*en la mayoría de los contextos. Por razones de compatibilidad con versiones anteriores, los literales de cadena no lo son const, pero intentar modificarlos da como resultado un comportamiento indefinido. (En C ++, los literales de cadena son const.)
Keith Thompson
2

Puede escribir su propia función que haga lo mismo strcat()pero que no cambie 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;
}

Si ambas cadenas juntas tienen más de 1000 caracteres, cortará la cadena a 1000 caracteres. Puede cambiar el valor de MAX_STRING_LENGTHpara satisfacer sus necesidades.

Pato Donald
fuente
Preveo el desbordamiento del búfer, te veo asignado strlen(str1) + strlen(str2), pero escribes strlen(str1) + strlen(str2) + 1caracteres. Entonces, ¿puedes realmente escribir tu propia función?
Liviu
¡Guauu! ¡Nunca liberas la memoria, desagradable, desagradable! return buffer; free(buffer);
Liviu
Por cierto, sizeof(char) == 1(además, hay otros errores más sutiles ...) ¿Puedes ver ahora por qué no tienes que escribir tu propia función?
Liviu
@Liviu Libero la memoria en la línea free(buffer);.
Donald Duck
1
free(buffer);después return buffer;nunca se ejecuta, verlo en un depurador;) Ahora veo: sí, tienes que liberar la memoria en la mainfunción
Liviu
1

Suponiendo que tiene un char [fixed_size] en lugar de un char *, puede usar una única macro creativa para hacerlo todo de una vez con un <<cout<<likepedido ("en lugar de% s el desarticulado% s \ n", "que", "printf formato de estilo "). Si está trabajando con sistemas integrados, este método también le permitirá omitir malloc y la gran *printffamilia de funciones como snprintf()(Esto evita que dietlibc también se queje de * 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);
  • Nota 1, normalmente no usarías argv [0] de esta manera, solo un ejemplo
  • Nota 2, puede usar cualquier función que genere un char *, incluidas funciones no estándar como itoa () para convertir enteros en tipos de cadena.
  • Nota 3, si ya está utilizando printf en cualquier parte de su programa, no hay razón para no usar snprintf (), ya que el código compilado sería más grande (pero en línea y significativamente más rápido)
technosaurus
fuente
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
fuente
1

Está intentando copiar una cadena en una dirección que está asignada estáticamente. Necesitas un gato en un búfer.

Específicamente:

...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

Hay un ejemplo aquí también.

Todd
fuente
0

Esta fue mi solución

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

pero necesita especificar cuántas cadenas va a concatenar

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

Prueba algo similar a esto:

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