¿Cómo se copian vectores?

A raíz del post sobre vectores y punteros me preguntaron que, entonces, ¿cómo se copian vectores en C?

Bueno, la respuesta fácil es “elemento a elemento“.

[UPDATE}

Gracias a un comentario del padawan me doy cuenta de que se me había escurrido memcpy. A lo mejor mi ego por hacerme una macro propia me cegó, 😉

El caso es que podemos usar:

void *memcpy(void *target, void *source, int count);

Para copiar un ‘count’ bytes empezando en ‘source’ a otro ‘target’. Sólo nos tenemos que llevar un pelín de cuidado con el valor de count que se refiere a bytes, no al tamaño de int o de nuestra estructura en particular. De todas formas es tan fácil como utilizar la sizeof:

elementos*sizeof(tipo_del_elemento_del_vector)

Considérese el siguiente ejemplo:

int v1[] = {0,1,2,3,4};
int v2[] = {4,3,2,1,0};
memcpy(v1, v2, 2*sizeof(int)); // copia los dos primeros elementos de v2 en v1

[/UPDATE]

Como hay muchas formas de definir la copia vamos a dar una bastante versátil:

La macro arraycp(type, target, source, offset, count) copia ‘count’ elementos de ‘source’  en ‘target’ a partir de la posición ‘offset’. Los elementos de los vectores deben ser del tipo ‘type’.

La definición de tal macro podría ser la siguiente:

#define vsize(v) (sizeof((v))/sizeof((v)[0]))
#define arraycp(type, target, source, offset, count)\
  {\
      int i, ts, ss, c;\
      type *t; ts = vsize(target);\
      type *s; ss = vsize(source) - offset;\
      c = (count);\
      for(i=0, t=(target), s=(source)+(offset);\
          i<c && i<ts && i<ss;\
          ++i, *t++=*s++)\
        ;\
  }

Recuerda que es muy importante que tanto ‘target’ como ‘source’ sean vectores (no punteros), si no, no funcionará. [1]

[1] Eso se debe a la macro vsize que sólo calcula correctamente el número de elementos de un vector si su parámetro es un vector. Gracias a Denis y a Adri por los comentarios.

[UPDATE]

Para simular el mismo comportamiento de esta macro con memcpy, utilícese la siguiente forma:

memcpy(target, source+offset, count*sizeof(type))

[/UPDATE]

Quizá para entenderla el lector deba estar familiarizados con el preprocesador de C pero la idea es básica:

  1. Creamos un nuevo ámbito de variables mediante las llaves.
  2. Declaramos i como entero para contar el número de elementos copiados.
  3. Declaramos ts y ss para llevar los tamaños del vector destino y del (sub)vector origen respectivamente.
  4. Inicializamos i a 0 en el for, en la condición del bucle tenemos en cuenta que el número de elementos copiados, i, sea inferior siempre al tamaño de los vectores y al propio límite establecido por el usuario con ‘count’.
  5. Declaramos sendos punteros al tipo del vector.
  6. Los inicializamos a las direcciones del vector destino y el vector fuente teniendo en cuenta el offset.
  7. En el incremento del for realizamos la copia e incrementamos i.

Hay una mejor version para GNU que pongo aquí aunque no la he probado.

En esta versión no hace falta indicar el tipo pues la extensión typeof nos lo proporciona automaticamente. Y lo que es mejor, podríamos copiar un vector de enteros sobre uno de flotantes aprovechándonos de las conversiones implícitas de tipos.

#define vsize(v) (sizeof((v))/sizeof((v)[0]))
#define arraycp(type, target, source, offset, count)\
  {\
      int i, ts, ss, c;\
      typeof((target)[0]) *t; ts = vsize(target);\
      typeof((source)[0]) *s; ss = vsize(source) - offset;\
      c = (count);\
      for(i=0, t=(target), s=(source)+(offset);\
          i<c && i<ts && i<ss;\
          ++i, *t++=*s++)\
        ;\
  }

Espero que os resulte de utilidad.

Anuncios

7 comentarios en “¿Cómo se copian vectores?

  1. ¿Y el memcpy?¿Es que nadie piensa en memcpy? :p Me parece más sencillo, memcpy( dest, org, tam*sizeof(tipodedatos) );

    Por cierto, el primer enlace lleva al inicio del blog.

  2. Una pregunta que podría solucionar si tuviera un compilador de c… ¿La “versión GNU” no pencaría si el target o el source son arrays vacíos?

  3. Bueno, que yo sepa no puedes tener un vector vacío porque cosas como:

    int v[0];
    int v[] = {};

    No son válidas.

    Ahora, lo primero que hace la macro es pasar las direcciones a punteros. Si esos punteros no apuntan a lo que deben… pues sobreescribirás cosas que no deberías tocar.

  4. Uhm, un par de notas.

    La primera, que memcpy sigue siendo “copiar arrays elemento a elemento”. Si bien libc implementa unas macros con ASM, al final para tamaños pequeños es movsb. Para tamaños grandes, primero alinea con movsb y luego pasa a copiar palabras con movsl o directamente páginas con manipulaciones de memoria virtual.

    La segunda, es que sí se puede tener un array vacío, porque definiciones como las que propone Salva (int v[0]; e int v[] = {}) se las traga gcc (y si les haces un sizeof da 0), sino que además, siempre podrías hacer cosas como:

    int *p = malloc(0);

    Y sí, como dice Denís, la macro cascará cuando el destino es un vector vacío. Siempre que por cascará queramos decir que copiará memoria de un sitio a otro y que no debería hacerlo. Igual te da un SIGSEGV, o igual sólo te llena de basura alguna porción de memoria.
    Si el origen es vacío, copiará de esa posición lo que haya (basura) al destino. No dará violación de segmento, pero tampoco es el comportamiento deseado.

    Resumiendo, que sí, que lo suyo es que la macro comprobara los tamaños antes de liarse a copiar, pero esto es C, ¿o es que nadie se acuerda de strcpy? xD

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s