jueves, 18 de agosto de 2011

Las Funciones memcpy() Y memmove()

Cuando terminamos el ciclo básico y dominamos una buena parte de las funciones de entrada y salida, empezamos a sentirnos agobiados por la incapacidad de mover y copiar la información que obtenemos. Quisiéramos o no, nos vemos forzados a estudiar las funciones para la manipulación de caracteres.

La  función strcpy es la que primero hallamos y utilizamos hasta que vemos su peligro, luego es strncpy, mas prometedora que strcpy pero el peligro aun toca la puerta, y al igual que strcpy solo funciona con cadena de caracteres y corrompen los datos al agregar el carácter \0 al final (con strncpy no siempre es cierto).

Buscando hallamos a memcpy() y memmove() e inmediatamente caemos en una encrucijada ¿Cuándo debo usar memmove? De esto se trata este artículo, explicar cuando utilizarlas y porque.
/* Tomado del código fuente de linux */
void *memcpy(void *dest, const void *src, size_t count)
{
 char       *tmp = dest;
 const char *s   = src;

 while(count--)
  *tmp++ = *s++;
 return dest;
}
Debido a la gran cantidad de versiones y optimizaciones realizadas a esta función, copie esta implementación para la cual su comportamiento está definido en todas las arquitecturas (no intentes optimizarla con el compilador). Antes de entrar en lleno, examinemos que dicen las especificaciones de ambas funciones con el Traductor 9000:


Todo parece estar bien, a excepción de esa palabra; superposición. Para entender más un poco sobre la superposición entre dos áreas de memoria, veamos un ejemplo visual sin superposición, pero antes definamos (de manera vaga) lo que superposición significa (para nuestros fines).
Es cuando dos áreas de memorias están correlacionadas y una modificación en una de las partes se ve reflejada a lo largo de las modificaciones siguientes.

Como se puede apreciar en la animación, una modificación en una área de memoria no afecta las modificaciones sucesivas. Veamos ahora otra animación un ejemplo que muestra claramente la superposición.

En la animación anterior, el uso de la función memmove es necesario. A estas alturas te podrías estar empezando a preguntar si  utilizar memmove y nunca memcpy, la respuesta simple es: podría. Los tíos de FreeBSD (quizás otros) meditaron sobre esto y modificaron su API, de manera que cuanto intentes utilizar memcpy o memmove, sin darte cuenta estarás usando bcopy; función muy parecida a memmove.

Despedida

Luego de haber leído este artículo sería lógico si terminas preguntándote ¿Cuál es el propósito de la función memcpy? ¿Introducir posibles errores en nuestra aplicaciones o copiar áreas de memoria? La respuesta es que memcpy algunas veces posee optimizaciones específicas; es más rápida.

Ahora que conocemos la diferencia entre ambas funciones usarémos memmove solo cuando sea necesario; siempre tratemos de utilizar memcpy, la cual en muchos casos posee mayor rendimiento.

Comentarios y Cosas Raras
 ... os he atrapado con las manos en la falda ¡Qué problema! ¿Eh? Terroríficas, precisas y calculadoras (frías), no podemos escapar de ellas; nos tienen dominados ...
Enlaces
  1. Ejemplo sin superposición
  2. Ejemplo con superposición
  3. Ejemplo con memmove

lunes, 15 de agosto de 2011

Buenas maneras para programar y técnicas de legibilidad

Anteayer estaba observando una carpeta con ficheros fuentes de ANTAÑO. Viendo estos ficheros fuentes (míos todos) difíciles de leer, con estilos diferentes y buenas técnicas de ofuscación (!), decidí elaborar este artículo que contiene de manera resumida una listas de buenos modales para utilizar mientras se programa.

Aclaro que ninguna de estas normas son de carácter obligatorio, pero se sugiere su puesta en práctica. Muchas de estas recomendaciones no son mías, son solo una compilación de aquellas que considero se deben poner en práctica y tomar en consideración por todos los programadores, principalmente por aquellos que programan en C, que es a los que principalmente está dirigido el artículo.

Maneras
Fuera de código
  • Recicla unas cuantas hojas de papel e imprime una copia del documento «GNU Coding Standards» y no lo leas, solo quémalo; será un lindo y simbólico gesto. Linux
  • Lea el fichero Conding Style (Estilo del código) encontrado en la documentación del proyecto al que piensas contribuir, apega tu estilo de programar en lo posible a sus demandas, pues, muchos proyectos son muy inflexibles en este asunto. Newbie Shell
  • Cuando inicies un nuevo proyecto y redactes el fichero Coding Style, se flexible, de esta manera ganarás la voluntad de más personas para que te ayuden en tu proyecto. Newbie Shell
Sangrado
  • Si necesitas más de 3 niveles de sangría (identation) en tu código, vas mal y deberías reparar tu programa. Linux
  • Evita si puedes, sagrados menores de cinco espacios de longitud. Sangrados muy pequeños pueden causar problemas de visibilidad de bloques y ocasiona en el peor caso problemas de legibilidad y fluidez mientras se programa.  (Linux y FreBSD hablan de esto pero sugieren ocho espacios. Cinco espacios es la media y es una buena media)
  • Usa la tecla Tab para sangrar y no espacios; utiliza espacios y no la tecla Tab para alinear. De esta manera tus ficheros fuentes serán portátiles (portable) entre editores, podrás darles formato a gusto y tendrán menor tamaño. Si considera que este tipo de cosas son triviales, debería reconsiderarlo y leer este artículo Tabs vs Spaces An Eternal Holy War de J. Zawinski. Newbie Shell
  • No pongas un else justamente después de un return, es innecesario y aumenta el nivel de sangrado del código. Asterisk
if(algo) {
      esto();
      return aquello;
} else {
      lo_otro();
      /* muchas líneas de código */
      return CONSTANTE;
}

Mejor así

if(algo) {
      esto();
      return aquello;
}
lo_otro();
/* muchas líneas de código */
return CONSTANTE;



Generales
  • Divide los problemas en subproblemas mas simples tantas veces como sea necesario, hasta que la resolución de los subproblemas se torne obvia. Newbie Shell, Alg. Divide y Venceras
  • Puedes utilizar goto, con buenas practicas y cuidado, para saber cómo, observa el fichero /usr/src/linux/kernel/fork.c de tu distribución GNU/Linux favorita. Newbie Shell
  • Los nombres de las funciones y métodos deberán estar en minúsculas y las palabras separadas por un guion bajo. Gimp
  • Los nombres de las variables y los nombres de los campos de las estructuras, deberán estar todos en minúsculas. Asimismo se sugiere que trates en lo posible de que los nombres de las estructuras estén en minúsculas también.
  • Los nombres de las variables y las funciones deberán ser cortos pero significativos. Los nombres de variables de una sola letra deberán evitarse, a excepción de las variables temporales desechables. Para el caso anterior, utiliza los nombres i, j, k, m, n para enteros; c, d, e para caracteres; p, q para punteros; s y t para puteros a cadena de caracteres. (SunOS) OpenSolaris
  • Evita en lo posible variables con nombres largos y de varias palabras como en: pointer_to_a_list en su lugar piensa y reduce el tamaño así: list_ptr, quizás listptr o listp. Newbie Shell
  • No utilices números mágicos, en su lugar usa constantes. Newbie Shell
  • Los valores de las enumeraciones deberán estar en mayúsculas. FreeBSD
  • Evita en lo posible usar variables globales, piensa sobre la necesidad de usarlas y en el mejor caso, escribe una explicación breve sobre las claras ventajas y las pocas desventajas. Tome dicho texto y agréguelo como documentación. Newbie Shell
  • Los nombre de las constantes deberán estar en mayúsculas.
  • En lenguaje C, no hagas conversión de tipo (void *). Conversiones implícitas de/a (void *) son explícitamente aceptables por la especificación de C. Asterisk
  • El número de variables locales utilizadas no deberá ser mayor de 10, de lo contrario estas haciendo algo mal. Piensa la función una vez mas y divídela en pedazos más cortos. Un cerebro humano por lo general puede mantener fácilmente el rastro de siete cosas diferentes, una cosa mas, y se confunde. Linux
  • Los comentarios son buenos, pero existe el peligro de sobrecomentar el código. Nunca trates de comentar cómo funciona tu código en un comentario, es mejor programarlo de manera que su lectura y significado sean visibles. Linux
  • Al declarar variables, utiliza un línea por declaración, de esa manera podrás agregar pequeños comentarios a cada una de ellas sobre su uso.
  • Al hacer comparaciones, pon la parte literal en la parte izquierda de la comparación y la parte variable en la parte derecha. Con esta técnica podrías evitar errores de tipografía, muchas veces no detectado por el compilador y muy difíciles de encontrar cuando los buscas. Sucede cuando por error pones un = en vez de == . Peter Van Der Linden Ejem:
/* Utilice */
if(CONSTANTE==var)

/* en vez de */
if(var==CONSTANTE)
 
Despedida

Sin dudas existen más recomendaciones propuestas por los programadores de los proyectos exitosos de allá fuera, pero a mi entender las que listo aquí son las más significativas a la hora de programar.

Si eres programador, experimentado o no, deberías intentar cumplir cada una de estas normas para programar o al menos intentar desarrollar tus propias estrategias de escritura basadas en el mismo propósito que intentan cumplir estas técnicas; Estilo Y Legibilidad.

Comentarios y cosas raras
3:00 am

– escribiendo de forma muy rápida – tac tac tac tac [¡espeis!] tac tac tac next tac tac tac tac [¡espeis!] step continue tac tac tac [enter] [enter] ... ¡Segmentation Fault! ...  ¡AH! Funciona, JODER ... por favor.

Referencias
  1. Asterisk Coding Guidelines
  2. FreeBSD kernel source file style guide
  3. C Style and Coding Standards for SunOS
  4. Linux kernel coding style