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:
- Creamos un nuevo ámbito de variables mediante las llaves.
- Declaramos i como entero para contar el número de elementos copiados.
- Declaramos ts y ss para llevar los tamaños del vector destino y del (sub)vector origen respectivamente.
- 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’.
- Declaramos sendos punteros al tipo del vector.
- Los inicializamos a las direcciones del vector destino y el vector fuente teniendo en cuenta el offset.
- 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.
¿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.
SÃ, mucho más sencillo, ahora edito la entrada y pongo como emular la macro con memcpy.
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?
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.
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
Claro, estaría genial que comprobara los tamaños pero es que n hay, a priori, manera de saber el tamaño de un array.