Tamaño de los vectores en C

En el post sobre copia de vectores, en los comentarios, se dice que la macro debería comprobar los tamaños pero… ¿es esto posible?

La respuesta es y es gracias al operador en tiempo de compilación sizeof.

Este operador devuelve el tamaño de un objeto o tipo en bytes. Así que podemos construir una macro muy sencilla que nos de el número de elementos de un vector:

#define vsize(v) (sizeof((v))/sizeof((v)[0]))

Ahora bien, muy importante, esta macro no funcionará con punteros. El tamaño de un puntero es el tamaño de la palabra (conjunto de bytes) usado para direccionar una posición de memoria: en máquinas de 32bits es de 4bytes y en máquinas de 64bits, de 8bytes. Así que nuestra macro no se comportará correctamente.

Con esto en mente se podría reescribir la macro de copia para que tuviese en cuenta el tamaño. La tenéis aquí y actualizada en la entrada original:

#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++)\
        ;\
  }

¿Comentarios, mejoras?

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