Archivo

Archivo para la categoría ‘C/C++’

¿ Y tú qué eres ? Identificando clases heredadas en C++

Viernes, 9 de Diciembre de 2011 Gaspar Fernández Sin comentarios

blogvision_estudios_small

En ocasiones, sobre todo cuando estamos con temas de herencia entre clases, tenemos la necesidad de consultar de qué tipo es una instancia. Es decir, tenemos una clase Mascota, y clases Perro y Gato (subclases de la primera). Imaginad que en el código hay muchas funciones, que queremos que sean comunes para todas las mascotas, y por tanto, aceptarán como parámetro una instancia de Mascota, pero en su ejecución, necesitan en un momento saber de qué tipo concreto son (Perro o Gato). ¿ Cómo podemos averiguarlo ?

Lo podemos hallar con dynamic_cast. dynamic_cast forma parte de la RTTI (RunTime Type Information / Información de tipos en tiempo de ejecución), y es capaz de guardar los datos del tipo específico de una instancia y utilizarla durante la ejecución del programa (no sólo en la compilación). Esto no está en la especificación original de C++ (del 1983), sino que fue introducido en 1991, además, en algunos compiladores no viene activado por defecto (según Wikipedia, es lógico, ya que esto puede causar un coste, tanto en memoria, espacio del ejecutable y uso de memoria.)

Una forma para comprobar el tipo que estamos utilizando es la siguiente:

1
2
3
4
5
Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      // Acciones con una instancia de Perro llamada a
    }

En este caso estamos comprobando que nuestra Mascota es un Perro.

A continuación vemos el ejemplo completo, en el que tenemos una serie de mascotas y vamos a llevarlas al veterinario, pero para eso, el veterinario determina qué tipo de mascota es y lo deriva al doctor adecuado (es un ejemplo). El ejemplo estará disponible para descarga al final del post.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <string>

using namespace std;


class Mascota
{
public:
  Mascota(string nombre):nombre(nombre)
  {
  }
  virtual ~Mascota()
  {
  }

  string getNombre()
  {
    return nombre;
  }
private:
  string nombre;
};

class Perro : public Mascota
{
public:
  Perro(string nombre):Mascota(nombre)
  {
  }
};

class Gato : public Mascota
{
public:
  Gato(string nombre):Mascota(nombre)
  {
  }
};

class Iguana : public Mascota
{
public:
  Iguana(string nombre):Mascota(nombre)
  {
  }
};

void veterinarioPerro(Perro *p)
{
  cout << "Ponemos una inyección a tu perro " << p->getNombre() << endl;
}

void veterinarioGato(Gato *g)
{
  cout << "Ponemos una inyección a tu gato " << g->getNombre() << endl;
}

void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

  Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      veterinarioPerro(a);
    }
  else if (Gato *a = dynamic_cast<Gato *>(m))
    {
      veterinarioGato(a);
    }
  else
    {
      cout << "No sé qué tipo de mascota tienes" << endl;
    }
}

int main()
{
  Mascota *m;
  Mascota *m2;
  Mascota *i;

  m=new Gato("Mini");
  m2=new Perro("Rufus");
  i= new Iguana("Igor");

  veterinario(m);
  cout << endl;
  veterinario(m2);
  cout << endl;
  veterinario(i);
}

Tenemos que tener en cuenta un requisito primordial para dynamic_cast, y es que la clase sobre la que hagamos la llamada, tiene que ser una clase polimórfica, y será polimórfica cuando contenga al menos un método virtual. Si no sabemos qué método puede ser virtual en la clase, siempre podemos hacer que sea el destructor.

Aunque, por poner un ejemplo de más utilidad, imaginemos que en lugar de tener variables disponibles, cuyo control podemos llevarlo a mano, tenemos un vector de mascotas, y vamos a llevarlas a todas al veterinario. Sólo pego aquí el main(), en la descarga al final de la página estará el ejemplo completo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      veterinario(*i);
      cout<<endl;
    }
}

Otra forma es utilizar typeid() para averiguar la clase a la que pertenece la instancia. Lo hacemos en la función veterinario(), es necesario incluir la biblioteca :

1
2
3
4
5
6
7
8
9
10
11
12
void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

   cout << typeid(*m).name() << endl;
  if (typeid(*m)==typeid(Perro))
    veterinarioPerro((Perro*)m);
  else if (typeid(*m)==typeid(Gato))
    veterinarioGato((Gato*)m);
  else
    cout << "No sé qué tipo de mascota tienes" << endl;
}

Algo que también se puede hacer es averiguar el nombre de la clase e imprimirlo con cout, aunque intuitivamente no podremos, porque normalmente el nombre de los tipos no llegan al programa compilado, cuando se compila con soporte RTTI, tenemos la opción de almacenar estos nombres, así que cuidado al poner los nombres de las clases, que se podrá ver en el resultado final.
Para poner por pantalla los nombres de las clases, debemos hacer lo siguiente (pongo sólo la parte de main() correspondiente a recorrer el vector):

1
2
3
4
for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << typeid(**i).name() << endl;
    }

typeid(instancia).name() nos devuelve en un formato textual el nombre de la clase. En el ejemplo hay ** (dos asteriscos) porque el iterador es un puntero a lo que estamos recorriendo, que son punteros a Mascota.
Hay que tener cuidado, porque esta opción no es igual en todos los compiladores, puede que algunos compiladores te devuelvan el nombre tal cual, y otros te devolverán el nombre con algún número o caracteres especiales en el caso de ser punteros, por ejemplo. El más claro ejemplo es GXX cuyo resultado es:

4Gato
5Perro
6Iguana
5Perro
4Gato
5Perro
4Gato
6Iguana

El número que precede los nombres de clase es el número de caracteres que tiene dicho nombre: Gato = 4, Perro = 5, Iguana = 6 . Si probamos con Mascota, saldrá un 7. Y no hay ninguna forma de hacerlo portable a diferentes compiladores por lo que tendremos que hallar soluciones específicas en el caso de que queramos tener el nombre de la clase en formato plano.

En GXX (g++) podemos utilizar lo siguiente, es una función que arregla el nombre para los humanos, no sólo de clases, sino más cosas. Podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
string demangle(const char *input)
{
  int status;
  string res(abi::__cxa_demangle(input, 0, 0, &status));
  return res;
}

int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << demangle(typeid(**i).name()) << endl;
    }
}

He utilizado una función para simplificar un poco esto, ya que __cxa_demangle(), tiene varios parámetros y nos interesa tener una función que la llame con los parámetros por defecto, para hacer la tarea más directamente. También tenemos que incluir

En G++ podemos utilizar la opción -fno-rtti si no queremos compilar nuestros programas en C++ con este soporte.

Descárgate los ejemplos: dynamic_types.tar.bz2

Utilizar float con sprintf() y derivados en Arduino

Miércoles, 7 de Diciembre de 2011 Gaspar Fernández 2 comentarios

globo-de-aire
El objetivo de la plataforma Arduino es que los programas sean pequeños, ya que tienen que caber en pocos Kbs. Un pequeño problema que tenemos con eso es que muchas bibliotecas no están implementadas completamente, sino que las encontramos en su versión light, en las que implementan sólo las funcionalidades más normales.
Un ejemplo de ello son los comandos printf() y scanf() cuya funcionalidad no cubre los valores de punto flotante (float, double), y que si hacemos el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char buffer[30];
  sprintf (buffer, "Valor: %f\n", 25.4);
  Serial.println(buffer);
  delay(1000);
}

Veremos constantemente:


Valor: ?
Valor: ?
Valor: ?

Y no muestra el valor deseado. Eso sí, ocupa 4.29Kb

La clave para la solución

El tema está en que el compilador no enlaza con las bibliotecas buenas, sino que enlaza con las ligeras. Por lo que tenemos que hacer que éste enlace con las bibliotecas correctas añadiendo el siguiente parámetro al compilador avr-gcc:

-Wl,-u,vfprintf -lprintf_flt

Haciendo el cambio para SConstruct

Si para compilar tus proyectos de Arduino utilizas este método, tienes que modificar una línea en el archivo SConstruct… pongo parte del contexto, cerca de la línea 170, donde pone:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Debemos escribir:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES-Wl, -u,vfprintf -lprintf_flt -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Con esta modificación, el programa en Arduino (el binario ocupa 5.8Kb) debe dar:

Valor: 25.400000
Valor: 25.400000
Valor: 25.400000

Más información

La IDE de Arduino hace las llamadas al compilador desde su código fuente en lugar de utilizar un Makefile, por ejemplo, por lo que para compilar incluyendo las instrucciones del linkador en el código fuente del IDE. El problema de esto es que no hay una forma fácil de conmutar el linkado con printf_flt y printf_min (que es como se llama la versión light).

Con scanf() también se puede

scanf() también permite la inclusión de la biblioteca que es capaz de leer floats. La instrucción que hay que escribir es:

-Wl,-u,vfscanf -lscanf_flt

Foto: Eric Lim Photography (Flickr) compartido con CC-by a 30/11/2011

Destructores virtuales en C++

Lunes, 5 de Diciembre de 2011 Gaspar Fernández Sin comentarios

papel destruido

Muchas veces, cuando leemos código de otras personas, hemos visto destructores declarados como virtual en algunas clases. Es una de esas cosas que si hacemos siempre no pasa nada, pero si no hacemos nunca, en ocasiones nos llevamos sorpresas; pero muchos desarrolladores optan por poner sólo en los casos justos.

¿ Para qué valían los métodos virtual ?

Eso está en un post anterior, pero básicamente valen para que las subclases tengan un método propio que se llame de la misma forma que el método declarado como virtual. Aunque cuando se trata de los destructores… en cada clase tendremos un destructor que se llama como mi clase, por lo que parece que esto no tiene demasiado sentido. Aunque está relacionado.

¡Demostración de la destrucción!

Aunque esto difiera de cómo construiríamos esta estructura en realidad, vamos a imaginarnos que nuestras clases formarán un edificio. Tendremos la clase Edificio, la clase Planta, y la clase Habitación, donde Planta será subclase de Edificio y Habitación subclase de Planta.

Para construir la habitación, todo va bien, primero creamos el Edificio, luego la Planta y finalmente la Habitación; pero para destruirlo todo, depende del tipo de objeto que creemos y, en ocasiones, como con las Mascotas, nos interesará declarar los objetos con la clase padre (o superclase) y a la hora de construirlo hacerlo con la clase hija (o subclase) correspondiente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>

using namespace std;

class Edificio
{
public:
  Edificio()
  {
    cout << "Se construye el edificio" << endl;
  }

  virtual ~Edificio()
  {
    cout << "Sacamos las bombillas del ascensor" << endl;
    cout << "Se destruye el edificio" << endl;
  }

private:
};

class Planta : public Edificio
{
public:
  Planta()
  {
    cout << "Se construye la planta" << endl;
  }

  ~Planta()
  {
    cout << "Sacamos los muebles de los pasillos" << endl;
    cout << "Se destruye la planta" << endl;
  }

private:
};

class Habitacion : public Planta
{
public:
  Habitacion()
  {
    cout << "Se construye la habitación" << endl;
  }

  ~Habitacion()
  {
    cout << "Saco las cosas de la habitación "<<endl;
    cout << "Se destruye la habitación" << endl;
  }

private:
};


int main()
{

  Edificio *h = new Habitacion();

  delete h;
  return 0;
}

La respuesta esperada es la siguiente:

Se construye el edificio
Se construye la planta
Se construye la habitación
Saco las cosas de la habitación
Se destruye la habitación
Sacamos los muebles de los pasillos
Se destruye la planta
Sacamos las bombillas del ascensor
Se destruye el edificio

Pero si quitamos el virtual, la respuesta será la siguiente:

Se construye el edificio
Se construye la planta
Se construye la habitación
Sacamos las bombillas del ascensor
Se destruye el edificio

Muchas veces, los destructores no tendrán nada, cuando no hay nada que hacer, pero imaginad que el Edificio reserva memoria para almacenar ciertos datos, la Planta, reserva también memoria para algo, y la Habitación por su parte también, en ese caso sería necesario pasar por todos los destructores para que cada uno libere su región de información. Así evitamos memory leaks.

En el caso de poner virtual en todos los destructores, no pasa nada.

Más ejemplos

Otro ejemplo puede ser cuando queremos crear un árbol, y los nodos pueden ser de dos tipos (nodo con ramificaciones, o sin ellas):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Nodo
{
public:
  Nodo(string n)
  {
    nombre=n;
    cout << "Construyo el nodo "<<nombre<<endl;
  }

  virtual ~Nodo()
  {
    cout<< "Destruyo el nodo "<<nombre<<endl;
  }
 
  virtual void print(int espacios)
  {
    cout << "Nodo: "<<nombre<<endl;
  }

public:
  string nombre;
};

class Rama: public Nodo
{
public:
  Rama(string n):Nodo(n)
  {  
    cout << "Construyo la rama "<<n<<endl;
  }

  ~Rama()
  {
    while (!nodos.empty())
      {
    delete nodos.back();
    nodos.pop_back();
      }
    cout <<"Destruyo la rama " << nombre << endl;
  }

  void addNodo(Nodo *n)
  {
    nodos.push_back(n);
  }

  void print()
  {
    cout << "Rama: "<<nombre<<endl;
    for (vector<Nodo*>::iterator it=nodos.begin(); it!=nodos.end(); ++it)
      {
    (*it)->print();
      }
  }

public:
  vector<Nodo*> nodos;
};

int main()
{
  Nodo *arbol= new Rama("main");

  Nodo *nodo= new Nodo("Abuelo sin nietos");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  nodo= new Rama("Abuelo 2");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  Nodo *nodo2= new Nodo("Hijo 1");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo2= new Rama("Padre 1");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  Nodo *nodo3= new Nodo("Hijo 3");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 4");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 5");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 6");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo2= new Nodo("Hijo 2");
  static_cast<Rama*>(nodo)->addNodo(nodo2);

  nodo= new Rama("Abuelo 3");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  nodo2= new Nodo("Hijo 7");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo2= new Rama("Padre 2");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo3= new Nodo("Hijo 8");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);

  cout << endl;
  arbol->print();
  cout << endl << endl;

  delete arbol;
}

En este caso, si quitamos el virtual al destructor, podemos ver que sólo se destruye el nodo main, quedándose todo lo demás en memoria.

Otros ejemplos de este uso pueden ser:

  • Sistemas que mantentan archivos, sockets, pipes abiertos desde la superclase (todo tiene que ser cerrado)
  • Instancias cuyas superclases tengan cadenas char* , tenemos que liberar todas las cadenas
  • Estoy abierto a sugerencias en los comentarios

Foto:  <a href=”http://www.flickr.com/photos/randomurl/445352972/”>WagsomeDog</a> (Flickr) Compartido con CC-by a 28/Nov/2011

[Arduino] Utilizando la memoria Flash en lugar de la SRAM para constantes

Viernes, 2 de Diciembre de 2011 Gaspar Fernández Sin comentarios

temp_ardublogOtra cosa no, pero los Arduino no son conocidos por su gran memoria RAM, y es que, por ejemplo en la serie Diecimila, con el Atmega168 tenemos 1Kb de RAM, con el Atmega328, hay 2Kb de RAM, aunque puede que para algunos de nuestros programas nos quedemos un poco cortos.

Una gran ayuda para esto puede ser utilizar las constantes que cree nuestro programa, en forma numérica de tabla de valores constante, o de cadena de caracteres, por ejemplo, para enviar mensajes predeterminados por el Serial, decir el nombre de la aplicación, la versión, etc.

PROGMEM

Será una macro creada para almacenar datos en espacio de programa. El programa no ocupará más, y tendremos más memoria libre para utilizar y reservar a nuestro antojo.

Antes de utilizar PROGMEM, debemos hacer

1
#include <avr/pgmspace.h>

y así poder tener acceso a todas las funciones adicionales que nos proporciona esta biblioteca.

Viendo la memoria libre

Hay una función que encontré aquí, un poco chapucera, pero eficiente (en la web hay mejores funciones, pero esta es la primera que encontramos), y que calcula el espacio que queda en la memoria (en bytes):

1
2
3
4
5
6
7
8
9
10
11
12
13
// this function will return the number of bytes currently free in RAM
// written by David A. Mellis
// based on code by Rob Faludi http://www.faludi.com
int availableMemory() {
  int size = 1024; // Use 2048 with ATmega328
  byte *buf;

  while ((buf = (byte *) malloc(--size)) == NULL);

  free(buf);

  return size;
}

Con esta función podemos ver la memoria que nos queda:

PROGMEM CON NÚMEROS (int, float, char, byte, unsingeds…)

Para probarlo, lo mejor es ver una demostración (no he incluido la función availableMemory(), copiad y pegad de arriba):

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

PROGMEM int numero=25;
void loop()
{
  Serial.println(numero, DEC);
  Serial.println(availableMemory(), DEC);
  delay(1000);
}

Podemos ver cómo numero está declarado como PROGMEM int, bien, eliminemos el PROGMEM y vemos qué hace, ¡tenemos 2 bytes menos libres! Aquí demostramos que de verdad no estamos utilizando la RAM.

Arrays de números

Ahora viene lo bueno, no hacemos nada si sólo almacenamos en Flash valores, por separado, lo interesante es poder almacenar arrays con lo que tendremos muchas más posibilidades.
Por ejemplo, podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
12
PROGMEM int numeros[]={10, 29, 38, 47, 56, 64, 73, 82, 91, 0};

void loop()
{
  static int pos=0;
  Serial.println(pgm_read_word(&numeros[pos++]));
  Serial.println(availableMemory(), DEC);
  if (pos==10)
    pos=0;

  delay(1000);
}

Cada segundo mostrará por pantalla un número del array de enteros, y todos estarán en Flash, el coste de eso será de unos 100bytes más en el binario que, por tanto también irá a Flash, además de algunos ciclos de procesador; aunque en este caso, importa más la memoria.

He utilizado pgm_read_word() porque el array es de enteros (2 bytes = 1 word), si nuestra variable fuera de 1 byte (char, byte) se podrá utilizar pgm_read_byte() y si la variable es de 4 bytes (long) podremos utilizar pgm_read_dword(), para variables tipo float tenemos de igual manera pgm_read_float().

Cadenas de caracteres sin buffer

Para escribir cadenas de caracteres, lo mejor es utilizar un buffer (pero ya estamos gastando memoria), por tanto vamos a hacer un ejemplo para imprimir por el Serial sin necesidad de utilizar buffer:

1
2
3
4
5
6
7
8
9
10
11
12
13
char mens[] PROGMEM = "Hola mundo cruel y despiadado";

void loop()
{
  char *mem=mens;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();

  Serial.println(availableMemory());
  delay(1000);
}

Podemos hacer el mensaje más largo, que seguimos consumiendo la misma cantidad de memorial. Para imprimir por el Serial con esta técnica podemos crear una función (printpgm()):

1
2
3
4
5
6
7
8
void printpgm(char *texto)
{
  char *mem=texto;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();
}

o como comentan aquí, modificar la clase HardwareSerial para incluir un método que imprima cadenas de caracteres procedentes de la memoria de programa.
Y creando estas funciones nos llevamos alguna que otra sorpresa en memoria libre (aunque pequeña).

Listas enlazadas en Arduino (primera versión)

Miércoles, 30 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Es parte de un proyecto personal que quiero compartir. Se trata de una clase para utilizar listas enlazadas desde Arduino, aunque la memoria RAM de estos bichillos no sea gran cosa (1K para la versión Diecimila), puede dar para mucho, y es que hay ocasiones en las que podemos necesitar esta flexibilidad a la hora de almacenar y acceder a la información (aunque no pueda ser mucha).

La clase ha sido desarrollada utilizando templates, por lo que podréis particularizar esta clase para cualquier tipo de dato que estéis manejando. Declarando la lista de la siguiente forma:

1
LList <tipo> lista;

Antes de dar el código, hagamos algunas operaciones, vamos a ver lo que podemos hacer con 1K (vale con 600 bytes libres):

  • Con un tipo int, cada nodo ocupará 4 bytes: 600 / 4 = 150 nodos (alguno menos en realidad). Pero estos nodos pueden indicar posiciones en un mapa, un pequeño informe de lo que ha transcurrido, lecturas, estado de periféricos, etc
  • Con un tipo:
    1
    2
    3
    4
    5
    struct Map
    {
      int key;
      int value;
    }

    : podemos almacenar 600/6 = 100 elementos con clave y valor, por ejemplo, una matriz o vector disperso que nos pueden ayudar para un cálculo matemático

  • Lo malo son las cadenas, no podemos escribir un libro, pero si hacemos un tipo que almacene unas 28 letras, cuyo nodo ocupará 30 bytes, podremos almacenar 600/30=20 mensajes distintos, aunque recomiendo que esos mensajes sean generados por el programa, ya que si los mensajes son predefinidos, podemos almacenarlos en la memoria flash, que tendremos más sitio.

Ahí va el código para los copy-paste aunque el archivo lo podéis bajar de aquí (9.5Kb). Por cierto, al final del archivo hay un código de ejemplo para probar que todo funcione bien.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
*************************************************************
* @file ArduinoList.pde
* Another implementation of linked lists to use in Arduino.
*    Copyright (C) 2011 Gaspar Fernández
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @brief Linked list implementation for Arduino
*
* Features:
*   - Can access to head and tail directly
*   - Can pop back and front from this class
*   - Can access any item from the item number
*   - Can set a callback function when an error occurs
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @web http://totaki.com/poesiabinaria
* @version 0.1
* @date 27 nov 2011
* Change history
*
* To-do:
*   - Better control over non-critical errors, maybe another callback
*   - Pre-defined error methods: by serial, by output leds...
*************************************************************/


#define LLIST_ERROR_NOMEMORY     1 // Not enough memory to insert element
#define LLIST_ERROR_NOTAIL       2 // Can't get last items
#define LLIST_ERROR_NOFRONT      3 // Can't get head items
#define LLIST_ERROR_NOITEMS      4 // List empty, can't read items
#define LLIST_ERROR_NEITEMS      5 // Not enough items (pos>=sz)

template <typename Type>
class LList
{
public:
  typedef void (*ErrorCallback)(int);

  // We can add more error types
  enum ListErrorType
    {
      NO_ERROR_REPORTING,   // No error reporting
      CALLBACK          // Errors will trigger a callback
    };

  // Destruction. Clear data
  ~LList();

  // Initialization
  LList();

  // Inserts new item in the last position
  void push_back(const Type item);
  // Is the list empty?
  inline bool isEmpty() const;
  // Returns the size of the list
  inline int size();
  // Returns the last item
  inline Type back();
  // Returns the first item
  inline Type front();
  // Return the last item and delete it
  Type pop_back();
  // Return the first item and delete it
  Type pop_front();

  // Get the item at position pos
  Type getElement(unsigned pos);
  // Insert a new item at position pos
  void insert(int pos, Type item);
  // set error callback function
  void setErrorCallback(ErrorCallback cback);
  // no error reporting
  void setNoErrorReporting();
  // clears the list
  void clear();
private:
  // Reports an error
  void report_error(int err);
 
  // Deletes a list when there is only one element
  void delete_list();

  typedef struct node
  {
    Type item;
    node *next;
  } node;

  node *head;           // Pointer to the head
  node *tail;           // Pointer to the tail
  int sz;           // List size
  ListErrorType errorType; 
  ErrorCallback ecback;     // Callback when reporting an error
};

template <typename Type>
LList<Type>::LList()
{
  head=NULL;
  tail=NULL;
  sz=0;
  errorType=NO_ERROR_REPORTING;
}

template <typename Type>
LList<Type>::~LList()
{
  clear();          // Clears the list before destroying
}

template <typename Type>
void LList<Type>::push_back(const Type item)
{
  node *aux=tail;
 
  // Reserve memory for a new node
  tail=(node*)malloc(sizeof(node));
  if (tail==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }

  // Stores the item information
  tail->item=item;
  tail->next=NULL;

  // Link to the list
  if (isEmpty())
    head=tail;
  else
    aux->next=tail;

  sz++;

}

template <typename Type>
bool LList<Type>::isEmpty() const
{
  return (sz==0);       // (sz==0) = EMPTY
}

template <typename Type>
int LList<Type>::size()
{
  return sz;
}

template <typename Type>
inline Type LList<Type>::back()
{
  if (isEmpty())        // List empty = No last item
    {
      Type t;
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }

  return tail->item;
}

template <typename Type>
inline Type LList<Type>::front()
{
  if (isEmpty())        // List empty = No first item
    {
      Type t;
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  return head->item;
}

template <typename Type>
Type LList<Type>::pop_back()
{
  node *aux=head;
  Type t;

  if (isEmpty())        // List empty = No last item
    {
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }
 
  if (head==tail)       // If there is only 1 item
    {
      t=head->item;
      delete_list();
      return t;
    }
  else
    {               // More than one item
      while (aux->next!=tail)   // Searches for the item previous to the last
    aux=aux->next;
     
      t=tail->item;     // I will return the tail item
      free(tail);      
      tail=aux;         // Define the new tail
      tail->next=NULL;
      sz--;         // 1 item less

      return t;
    }
}

template <typename Type>
Type LList<Type>::pop_front()
{
  Type t;
  node *aux=head;
 
  if (isEmpty())        // List empty = No head
    {
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  t=aux->item;          // I will return the head
  head=head->next;      // The new head is the item after the head
  free(aux);
  if (head==NULL)       // If I had deleted the last item, replace the tail
    tail=NULL;          // too.
  sz--;

  return t;
}

template <typename Type>
void LList<Type>::delete_list()
{
  free(head);           // I will call this method when deleting
  head=NULL;            // a list with only one element, so all
  tail=NULL;            // the variables are set.
  sz=0;
}

template <typename Type>
void LList<Type>::insert(int pos, Type item)
{
  node *newitem;
  node *aux=head;
  int i;

  // Allocate memory for the new item
  newitem=(node*)malloc(sizeof(node));
  if (newitem==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }
  // Stores the item information
  newitem->item=item;
  newitem->next=NULL;

  // Link the item to the list
  if (isEmpty())
    {               // If the list is empty there will be only
      head=newitem;     // one item, so it will be the head and the
      tail=newitem;     // tail.
    }
  else if (pos==0)      // If the item is going to be inserted at the beginning
    {
      newitem->next=head;   // we will move the head
      head=newitem;
    }
  else if (pos>=sz)     // If the position is greater than the last item's position
    {               // it will be inserted after this item, at the tail.
      tail->next=newitem;      
      tail=newitem;
    }
  else
    {               // If not, we will have to find the position of the
      i=0;          // previous item to link its next pointer to the new
      while (i<pos-1)       // element.
    {
      aux=aux->next;
      ++i;
    }
      newitem->next=aux->next;  // And then link the next pointer of our new element
      aux->next=newitem;    // to where the next pointer of the previour element
    }               // was pointing.
  sz++;
}

template <typename Type>
Type LList<Type>::getElement(unsigned pos)
{
  int i=0;
  Type t;
  node *aux=head;

  if (isEmpty())        // If the list is empty = No data
    {
      report_error(LLIST_ERROR_NOITEMS);
      return t;
    }

  if (pos>=sz)          // If the item asked for is greater than the
    {               // last item's position. There is no valid element
      report_error(LLIST_ERROR_NEITEMS);
      return t;
    }
 
  if (pos==sz-1)        // If the item we asked for is the last, we can
    return tail->item;      // return it inmediately


  while (i<pos)         // If not, we must look for it
    {
      aux=aux->next;
      i++;
    }
  return aux->item;
}

template <typename Type>
void LList<Type>::setErrorCallback(ErrorCallback cback)
{
  ecback=cback;         // This method sets the error callback to invoke
  errorType=CALLBACK;       // when an error occurs.
}

template <typename Type>
void LList<Type>::report_error(int err)
{
  if (errorType==CALLBACK)  // If the error reporting is through a callback,
    {               // call it.
      ecback(err);
    }
}

template <typename Type>
void LList<Type>::clear()
{
  node *aux;

  while (head!=NULL)        // Delete all elements
    {
      aux=head;
      head=head->next;
      free(aux);
    }
 
  tail=NULL;            // Restore variable stats
  sz=0;
}  

template <typename Type>
void LList<Type>::setNoErrorReporting()
{
  errorType=NO_ERROR_REPORTING;
}

// Test program
// LList<int> l;

// void blink(int err)
// {
//   while (1)
//     {
//       digitalWrite(11,HIGH);
//       delay(1000);
//       digitalWrite(11, LOW);
//       delay(1000);  
//     }
// }

// void setup()
// {
//   pinMode(11, OUTPUT);
//   Serial.begin(9600);
//   l.setErrorCallback(blink);
//   l.push_back(45);
//   l.push_back(42);
//   l.push_back(47);
//   l.push_back(89);
//   l.insert(0, 33);
//   l.insert(1, 53);
//   l.insert(9, 73);
//   // 33
//   // 53
//   // 45
//   // 42
//   // 47
//   // 89
//   // 73
// }

// void loop()
// {
//   char r;
//   if (Serial.available()>0)
//     {
//       while (Serial.available()>0)
//  {
//    r=Serial.read();
//    delayMicroseconds(10000);
//  }

//       for (unsigned i=0; i<l.size(); i++)
//  {
//    Serial.println(l.getElement(i), DEC);
//  }
//       Serial.print("Elements: ");
//       Serial.println(l.size(), DEC);
//       Serial.print("Last: ");
//       Serial.println(l.pop_front(), DEC);
//     }
// }

Iniciación a los memory leaks [ejemplos en C++]

Lunes, 28 de Noviembre de 2011 Gaspar Fernández Sin comentarios

leak

Hablemos de un fenómeno que nos afecta, sobre todo en desarrollos que están en ejecución durante mucho tiempo, pero , y es que, debido a la mala gestión de la memoria podemos llegar a consumir más de lo necesario y podemos volver loco al sistema operativo utilizando la memoria virtual para darnos el espacio que necesitamos.

Los memory leaks son fugas de memoria debidas a que hemos pedido un cierto espacio de memoria durante la ejecución de nuestro programa, la hemos usado y cuando hemos dejado de usarla, no la hemos liberado, por tanto estamos ocupando más de lo que necesitamos y acaparando recursos; un ejemplo de esto son esos procesos o programas que tenemos arrancados durante varios días, y cuanto más tiempo llevan arrancados más memoria ocupan (a veces es necesario, pero otras veces, la mayoría, no).

Antes de iniciarnos en la creación de un programa acerca de lo que no debemos hacer, vamos a hacerlo bien, y echaremos un vistazo a un artículo sobre el Out Of Memory Killer (desconozco si Windows tiene algo parecido, tal vez instalando alguna aplicación), que será una utilidad de nuestro kernel que matará el proceso cuando esté consumiendo mucha memoria, o nos permitirá matarlo fácilmente con las teclas (Alt+SysRq o imprimir pantalla+F). Con unas líneas antes de nuestro programa, nos podemos evitar un disgusto, como puede ser un reinicio de sistema mientras hacemos pruebas, y puede que estemos ejecutando algo útil.

¡Precaución!

Si vemos que nuestro proceso hace que el sistema vaya muy lento, será porque en lugar de utilizar nuestra RAM, estará usando la memoria virtual de forma intensa, esta memoria es mucho más lenta que nuestra RAM, si hemos entendido lo que quiero demostrar, podemos parar el proceso gracias al OOM Killer de antes con Alt+SysRq+F, pero sólo pulsa una vez las teclas, aunque veamos que no nos hace mucho caso, el sistema tarda un tiempo en volver a recolocar toda la información y limpiar la memoria del proceso. Si pulsamos más veces, lo mismo terminamos matando el servidor gráfico o algún proceso importante, y no queremos eso.

Ajustando OOM Killer

Para todo eso, debemos incluir el siguiente código (al final pondré todo el programa, pero primero lo analizamos por separado), el objetivo es escribir el valor 15 sobre el fichero /proc/PID/oom_adj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void error(const char *mens)
{
  cerr << "ERROR: ("<<mens<<") errno: "<<errno<<": "<<strerror(errno)<<endl;
  exit (1);
}

void ajusta_oom()
{
  ofstream fi;
  char nom[30];

  // Obtenemos el nombre del fichero, aunque estemos haciéndolo
  // a lo C++, uso sprintf() aquí porque es más rápido.
  sprintf(nom, "/proc/%d/oom_adj", getpid());

  // Abrimos el fichero
  fi.open(nom);
  if (fi.fail())
    error("No puedo abrir oom_adj");

  // Escribimos 15 en el fichero, para dar más "apeletas de muerte"
  // a este proceso. Ver:
  // http://totaki.com/poesiabinaria/2010/04/cuando-un-proceso-se-come-la-memoria-de-nuestro-sistema/
  fi<<"15";
  if (fi.fail())
    error("No puedo escribir oom_adj");

  fi.close();
}

Gastando memoria

Para esta parte hay que tener cuidado, ya que depende de la RAM que tengamos cada uno, yo he hecho esto en un equipo con 2GB de RAM, hasta que se ha vuelto extremadamente lento. Aunque el objetivo es que veamos la memoria que está actualmente en consumo. Podemos utilizar el comando free() en paralelo con el programa para ver que cada vez tenemos más memoria consumida, cuando no deberíamos, porque la intuición nos diría que cuando salimos de la función todo lo reservado se libera, pero no es así.
En los sistemas operativos actuales no podremos ponernos a reservar memoria sin control, ya que el SO casi siempre nos la dará (aunque no disponga de ella, dejando el marrón al gestor de memoria del futuro), si de verdad queremos reservar memoria, gastarla y darnos cuenta, debemos utilizar la memoria que reservamos; por ello he optado por rellenar un vector de números. Cuidado con subir mucho más MAX_NEW_ELEMENTS, es más, si tienes poca memoria, te recomiendo bajarla un poco, a la mitad, por ejemplo, en un primer cálculo, sólo contando los elementos del vector (8.000.000 x 8 (long int) = 64Mb), por lo que depende de lo que haya en ejecución, con cuatro llamadas a la función gasta_memoria() habremos gastado 256Mb de memoria.
Para ver lo que vamos gastando, debemos ejecutar en un terminal el programa que voy a mostrar, y en otro terminal ejecutamos free. El programa hará varias paradas pidiendo que pulsemos intro a cada llamada de gasta_memoria() para que podamos ver lo que se ha gastado, después del código veremos la ejecución.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <fstream>

#define MAX_NEW_ELEMENTS 8000000

using namespace std;

// Sale del programa devolviendo un error
void error(const char *mens);
// Ajunta oom_adjust para matar este proceso si se nos cuelga el sistema
// Así nos quitamos un disgusto
void ajusta_oom();
// Gasta memoria (casi) sin control
void gasta_memoria();
// Espera una tecla
void intro();

void gasta_memoria2()
{

  vector <long int> *v;


  v=new vector<long int>;

  for (unsigned long i=0; i<MAX_NEW_ELEMENTS; i++)
    v->push_back(i);

  intro();
  // delete v;
}


int main()
{
  ajusta_oom();
  cout << "OOM ajustado" << endl;
  intro();
  gasta_memoria();
  gasta_memoria2();
  gasta_memoria();
  gasta_memoria2();
}

void intro()
{
  char key;
  cout << "Pulse Intro"<<endl;
  cin.get(key);
}

void gasta_memoria()
{
  vector <long int> *v;

  v=new vector<long int>;

  for (unsigned long i=0; i<MAX_NEW_ELEMENTS; i++)
    v->push_back(i);

  intro();
  // delete v;
}

void error(const char *mens)
{
  cerr << "ERROR: ("<<mens<<") errno: "<<errno<<": "<<strerror(errno)<<endl;
  exit (1);
}

void ajusta_oom()
{
  ofstream fi;
  char nom[30];

  // Obtenemos el nombre del fichero, aunque estemos haciéndolo
  // a lo C++, uso sprintf() aquí porque es más rápido.
  sprintf(nom, "/proc/%d/oom_adj", getpid());

  // Abrimos el fichero
  fi.open(nom);
  if (fi.fail())
    error("No puedo abrir oom_adj");

  // Escribimos 15 en el fichero, para dar más "apeletas de muerte"
  // a este proceso. Ver:
  // http://totaki.com/poesiabinaria/2010/04/cuando-un-proceso-se-come-la-memoria-de-nuestro-sistema/
  fi<<"15";
  if (fi.fail())
    error("No puedo escribir oom_adj");

  fi.close();
}

La ejecución será escalonada, es decir, ejecutamos, y antes de pulsar intro ejecutamos free(), así vemos cómo se va gastando la memoria tal y como indica la imagen.
testi
Lo que quiero demostrar es que el puntero al vector v, cada vez que ejecutamos new vector sobre ella, restablece su valor, y por tanto, aunque la memoria asociada a ese vector sigue reservada (y encima tiene 8000000 de elementos), ya no tenemos forma de acceder a ella, ya no sabemos la dirección de memoria con la que accedemos; vamos que el hecho de que no sea accesible no significa que no esté reservada, en uso y molestando, porque el sistema no podrá utilizar esos datos para otras cosas.
En este caso, la solución a este problema habría sido escribir:

1
delete v;

al final de la función gasta_memoria(). Aunque en ocasiones esto no es tan fácil de ver.

Cuando el programa se cierra…

Generalmente el sistema operativo, que sí que sabe lo que el proceso ha reservado y consumido, libera la memoria, y a veces puede invertir un rato, aunque a veces no aparezca toda la memoria como libre directamente, al cabo de unos minutos (como mucho) ya estará listo.

Si el sistema no reacciona…

Recuerdo, que si hemos reservado mucha, mucha memoria, el sistema dejará de responder, para ello podemos pulsar Alt+SysRq+F, y el programa morirá rápidamente (pero, pulsad estas teclas, sólo una vez, y esperar).

Foto: tao_zhyn (Flickr) Compartido con CC-by a 26/11/2011

Polimorfismos: enrevesando la herencia entre clases [C++]

Sábado, 26 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Cuando desarrollamos bajo el paradigma de la programación orientada a objetos, a poco que avanzamos tenemos la sensación de estar escribiendo más para hacer lo mismo que hacíamos antes sin clases ni instancias ni tanta estructuración.
Pero contamos con herramientas que, aunque al principio nos sonarán algo chocantes, a la larga terminarán abriéndonos un mundo de posibilidades y comprenderemos por qué es tan importante, tan utilizado y se exige en multitud de universidades y centros educativos.
Uno de esos grandes avances que nos permite la programación orientada a objetos y que hoy particularizaremos a C++ son los polimorfismos.

Imaginemos (el ejemplo es clases_animalun poco tonto, lo reconozco, pero es lo más sencillo que se me ocurre para no llenar el post de código. Estoy abierto a sugerencias) que vamos a crear un software de mascota virtual, para eso construimos una clase padre (o superclase) llamada Animal. El sistema está en una fase muy temprana de desarrollo y los animales sólo pueden emitir sonidos (aunque en este programa se pondrá una onomatopeya por pantalla).

A partir de la clase Animal crearemos las clases Perro y Gato. Lo que necesitamos es que todos los miembros de Animal tengan un método hablar() que les permita emitir su sonido característico. Nuestra función main() queda así:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
  Mascota *p, *g;
 
  p=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << p->hablar() << endl;
  cout << g->hablar() << endl;

  return 0;
}

Lo que deseamos es que p (declarado como Mascota, pero en realidad es de clase Perro), diga “GUAU” y g (instancia de Gato) diga “MIAU”. Para eso necesitamos crear un polimorfismo (será un método que en cada subclase (clase heredera, o clase hija) ejecute el método correspondiente dependiendo de la clase de la instancia que tengamos. En C++ lo hacemos con métodos virtuales:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  virtual string hablar() = 0;

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string hablar();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::hablar()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string hablar();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::hablar()
{
  return "MIAU";
}

Cuando lo ejecutemos, una vez creados el Perro (Ramiro) y el Gato (Lancelot), cada uno dirá su palabra correspondiente. Para entender la diferencia de lo que origina utilizar virtual o no utilizarlo, lo mejor es probarlo. Vemos que para crear la función “virtual string hablar()” escribimos =0 (es una forma especial para que la función quede definida y no sólo declarada). Por tanto, para eliminar el virtual, debemos declarar la función como virtual string hablar() y definir su comportamiento. El fragmento quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::hablar()
{
  return "BUU";
}

En este caso, un “Animal por defecto” (que no pertenecería a ninguna especie) diría “BUU”, si ejecutamos el programa tal cual lo tenemos ahora, la salida sería:

Acaba de nacer tu mascota Ramiro con 4patas
Acaba de nacer tu mascota Lancelot con 4patas
BUU
BUU

¡ Los dos dirían BUU ! Y eso es porque inicialmente están declaradas como Animal, así que al llamar al método hablar() se llamará al de la clase Animal en lugar de Perro o Gato.

Ahora hacemos una pequeña modificación. Cambiamos los métodos virtual hablar() por palabra() y los ponemos en la parte privada, para el acceso desde main() creamos otra función hablar() no virtual en Mascota, cuyo contenido será:

1
2
3
4
string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

El objetivo es que cada animal, antes de decir su palabra escriba: “(minombre) dice: (mipalabra)”, en el caso del perro: “Ramiro dice: GUAU”. Aunque claro, es un método de Animal, por tanto this apuntaría a un Animal, que lo es, pero también es un Perro y como perro tiene su palabra característica y su función palabra() definida y propia, en la parte privada, por lo que intuitivamente no tendríamos acceso; pero virtual también funciona aquí para poder seleccionar el método hablar() de la clase correspondiente.

El resultado final queda así (por si quieres copiar y pegar):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  virtual string palabra();

  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::palabra()
{
  return "BUUU";
}

string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string palabra();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::palabra()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string palabra();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::palabra()
{
  return "MIAU";
}

int main()
{
  Mascota *m, *g;
 
  m=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << m->hablar() << endl;
  cout << g->hablar() << endl;
  return 0;
}

Nota: A veces también se llama polimorfismo a la sobrecarga (de métodos, por ejemplo, donde podemos describir varios métodos homónimos (todos llevarán el mismo nombre), pero con diferentes tipos de argumento, y en función de ellos, el compilador podrá saber a qué método llamamos. Todo esto tiene como función hacer la programación algo más intuitiva y natural.

Conociendo el proceso que me ha enviado una señal (signal)

Jueves, 24 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Hace tiempo hablábamos de capturar señales, aunque en ocasiones, es necesario saber quién me envía esa señal, si por ejemplo nos envían un SIGINT o SIGTERM, tal vez queremos saber qué proceso nos quiere muerto y qué usuario lo ha invocado. O tal vez estamos esperando una señal de control (SIGUSR1, por ejemplo) por parte de un proceso cliente específico.

El problema es que por nuestro modo actual de direccionar señales (con signal(señal, funcion)) sólo comunicamos el número de señal que se ha recibido, sin más información.

signals_from

Para capturar esta información necesitamos utilizar sigaction para establecer la función a la que llamaremos cuando llegue la señal, para utilizarla podemos utilizar el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),
      signum,
      strsignal(signum),
      siginfo->si_pid,
      siginfo->si_uid,
      siginfo->si_errno);
}


int main(int argc, char *argv[])
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = recibe_signal;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    sigaction(SIGUSR1, &sact, NULL);

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause;

    return 0;
}

Como vemos sigaction tiene 3 parámetros:

  • La señal que queremos capturar
  • Un registro de tipo struct sigaction
  • La acción que se ejecutaba antes, por si queremos guardarla para restaurarla más tarde

Dentro del registro, tendremos como valores importantes (struct sigaction act):

  • sact.sa_sigaction : que especifica la función que la llamaremos, esta función será del tipo void funcion(int, siginfo_t*, void*), donde el primer int recibirá la señal que nos han enviado (tal y como hacía la función que asignábamos con signal), el segundo parámetro será información asociada a la señal, y la tercera el contexto de ejecución.
  • sact.sa_handler : será la función a la que se llama, es la misma que cuando hacemos signal(señal, funcion)
  • sact.sa_flags : Es un conjunto de flags, si especificamos SA_SIGINFO, estaremos dando paso a la función especificada por sa_sigaction, si no utilizaremos sa_handler. Aunque esta variable vale para más cosas, para esta práctica nos vale con saber esto. (man sigaction para más info)
  • sact.sa_mask
  • : Nos proporciona una máscara de las señales que serán bloquedas, podemos utilizar sigfillset() para rellenar toda la máscara automáticamente. Si queremos que unas se bloqueen y otras no podemos utilizar sigaddset() y sigdelset()

Ahora bien, la función que se llamará cuando venga una señal tendrá el prototipo que hemos visto antes con tres parámetros: la señal recibida, la información de la señal con un puntero a siginfo_t y el contexto de ejecución, que se pasará con un puntero a void, que por ahora no utilizaremos. La estructura que controla la información de la señal entre otros muchos datos nos facilitará lo siguiente:

  • si_pid : PID que nos envía la señal (y esta acción da título al post)
  • si_uid : Usuario que ejecuta el proceso que nos manda la señal
  • si_errno : Algún error que haya causado la señal
  • … para más información, man sigaction

Para preparar la sentencia tendremos que escribir varias líneas definiendo los valores del registro así como la llamada a la función sigaction(), por lo tanto podremos crear una función que tenga los parámetros más comunes a la hora de definir este tipo de acciones, así lo llamamos como a signal():

1
2
3
4
5
6
7
8
9
10
11
12
int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

En esta función es importante poner bien el prototipo de la función como parámetro, eso lo comenté en un post anterior sobre callbacks

Ahora, vamos a dejar el código más bonito, este programa implementará el control de errores y nos permitirá salir cuando recibamos 5 SIGINT (pulsemos 5 veces control+C):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void error(char *cadena);
int sigact(int signum, void funcion(int, siginfo_t *, void *));
void llegasigint();
void recibe_signal(int signum, siginfo_t *siginfo, void *context);

int main(int argc, char *argv[])
{
  if (sigact(SIGUSR1, recibe_signal)==-1)
    error("No puedo establecer SIGUSR1");

  if (sigact(SIGINT, recibe_signal)==-1)
    error("No puedo establecer SIGINT");
  if (sigact(SIGTERM, recibe_signal)==-1)
    error("No puedo establecer SIGTERM");
  if (sigact(SIGUSR2, recibe_signal)==-1)
    error("No puedo establecer SIGUSR2");
  if (sigact(SIGQUIT, recibe_signal)==-1)
    error("No puedo establecer SIGQUIT");

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause();

    return 0;
}

void error(char *cadena)
{
  fprintf(stderr, "ERROR: %s (errno: %d: %s)\n", cadena, errno, strerror(errno));
  exit(1);
}

void llegasigint()
{
  static int num=0;     /* No he querido usar variables globales */

  num++;
  if (num==5)
    {
      printf ("FIN del programa\n");
      exit(1);
    }
}

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),     /* PID del proceso actual */
      signum,       /* Número de señal recibida */
      strsignal(signum),    /* Señal escrita en texto (inglés) */
      siginfo->si_pid,  /* PID del proceso que nos manda la señal */
      siginfo->si_uid,  /* Usuario que nos manda la señal */
      siginfo->si_errno);   /* Error producido */

  if (signum==SIGINT)
    llegasigint();
}

int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

RIP Dennis Ritchie

Domingo, 16 de Octubre de 2011 Gaspar Fernández 2 comentarios

Muchos medios de comunicación y blogs ( 1, 2, 3, 4, 5 por ejemplo ) ya informaron de la noticia. Es una lástima que una de las grandes mentes del siglo XX y parte del XXI pasara tan desapercibida, y es que nos enteramos de la noticia hace 3 días habiendo sucedido hace una semana. Y es que sin él, este blog no existiría (C / C++ / PHP (hecho en C) / Linux (tipo Unix)), tal vez muchas de las cosas que hacemos en el ordenador no serían posibles, a lo mejor ni tendríamos ordenadores en casa, ni MP3, ni gadgets, ni smartphones, tal vez sea demasiado decir, puede que en un mundo paralelo todo esto exista sin su aportación lo que es seguro es que nada de esto sería como lo conocemos.

Dennis Ritche dejó su huella en el mundo de la informática, y una gran huella, si pudiéramos contar cuánto tiempo invirtió en su vida aprendiendo todo lo que tuvo que aprender y trabajando en su legado a la humanidad y todo el tiempo que el resto de la humanidad ha invertido utilizándolo estaríamos ante una persona situada en el Top 10 de la edad contemporánea. Desde aquí, le doy las gracias.

Después de enterarnos de esta pérdida, es inevitable compararla con la muerte de Steve Jobs, pocos días antes, alguien que sin duda, también ha hecho mucho por la tecnología, aunque se movieron en campos diferentes, uno nos proporcionó la base y las herramientas, otro utilizó esas herramientas para construir algo mayor y ver futuro en cosas que nadie consideraba útiles.

Y pensar que hace unos días estuve hablando de él en otro blog.
¡Descanse en paz Dennis Ritchie!

Ocultar los parámetros de nuestro proceso a ps

Martes, 11 de Octubre de 2011 Gaspar Fernández Sin comentarios

password_flickrEn ocasiones, estamos desarrollando una aplicación, y ésta necesita que le pasemos como parámetro, por ejemplo, una contraseña. El gran peligro que esto tiene es que cualquier usuario, pidiendo un listado de procesos con ps podrá ver la contraseña.

Imaginemos que tenemos una aplicación (que hemos hecho nosotros) que conecta con un servidor, y dado que hemos hecho un script para automatizar el proceso, el nombre de usuario y contraseña los pasaremos como parámetro al ejecutable. Dicho proceso se llama “aplicacionsegura”, dicho programa lo hemos lanzado en un servidor y otros usuarios tendrán acceso a dicha máquina. Ahora uno de ellos ejecuta lo siguiente:

$ ps ax
….
10560 pts/3    S+     0:00 ./aplicacionsegura –user=usuario –passwd=micontraseña

¡Nuestro gozo en un pozo! Otro usuario ya tiene nuestra contraseña, y ha sido muy sencillo. Y además lo podremos ver (de hecho ps es lo que hace) viendo el archivo /proc/PID/cmdline. Aunque debemos ver qué opciones tenemos:

Modificar el parámetro passwd

Para ilustrar el ejemplo, vamos a fijar la posición de –passwd=xxxxx al segundo parámetro (la captura de parámetros no es objeto de este post). Lo primero que vamos a probar es introducir un terminador en las cadenas de los parámetros, Si la cadena empieza por ‘\0′ estará vacía:

1
2
3
4
...
argv[1][0]='\0';
argv[2][0]='\0';
....

Aunque tardaremos poco en darnos cuenta que da igual, por una parte, no podríamos borrar todos los rastros de información, ya que si escribimos en la posición 0 de la cadena “–passwd=micontraseña” un terminador, el resto de la cadena se quedará en memoria y podrá ser leída. De todas formas, la cadena parece que sólo tiene un espacio para ps, es más, el \0 le ha dado igual.

Probemos ahora escribiendo “HOLA” en la cadena de la contraseña (ahora sí pongo el código completo, test.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  /* Reservamos memoria de sobra */
  char password[40];
  char usuario[40];

  if (argc<3)
    {
      fprintf(stderr,"Debe tener dos parámetros:\n%s --user=USUARIO --passwd=CLAVE\n", argv[0]);
      return EXIT_FAILURE;
    }

  /* Esto es un simple ejemplo, vamos a quitar --user y --passwd de una forma fea */

  /* Copiamos el usuario en otra variable */
  strcpy(usuario, argv[1]+7);   /* +7, para eliminar --user= (7 caracteres) */

  /* Copiamos la contraseña en otra variable */
  strcpy(password, argv[2]+9);  /* +9, para eliminar --passwd= (9 caracteres) */

  strcpy(argv[2]+9, "HOLA"); // EN LA CLAVE PONEMOS "HOLA"

  printf("user: %s ; pass: %s", usuario, password);
  /* Esperamos una tecla para terminar (sólo para que no se cierre demasiado rápido */
  /* y nos dé tiempo a mirar la información de ps */
  getchar();
  return EXIT_SUCCESS;
}

Ahora ejecutaremos lo siguiente:

$ gcc -o test test.c
$ ./test & # El programa test esperará una tecla para terminar, pero lo ejecutaremos de fondo, para que no se cierre.
$ ps ax
….
10809 pts/3 S+ 0:00 ./test –user=usuario –passwd=HOLA traseña
….
$ killall test # Para matar el proceso

Un efecto curioso, las cadenas de los parámetros no son NULL-terminated, de cara a /proc/PID/cmdline por lo que cuando ponemos un terminador, lo toma como un espacio. Y se ve parte del parámetro. Lo que sí podemos hacer es sustituir por asteriscos, tantos asteriscos como letras tenga el parámetro.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void rellenaConAsteriscos(char *cadena);

int main(int argc, char *argv[])
{
  /* Reservamos memoria de sobra */
  char password[40];
  char usuario[40];

  if (argc<3)
    {
      fprintf(stderr,"Debe tener dos parámetros:\n%s --user=USUARIO --passwd=CLAVE\n", argv[0]);
      return EXIT_FAILURE;
    }

  /* Esto es un simple ejemplo, vamos a quitar --user y --passwd de una forma fea */

  /* Copiamos el usuario en otra variable */
  strcpy(usuario, argv[1]+7);   /* +7, para eliminar --user= (7 caracteres) */

  /* Copiamos la contraseña en otra variable */
  strcpy(password, argv[2]+9);  /* +9, para eliminar --passwd= (9 caracteres) */

  rellenaConAsteriscos(argv[1]+7);
  rellenaConAsteriscos(argv[2]+9);

  printf("user: %s ; pass: %s", usuario, password);
  /* Esperamos una tecla para terminar (sólo para que no se cierre demasiado rápido */
  /* y nos dé tiempo a mirar la información de ps */
  getchar();
  return EXIT_SUCCESS;
}

void rellenaConAsteriscos(char *cadena)
{
  while (*cadena!='\0')
    *(cadena++)='*';
}

Ahora al ejecutar ps:

$ ps ax

10906 pts/3 S+ 0:00 ./test –user=******* –passwd=*************

Aunque esta forma revela en cierto modo la longitud de las contraseñas y el nombre de usuario, puede que no nos interese, que queramos eliminar el parámetro entero y que no se vea:

La manera rápida pero peligrosa y de andar por casa

El objetivo es escribir algo más largo que el último parámetro. Es decir, si el parámetro de contraseña tiene 22 letras, tenemos que escribir algo más grande, unas 23 letras, o cargarnos el terminador del segundo argumento (no son NULL-terminated, el kernel sabe dónde empieza y dónde termina la cadena, pero hay un NULL para comprobar que todo va bien).

En este caso podemos hacer:

1
strcpy(argv[2]+9, "Esto es una cadena muy grande para ver si cargándome el segundo parámetro desaparece toda la información");

y veremos cómo ps no nos dice nada, en principio no es demasiado elegante, nos estamos cargando un terminador, que nosotros podríamos necesitar, para lectura, y estamos escribiendo en una zona de memoria que no es nuestra, ya que el argumento tiene reservado un tamaño de memoria, y estamos escribiendo más de lo que debemos (podemos producir una violación de segmento).

Decirle al kernel que no queremos los parámetros

Parecida a la forma anterior, podemos hacer:

1
argv[2][strlen(argv[2])]=' ';

lo que no es intuitivo para nada, porque nos estamos cargando el terminador, que controla el paso de parámetros. De esta forma, no tendremos ningún parámetro. Tambén podemos hacer una forma general

1
argv[argc-1][strlen(argv[argc-1])]=' ';

De esta forma desaparecerán todos los argumentos de /proc/PID/cmdline ; sólo tendremos la ruta y el nombre del ejecutable, y nuestros datos, al menos por esta parte, estarán seguros.
Foto: marc falardeau (Flickr)

Visita otras webs de la red