Archivo

Archivo para la categoría ‘Cosas que damos por hechas’

Formateando la salida en Serial para Arduino [1ª parte] ( y no sólo para Arduino )

Lunes, 22 de Agosto de 2011 Gaspar Fernández Sin comentarios

Arduino - Minicom - Emacs Una de las cosas que más echo de menos programando con Arduino es el formateo de texto de cara al serial, ya que éste sólo permite cadenas de texto o números, y no todo junto. En principio es comprensible, el sistema debe estar en un tamaño muy reducido, y esta característica engordará el binario. como mínimo 1.5Kb; Podemos optar por varias soluciones que explicaré en este post:

  • sprintf()
  • Usando la clase String
  • vsnprintf()
  • Inventar nuestro printf() particular con salida a cadena
  • Inventar nuestro printf() particular con salida a Serial

Usando sprintf()

Esta forma es muy sencilla y engordará nuestro ejecutable 1.5Kb aproximadamente. Será útil cuando vayamos a formatear poco texto, en pocas salidas, ya que debemos almacenar en una variable la cadena resultante y luego esa cadena es la que podemos escribir en Serial; son 3 líneas de código por cada salida que vayamos a hacer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int contador=0;

void setup()
{
  Serial.begin(19200);
}

void loop()
{
  char texto[50];

  sprintf(texto, "Transcurridos %d segundos", contador++);
  Serial.write(texto);
  delay(1000);
}

Aunque con sprintf todo funciona bien, recomiendo utilizar snprintf() ya que a éste último le debemos decir el tamaño de la variable a escribir y si los valores hacen la cadena más larga de lo que debería, ésta será recortada, es decir, si reservamos 50 bytes para la cadena, con sprintf() tal vez se escriban más, depende de los datos a escribir, pero snprintf() escribirá 50.

1
snprintf(texto, 50, "Transcurridos %d segundos desde la ultima vez que se nos ocurrio poner en pantalla el valor del contador", contador++);

Este ejemplo se recortará, por ejemplo, cuando contador vale 100 a:

Transcurridos 100 segundos desde la ultima vez qu

Usando la clase String

Es más estilo C++, pero la clase String nos permite hacer muchas cosas, entre ellas almacenar cadenas dinámicas y no tener que preocuparnos tanto por el tamaño de un array de caracteres, aunque en un ordenador no pasa nada, en los tiempos que corren tenemos memoria de sobra, pero un pequeño Arduino no tiene mucha, por lo que tenemos que procurar no pasarnos. Este método tiene dos inconvenientes: engordará el ejecutable 2.2Kb y sólo vale para pocas salidas formateadas porque son más líneas de código que antes; aunque algo más intuitivo (si te resulta incómoda la forma de formatear de printf(), aunque este método es menos potente).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int contador=0;

void setup()
{
  Serial.begin(19200);
}

void loop()
{
  String texto;
  texto="Transcurridos "+contador;
  texto=texto+" segundos desde la ultima vez que se nos ocurrio poner en pantalla el valor del contador";
  Serial.println(texto);

  delay(1000);
}

Usando vsnprintf()

Llegamos al primer método interesante y es que nos permitirá hacer múltiples salidas, incluso escribir una función con la que podemos crear esa salida. Este método incrementará el binario en 1.5Kb, y nos permitirá fácilmente añadir salidas formateadas al estilo printf(), para ello crearemos la función printSerial(), que aceptará múltiples argumentos y se los pasará a la función vsnprintf(). Para esta función también debemos saber el número de bytes que ocupará la cadena, por lo que estableceremos un máximo: MAX_CADENA.

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
#include <stdarg.h>
#define MAX_CADENA 128
int contador=0;

void setup()
{
  Serial.begin(19200);
}

void printSerial(const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // argumentos args
  vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);

  Serial.print(tmp);  
}

void loop()
{
  printSerial("Transcurridos %d segundos desde que se inicio.", contador++);

  delay(1000);
}

Y con una pequeña modificación podemos especificar en qué Serial escribir (si tenemos Arduino Mega, por ejemplo), por unos pocos bytes más:

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
#include <stdarg.h>
#define MAX_CADENA 128
int contador=0;

void setup()
{
  Serial.begin(19200);
}

void printSerial(HardwareSerial &sp, const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // argumentos args
  vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);

  sp.print(tmp);  
}

void loop()
{
  printSerial(Serial, "Transcurridos %d segundos desde que se inicio.", contador++);

  delay(1000);
}

Esta función podrá ser copiada en vuestro código o añadida como una librería.

Extraer 10 elementos ordenados de grandes colecciones de datos

Lunes, 25 de Julio de 2011 Gaspar Fernández 3 comentarios

En ocasiones podemos tener una colección grande de información (de varios millones de elementos), y debemos obtener los diez primeros (por ejemplo) menores.
Por poner un ejemplo rápido, vamos a utilizar números, tenemos una colección de 15 números y necesitamos extraer los 5 menores:

183 231 211 312 615 314 654 923 485 21 540 30 20 98 34

En este caso, queremos obtener: 20, 21, 30, 34, 98

El problema es la optimización, necesitamos hacerlo en el menor tiempo posible, utilizando la menor cantidad de operaciones, tal vez haya un método mejor que el que voy a proponer, aunque este no es malo del todo.

Imaginemos que nuestra colección de elementos es un array (por facilitar un poco las cosas) y tiene un millón de elementos (a los que asignaremos valores de manera aleatoria); queremos obtener los 10 elementos menores.

Para ello, he utilizado dos métodos:

  1. Ordenamos el array de elementos por completo y extraemos los 10 primeros.
  2. Generamos un array de menos elementos y vamos introduciendo los elementos más pequeños.

Para cronometrar el código he utilizado una función de un post anterior, además el algoritmo quicksort está sacado de Wikipedia.
Para medir el tiempo he hecho varias ejecuciones del algoritmo con la velocidad de mi procesador al mínimo (800MHz).
El primero de ellos es, digamos, el más intuitivo:

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
#include <time.h>
#include <stdio.h>

#define TAM_ARRAY 1000000
#define TAM_LISTA 10

int colocar(int *v, int b, int t);
void quicksort(int* v, int b, int t);
unsigned long long crononsec(int startstop);

int main()
{
  int array[TAM_ARRAY];
  int i;

  crononsec(1);
  srand(time(NULL));
  for (i=0; i<TAM_ARRAY; i++)
    {
      array[i]=rand()%TAM_ARRAY;
    }
  printf("%.2fms generando array\n", (float)crononsec(0)/1000000);

   crononsec(1);
  quicksort(array, 0, TAM_ARRAY-1);
  printf("%.2fms ordenando array con quicksort\n", (float)crononsec(0)/1000000);

  for (i=0; i<TAM_LISTA; i++)
    printf("Elemento %d: %d\n", i, array[i]);
}

int colocar(int *v, int b, int t)
{
    int i;
    int pivote, valor_pivote;
    int temp;
 
    pivote = b;
    valor_pivote = v[pivote];
    for (i=b+1; i<=t; i++){
        if (v[i] < valor_pivote){
                 pivote++;    
                temp=v[i];
                v[i]=v[pivote];
                v[pivote]=temp;
 
        }
    }
    temp=v[b];
    v[b]=v[pivote];
    v[pivote]=temp;
    return pivote;
}
 
void quicksort(int* v, int b, int t)
{
     int pivote;
     if(b < t){
        pivote=colocar(v, b, t);
        quicksort(v, b, pivote-1);
        quicksort(v, pivote+1, t);
     }  
}

unsigned long long crononsec(int startstop)
{
  static unsigned long pre_time;
  static unsigned long pre_secs;
  struct timespec ts;

  if (startstop)
    {
      clock_gettime(CLOCK_MONOTONIC, &ts);
      pre_time=ts.tv_nsec;
      pre_secs=ts.tv_sec;
    }
  else
    {      
      clock_gettime(CLOCK_MONOTONIC, &ts);
      return (ts.tv_sec-pre_secs)*1000000000+ts.tv_nsec - pre_time;
    }

    return 0;
}

Este código ha tardado una media de 1045.66ms con 5 ejecuciones. Aunque sólo es un segundo, son un millón de elementos, no es mucho, y estamos hablando de una ejecución, en el caso de, por ejemplo una aplicación de servidor, con este mismo algoritmo ejecutándose de forma concurrente todo se retrasa mucho más.

El algoritmo que propongo es el 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
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
#include <time.h>
#include <stdio.h>

#define TAM_ARRAY 1000000
#define TAM_LISTA 10

void diezmenores(int *v, int *lista, int total_lista, int total);
unsigned long long crononsec(int startstop);

int main()
{
  int array[TAM_ARRAY];
  int lista[TAM_LISTA];
  int i;

  crononsec(1);
  srand(time(NULL));
  for (i=0; i<TAM_ARRAY; i++)
    {
      array[i]=rand()%TAM_ARRAY;
    }
  printf("%.2fms generando array\n", (float)crononsec(0)/1000000);

  crononsec(1);
  diezmenores(array, lista, TAM_LISTA, TAM_ARRAY);
  printf("%.2fms ordenando array con diezmenores\n", (float)crononsec(0)/1000000);

  for (i=0; i<TAM_LISTA; i++)
    printf("Elemento %d: %d\n", i, lista[i]);
}

void diezmenores(int *v, int *lista, int total_lista, int total)
{
  int i;
  int j;
  int mayor=v[0];
  int pmayor=0;
  for (i=0; i<total_lista; i++)
    {
      lista[i]=v[i];
      if (lista[i]>mayor)
    {
      pmayor=i;
      mayor=lista[i];
    }
    }
  for (i=total_lista; i<total; i++)
    {
      if (v[i]<mayor)
    {
      lista[pmayor]=v[i];
      pmayor=0;
      mayor=lista[0];
      for (j=1; j<total_lista; j++)
        {
          if (lista[j]>mayor)
        {
          mayor=lista[j];
          pmayor=j;
        }
        }    
    }
    }
}

unsigned long long crononsec(int startstop)
{
  static unsigned long pre_time;
  static unsigned long pre_secs;
  struct timespec ts;

  if (startstop)
    {
      clock_gettime(CLOCK_MONOTONIC, &ts);
      pre_time=ts.tv_nsec;
      pre_secs=ts.tv_sec;
    }
  else
    {      
      clock_gettime(CLOCK_MONOTONIC, &ts);
      return (ts.tv_sec-pre_secs)*1000000000+ts.tv_nsec - pre_time;
    }

    return 0;
}

Con este algoritmo el tiempo es de: 21.236ms por ejecución, es decir, 49.240 veces menos.
Vale, los resultados no se obtienen ordenados, pero sería muy rápido aplicar quicksort (o cualquier método de ordenación) a la lista de 10 elementos.
Por otra parte, dado que el objetivo del algoritmo no es ordenar el array ni nada de eso, si hacemos que el tamaño de la lista sea muy grande, el tiempo de ejecución crecerá muy rápidamente (la lista debe ser relativamente pequeña).

Lo podemos utilizar para dar una muestra de datos de forma rápida, por ejemplo, ante la existencia de varios millones de elementos, podemos elegir unos 200 aplicando un cierto criterio (orden alfabético, o numérico entre ellos).

¡ Siempre olvido la alineación de las variables ! GRR

Jueves, 7 de Abril de 2011 Gaspar Fernández 2 comentarios

1GB DDR3 Memory Module

Algo que pocas veces tenemos en cuenta es la alineación de las variables en la memoria RAM. Muchas veces, ni nos va, ni nos viene, aunque en ciertas ocasiones suele causar calentamientos de cabeza.

Tiene que ver con la forma que tiene la CPU para dialogar con la RAM y la arquitectura de éstas. Partimos del hecho de que pedir un dato a la memoria es algo lento, sí se hace muchos millones de veces por segundo, pero mientras viene o no viene el dato, la CPU simplemente espera.  Ahora, tenemos que tener en cuenta:

  • El diálogo CPU<–>Memoria es a través de palabras y la palabra, en un sistema de 32bit es de 32bit (4bytes), en uno de 64bit serán 8bytes, no en todos los procesadores es así, pero casi.
  • Cada byte en memoria tiene una dirección, para que la CPU y la memoria puedan ubicar el dato.
  • Por otra parte,  las palabras en memoria tienen una posición que coincide con que su dirección sea divisible por la longitud de la palabra.
  • Sólo podemos acceder a una palabra en un mismo instante.

Por tanto, en mi PC de 32bit, cuando quiero un int de 32bit, normalmente me lo traigo de direcciones que sean divisibles por 4, por lo tanto, sólo hago una lectura a la memoria; por ejemplo, mi dato ocupa desde la dirección 0×0030 a la 0×0033 (4bytes), me lo traigo de una sola tirada. De otro modo, si mi dato ocupara desde la direccion 0×0032 a la 0×0035 (4bytes también), tendría que hacer dos peticiones, la palabra 0×0030–0×0033 y la palabra 0×0034-0×0036, y luego coger los dos últimos bytes de una y los dos últimos bytes de la otra; cosa que por experimentar está bien, pero no es óptimo, perdemos mucho tiempo, tardamos el doble. Si nos queremos traer una variable de 8bytes, tendremos que hacerlo de dos tandas, y por diseño, es más fácil traérsela de una dirección de memoria divisible por 8

También tenemos que tener en cuenta que perdemos memoria, es decir, tendremos bytes en memoria que no usaremos para nada (y a veces no nos sobra precisamente), pero sólo podemos aguantarnos… u optimizar más nuestros programas (siempre que compense). Por ejemplo cuando declaramos dos variables, un short (2bytes) y un int(4 bytes), la primera de ellas (el short) se situará en memoria en la dirección 0×0030 y la segunda (el int) en la 0×0034, dejando 2 bytes dentro de la memoria inutilizados. Aunque los compiladores modernos, a la hora de declarar variables intentan hacerlo de la forma más inteligente posible, para aprovechar los huecos al máximo, aunque no declaremos las variables por orden, a la hora de situarlas en memoria, se suelen colocar 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
#include <stdio.h>

int main()
{
  char c,c2,c3,c4,c5,c6;
  int i;
  short k;
  long long i2;
  long long ai2;
  short k2;


  printf("%lX [c]\t\t(%lu bytes)\n",    (long unsigned)&c,   sizeof(c));
  printf("%lX [c2]\t\t(%lu bytes)\n",   (long unsigned)&c2,  sizeof(c2));
  printf("%lX [c3]\t\t(%lu bytes)\n",   (long unsigned)&c3,  sizeof(c3));
  printf("%lX [c4]\t\t(%lu bytes)\n",   (long unsigned)&c4,  sizeof(c4));
  printf("%lX [c5]\t\t(%lu bytes)\n",   (long unsigned)&c5,  sizeof(c5));
  printf("%lX [c6]\t\t(%lu bytes)\n",   (long unsigned)&c6,  sizeof(c6));
  printf("%lX [i]\t\t(%lu bytes)\n",    (long unsigned)&i,   sizeof(i));
  printf("%lX [i2]\t\t(%lu bytes)\n",   (long unsigned)&i2,  sizeof(i2));
  printf("%lX [k]\t\t(%lu bytes)\n",    (long unsigned)&k,   sizeof(k));
  printf("%lX [ai2]\t\t(%lu bytes)\n",  (long unsigned)&ai2, sizeof(ai2));
  printf("%lX [k2]\t\t(%lu bytes)\n",   (long unsigned)&k2,  sizeof(k2));

  return 0;
}

Nota: represento variables de tipo long unsigned para representar bien los valores en sistemas de 64bit.
El resultado es algo así:

BFAF2BCF [c]            (1 bytes)
BFAF2BCE [c2]           (1 bytes)
BFAF2BCD [c3]           (1 bytes)
BFAF2BCC [c4]           (1 bytes)
BFAF2BCB [c5]           (1 bytes)
BFAF2BCA [c6]           (1 bytes)
BFAF2BC4 [i]            (4 bytes)
BFAF2BB8 [i2]           (8 bytes)
BFAF2BC2 [k]            (2 bytes)
BFAF2BB0 [ai2]          (8 bytes)
BFAF2BAE [k2]           (2 bytes)

Esto resultará lo siguiente:

BFAF2BA8 BFAF2BAB BFAF2BAC k2 k2 BFAF2BAF
BFAF2BB0 ai2 ai2 ai2 ai2 BFAF2BB3 BFAF2BB4 ai2 ai2 ai2 ai2 BFAF2BB7
BFAF2BB8 i2 i2 i2 i2 BFAF2BB7 BFAF2BBC i2 i2 i2 i2 BFAF2BBF
BFAF2BC0 k k BFAF2BC3 BFAF2BC4 i i i i BFAF2BC7
BFAF2BC8 c6 c5 BFAF2BC7 BFAF2BCC c4 c3 c2 c1 BFAF2BCF

El caso es que las variables siempre encajan.

Pero el tema es que cuando creamos un struct en C, lógicamente las variables también tienen que encajar, y además tienen que estar en el mismo orden en que las ponemos, el compilador, esta vez no tiene la libertad de antes de reubicar información.
Vemos el siguiente ejemplo:

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
#include <stdio.h>

typedef struct
{
  short s1;         /* 2 */
  int   i1;         /* 4 */
  long long l1;         /* 8 */
  int   i2;         /* 4 */
  long long l2;         /* 8 */
  short s2;         /* 2 */
  char  c1;         /* 1 */
  short s3;         /* 2 */
} mi_struct;

int main()
{
  mi_struct e1;
  printf("%lX [e1]\t\t(%lu bytes)\n",       (long unsigned)&e1,      sizeof(e1));
  printf("%lX [e1.s1]\t\t(%lu bytes)\n",    (long unsigned)&e1.s1,   sizeof(e1.s1));
  printf("%lX [e1.i1]\t\t(%lu bytes)\n",    (long unsigned)&e1.i1,   sizeof(e1.i1));
  printf("%lX [e1.l1]\t\t(%lu bytes)\n",    (long unsigned)&e1.l1,   sizeof(e1.l1));
  printf("%lX [e1.i2]\t\t(%lu bytes)\n",    (long unsigned)&e1.i2,   sizeof(e1.i2));
  printf("%lX [e1.l2]\t\t(%lu bytes)\n",    (long unsigned)&e1.l2,   sizeof(e1.l2));
  printf("%lX [e1.s2]\t\t(%lu bytes)\n",    (long unsigned)&e1.s2,   sizeof(e1.s2));
  printf("%lX [e1.c1]\t\t(%lu bytes)\n",    (long unsigned)&e1.c1,   sizeof(e1.c1));
  printf("%lX [e1.s3]\t\t(%lu bytes)\n",    (long unsigned)&e1.s3,   sizeof(e1.s3));
 
  return 0;
}

Y en este ejemplo sí que podemos ver gran diferencia entre 32bit y 64bit.

32bit
BFC5E510 [e1]           (36 bytes)
BFC5E510 [e1.s1]                (2 bytes)
BFC5E514 [e1.i1]                (4 bytes)
BFC5E518 [e1.l1]                (8 bytes)
BFC5E520 [e1.i2]                (4 bytes)
BFC5E524 [e1.l2]                (8 bytes)
BFC5E52C [e1.s2]                (2 bytes)
BFC5E52E [e1.c1]                (1 bytes)
BFC5E530 [e1.s3]                (2 bytes)
BFC5E510 e1.s1 e1.s1 ? ? BFC5E513 BFC5E514 e1.i1 e1.i1 e1.i1 e1.i1 BFC5E517
BFC5E518 e1.l1 e1.l1 e1.l1 e1.l1 BFC5E51B BFC5E51C e1.l1 e1.l1 e1.l1 e1.l1 BFC5E51F
BFC5E520 e1.i2 e1.i2 e1.i2 e1.i2 BFC5E523 BFC5E524 e1.l2 e1.l2 e1.l2 e1.l2 BFC5E527
BFC5E528 e1.l2 e1.l2 e1.l2 e1.l2 BFC5E52B BFC5E52C e1.s2 e1.s2 e1.c1 ? BFC5E52F
BFC5E530 e1.s3 e1.s3 ? ? BFC5E533

Ya de primeras vemos que la estructura ocupa 36bytes y la suma de sus partes (2 + 4 + 8 + 4 + 8 + 2 + 1 + 2 = 31 bytes), por lo que vemos que la estructura ocupa más.
Si vemos el mapa de memoria (la tabla de arriba), vemos cinco interrogaciones, es decir, 5 huecos donde no trabajamos, y sólo servirán para poder alinear los demás datos.
Vemos que las primeras interrogaciones están en BFC5E512 y es que lo próximo que tenemos que colocar es un entero, por ello nos desplazamos hasta BFC5E514. El siguiente byte problemático es justo después de un char, y lo siguiente será al final, porque se nos quedan algunos bytes “colgados” y no vamos a poder aprovechar.

64bit
7FFFB3652EF0 [e1]               (40 bytes)
7FFFB3652EF0 [e1.s1]            (2 bytes)
7FFFB3652EF4 [e1.i1]            (4 bytes)
7FFFB3652EF8 [e1.l1]            (8 bytes)
7FFFB3652F00 [e1.i2]            (4 bytes)
7FFFB3652F08 [e1.l2]            (8 bytes)
7FFFB3652F10 [e1.s2]            (2 bytes)
7FFFB3652F12 [e1.c1]            (1 bytes)
7FFFB3652F14 [e1.s3]            (2 bytes)

En la tabla acortaré las direcciones de memoria:

B3652EF0 e1.s1 e1.s1 ? ? e1.i1 e1.i1 e1.i1 e1.i1 B3652EF7
B3652EF8 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 e1.l1 B3652EFF
B3652F00 e1.i2 e1.i2 e1.i1 e1.i2 ? ? ? ? B3652F07
B3652F08 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 e1.l2 B3652F0F
B3652F10 e1.s2 e1.s2 e1.c1 ? e1.s3 e1.s3 ? ? B3652F17

De primeras vemos que en 64bit la estructura ocupa 40bytes en lugar de 31bytes… pero esta vez, vemos cómo a la hora de colocar un long int (8bytes) en B3652F04, se lo lleva a B3652F08, así será capaz de leerlo en una lectura a memoria (de la palabra entera de 8bytes) en lugar de 2 lecturas (de 4bytes cada una en dos palabras diferentes).

¿Cuándo debemos tener esto en cuenta?

Esto tampoco tiene mucha importancia si sólo vamos a almacenar una pequeña información en un registro, pero, si vamos a almacenar un array de muchos elementos de un registro, o una lista enlazada, podremos observar que si en el registro tenemos 9bytes que sólo sirven para hacer bulto, es decir, para conseguir alinear la información, en un millón de elementos, estaremos perdiendo cerca de 9Mb, nos puede ayudar a optimizar un poco la información que almacenamos.

Por otra parte, si lo que estamos haciendo es implementar una lectura/escritura de información en un formato determinado y, por ejemplo, tenemos:

  • 3 bytes de identificación
  • 4 bytes de control
  • 4 bytes tamaño
  • resto de archivo de información

No podremos crear un registro del tipo:

1
2
3
4
5
6
struct
{
  char identif[3];
  int  control;
  int  size;
} TFormato;

Ya que desde el último carácter hasta el entero de control habrá 1byte de diferencia y el formato no se respetará correctamente, por lo que seguramente haya algún fallo.

Esto tampoco quiere decir que no se pueda leer una variable que no esté alineada, siempre podemos hacerlo jugando con los bytes y trabajando un poco con el código, aunque las arquitecturas modernas lo permiten directamente con instrucciones especiales, eso sí, no se recomienda mucho su uso, ya que es más lento hacer dos peticiones a memoria, y luego trabajar con el resultado de las dos para unirlo; por otra parte, si trabajamos con un procesador que no disponga de estas instrucciones, tendremos que programar cómo hacemos las peticiones y la fusión de la información, aunque de eso se encargue el compilador, debemos tenerlo en cuenta si queremos que nuestro programa sea rápido.

Un pequeño ejemplo de un acceso no alineado

Si queremos provocar un acceso no alineado en C, podemos hacer lo siguiente: crear un array de char más o menos grande, y un puntero a un entero que apunte a una dirección dentro del array de char (con que apunte a la dirección base+1 vale para que no esté alineado.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main()
{
  unsigned char datos[6];
  int *punt;
  printf("%lX (%lu)\n", (unsigned long)&datos, sizeof(datos));
  punt=(int*)&datos[1];
  printf("%lX (%lu)\n", (unsigned long)punt, sizeof(*punt));
  *punt=123456789;
  printf("%d %d %d %d => %d %X <= %X %X %X %X\n", datos[1], datos[2], datos[3], datos[4], *punt, *punt, datos[4], datos[3], datos[2], datos[1]);
 
}

La salida es algo así (en 64bits, aunque sólo se nota en el tamaño de las direcciones de memoria):

7FFFC98BE8B0 (6)
7FFFC98BE8B1 (4)
21 205 91 7 => 123456789 75BCD15 <= 7 5B CD 15

Primero mostramos la dirección de memoria base del array y el tamaño del mismo, luego la dirección de memoria base de la variable entera con la que trabajaremos y su tamaño (muy importante el sizeof(*punt), sin el * nos daría el tamaño del puntero, y estamos en 64bit, es decir, sería de 8bytes.
Y tras ello, asignaremos al entero el valor 123456789, podremos ver el valor en decimal de cada uno de los bytes que componen el entero, el valor del entero, y luego el valor del mismo en hexadecimal acompañado de los valores en hexadecimal de cada uno de los bytes de antes.

Esto está diseñado para verse en un sistema little endian, en un sistema big endian, la última línea de salida sería:

7 91 205 21 => 123456789 75BCD15 <= 15 CD 5B 7

Foto: wwarby (Flickr)

Preincremento y postincremento (x++ != ++x)

Jueves, 27 de Enero de 2011 Gaspar Fernández 2 comentarios

Hace unos días un alumno de clases particulares me preguntó la diferencia entre estos dos; me pareció una pregunta interesante ya que los únicos usos que había visto eran como única sentencia:

a++;
++a;

En este uso no hay diferencia, puesto que hagamos las cosas en el orden que las hagamos el resultado será igual; aunque en este ejemplo tampoco se tiene clara la idea del orden de las operaciones. Pero veamos otro ejemplo:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, char *argv[])
{
  int a=10;
  int b;

  b=a++;
  printf("a=%d\nb=%d\n", a, b);
}

Vemos que estamos realizando un postincremento de a, o lo que es lo mismo, incrementamos la variable a después de las demás operaciones (asignar a b el valor de a).

Por lo tanto, si a vale 10, b tomará el valor 10 y tras eso a incrementará 1 pasando a valer 11.
Al ver la salida podemos observar:

$ ./prepost
a=11
b=10

En cambio podemos crear este programa también (igual que el anterior cambiando a++ por ++a),

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, char *argv[])
{
  int a=10;
  int b;

  b=++a;
  printf("a=%d\nb=%d\n", a, b);
}

Cuya salida es:

$ ./prepost2
a=11
b=11

En este caso, preincrementamos a. Antes de cualquier operación incrementamos la variable. Por tanto, si a vale 10, incrementamos esa variable pasando a valer 11 y luego asignamos ese valor a b.

La gran ventaja de esto es que con la experiencia suficiente nos ayuda a reducir el código que escribimos y optimizar la velocidad de nuestros programas. Además, a la hora de crear nuestros programas podremos ahorrar mucho código, sobre todo a la hora de lidiar con punteros de cadenas o imágenes donde, en ocasiones, podemos realizar tareas de forma más fácil.

Dejo algunos ejemplos más, aunque sencillos del uso de estos operadores:

Mareando un poco la perdiz

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, char *argv[])
{
  int a=10;
  int b=5;

  b=a++ + ++b;
  printf("a=%d\nb=%d\n", a, b);
}

Que da como salida:

$ ./prepost3
a=11
b=16

En este caso… cogemos a (que vale 10), luego incrementamos b (que valía 5 y pasa a valer 6), sumamos los dos valores (que da en total 16) y luego incrementamos a (que pasa a valer 11).

En un do-while

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main(int argc, char *argv[])
{
  int a;

  a=0;
  do
    printf("Bucle a=%d\n", a);
  while (++a<5);
}

Cuya salida es:

$ ./preposw
Bucle a=0
Bucle a=1
Bucle a=2
Bucle a=3
Bucle a=4

En este caso, al finalizar cada iteración, incrementamos a y luego miramos si es menor que 5; por lo que, en la última vuelta del bucle, si a vale 4, incrementaremos su valor (pasando a ser 5) y luego veremos que ya no es menor que 5 por lo que saldremos del bucle.

En contraposición podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main(int argc, char *argv[])
{
  int a;

  a=0;
  do
    printf("Bucle a=%d\n", a);
  while (a++<5);
}

Cuya salida es:

$ ./preposw
Bucle a=0
Bucle a=1
Bucle a=2
Bucle a=3
Bucle a=4
Bucle a=5

En este caso, al verificar que a < 5 antes de incrementar a. Cuando a vale 4, haremos la comparación, cuando todavía es menor que 5 e incrementaremos. Daremos otra vuelta más con a valiendo 5, donde, en la siguiente comparación (a<5) ahora sí que saldrá del bucle, aunque si verificáramos el valor de a tras salir del bucle sería 6.

Contar caracteres a mano

Un ejemplo tonto más, para contar las letras de una cadena:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i=0;
  char *str="HOLA";

  // Con esto vale
  while (str[++i]!='\0');

  printf("Longitud: %d\n", i);
  }

Salida:

$ ./prepos
Longitud: 4

En este caso, dentro del while, primero incrementamos i, luego comparamos str[i] con ‘\0′ y si efectivamente tiene ese valor saldremos del bucle. No se crea un bucle infinito porque el incremento se produce (aunque sea en la misma sentencia de la comparación).
Hay que tener cuidado con este ejemplo, ya que si la cadena está vacía (es decir, que el primer carácter que encontremos sea \0) tal vez este método produzca resultados inesperados, ya que nunca llegamos a comparar el carácter 0 (incrementamos antes de comparar ese carácter). Una posible solución es inicializar i a -1.

Nota: Todo lo comentado aquí vale tanto con preincremento/postincremento como con predecremento/postdecremento (–a, a–), ya que son operaciones muy similares.

Número variable de argumentos en una función de C

Lunes, 24 de Enero de 2011 Gaspar Fernández 3 comentarios

Si habéis programado en C, seguramente la primera función que aprendisteis a utilizar era printf() ; y en todo el tiempo que pasasteis estudiando C no encontrasteis una función similar. Y es que esta función puede tener un número indeterminado de parámetros y es capaz de analizarlos todos.

Y es que quitando printf(), scanf() y derivadas de estas, la necesidad de una función con múltiples parámetros casi no se nos presenta. Aunque tal vez, alguna vez necesitemos crear una función con parámetros opcionales, es decir, si están presentes se tomarán y si no, se tomará un valor por defecto.

Esto lo podemos hacer con la biblioteca stdarg.h la cual nos proporciona macros para hacer esto posible.

Echemos un vistazo al 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
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

void poneargs(int nargs, ...)
{
  va_list ap;
  int i;
  int num;

  va_start(ap, nargs);
  for (i =0; i<nargs; i++)
    {
      num=va_arg(ap, int);
      printf("Argumento %d = %d\n", i, num);
    }
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  poneargs(5, 6, 77, 445, 34, 23, 12, 43, 32);
  poneargs(2, 23, 94);
  poneargs(0);

  return EXIT_SUCCESS;
}

Aquí vemos cómo la función poneargs() nos coloca los valores de los argumentos que le hemos pasado, siendo el primero de ellos el número total de argumentos que tenemos.
Con esto, hay que dejar claro que no tenemos forma de contar los argumentos que pasamos (igual que printf, podíamos pasar tantos argumentos como %[x] tuviéramos en la cadena de formato). Así que las técnicas para llegar al límite de los comandos son:

  • Poner un argumento indicando el número de argumentos a colocar.
  • Poner un argumento con un valor fijo al final que nos indique que hemos llegado al último parámetro.
  • Poner códigos con los argumentos que tenemos que leer. Idea de printf()

Lo que sí que tenemos que tener en cuenta es que, como estamos acostumbrados cada argumento tiene un tipo fijo, y se lo tenemos que decir a va_arg() en su segundo argumento; y va_arg() nos va a devolver una variable del mismo tipo que le dijimos (éste es el valor del argumento).

En el siguiente ejemplo creamos una función new_cliente() que puede tener hasta cuatro argumentos de tipo cadena de caracteres. De hecho son opcionales. Cuando creamos un cliente en un programa real puede que no tengamos todos sus datos y puede que algunos sean opcionales:

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
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

void new_cliente(int nargs, ...)
{
  va_list ap;

  char *valor;

  va_start(ap, nargs);

  switch (nargs)
    {
    case 4:
      valor=va_arg(ap, char*);
      printf("Twitter: %s\n", valor);      
    case 3:
      valor=va_arg(ap, char*);
      printf("URL: %s\n", valor);      
    case 2:
      valor=va_arg(ap, char*);
      printf("Blog: %s\n", valor);      
    case 1:
      valor=va_arg(ap, char*);
      printf("Nombre: %s\n", valor);
      break;
    default:
      printf("Número de parámetros incorrecto\n");
    }
 
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  printf("Un parámetro:\n");
  new_cliente(1, "Gaspy");

  printf("Dos parámetros:\n");
  new_cliente(2, "Gaspy", "Poesía Binaria");

  printf("Tres parámetros:\n");
  new_cliente(3, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/");

  printf("Cuatro parámetros:\n");
  new_cliente(4, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/", "http://twitter.com/blakeyed");

  return EXIT_SUCCESS;
}

Esto también podría valernos como una pequeña simulación de sobrecarga de funciones (estilo C++) aunque en C no cumpliría la tarea de hacer más legible el código ya que en la misma función tendríamos que incluir las opciones de los argumentos.

El método más rápido para traer un valor pasado por $_GET [ PHP ]

Miércoles, 15 de Diciembre de 2010 Gaspar Fernández 1 comentario

Tal vez por paranoia, por ganas de perder el tiempo, o por optimizar aún más el código fuente nos encontremos ante esta cuestión.

He realizado una serie de pruebas para ver cuál es el método que mejor funciona para traer un valor de $_GET basándome en el peor de los casos: cuando este valor no existe. Tal vez las pruebas no tengan excesiva validez por el método utilizado, aunque he intentado hacer todas las pruebas en las mismas condiciones.

Las medidas de tiempo han sido tomadas con el comando time de la siguiente manera:

$ time php myscript.php

Se han hecho un total de 7 pruebas, 1 de ellas de control. Las pruebas se han basado en la generación de cadenas aleatorias (que más tarde serán nuestro parámetro $_GET), además, la obtención del parámetro ha sido hecha unas 500000 veces con un bucle for. Se detallan las pruebas realizadas.
Cada prueba se ha repetido tres veces y se ha capturado el tiempo de las tres veces. Estamos usando un software complicado que hará muchas cosas mientras se realiza el test, es más, el sistema operativo puede requerir hacer una tarea que puede influir en nuestro resultado, por lo que así minimizamos el error (y si en alguna ocasión sale un dato desorbitado podemos descartarlo).

Prueba de control [TEST 0]

La generación de parámetros es lo que más tarda. Se ha utilizado uniqid() para obtener cadenas de la misma longitud y contenido arbitrario y diferente cada vez. Además, tanto la obtención del parámetro (en las siguientes pruebas) como la generación del nombre del parámetro se hace un número N de veces gracias a un bucle (que también invierte un tiempo).

Con esta prueba de control podremos ver en qué grado son significativos los valores obtenidos luego (cuando hagamos la obtención de datos), ya que vemos que el bucle y la generación tardan de media 4.493 segundos.

El código fuente del script para esta prueba es el siguiente:

1
2
3
4
5
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
}

Anulando error_reporting() [TEST 1]

Lo que nos impide hacer un $variable=$_GET['parametro']; es el error_reporting, ya que si éste no existe, nos devolverá un feo error por pantalla. Así que probemos primero desactivando esto y obteniendo el valor de la variable.

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";
error_reporting(E_NONE);
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=$_GET[$getVar];
}

Con @$_GET[...] [TEST 2]

Otra manera de impedir que PHP muestre sus errores en pantalla, es colocando una @ delante de lo que creemos que puede fallar, y en este caso es la sentencia de $_GET[$getVar].

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";

for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=@$_GET[$getVar];
}

if…else [TEST 3]

No es nada intuitivo, PHP es un lenguaje de scripting, así que cuanto más escribamos, peor, pero merece la pena hacer la prueba, y nos podemos llevar una sorpresa. Ahora, hacemos la obtención del dato, pero primero comprobamos que éste existe con isset().

1
2
3
4
5
6
7
8
9
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
if (isset($_GET[$getVar]))
$dato=@$_GET[$getVar];
else
$dato=false;
}

Operador ternario ? [ TEST 4 ]

Tal vez vaya mejor que el if…else (TEST 3), por aquella razón de escribir menos que comentaba…

1
2
3
4
5
6
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=(isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

Creando una función auxiliar gval() [ TEST 5 ]

Tal vez escribimos más que con el TEST 2 pero, vamos a probar, qué tal va. Tal vez si la diferencia de tiempo no es significativa podemos utilizar este método…

1
2
3
4
5
6
7
8
9
10
11
function gval($getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Función auxiliar y parámetro por referencia [ TEST 6 ]

Una nueva prueba, ya que hemos hecho una función auxiliar, veamos cómo influye el pasar el parámetro por valor o pasarlo por referencia. En este caso podríamos hacerlo sin problema.

1
2
3
4
5
6
7
8
9
10
11
function gval(&amp;$getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Resultados obtenidos

Tipo de prueba Primer resultado Segundo resultado Tercer Resultado Media
TEST 0 4,4530 4,5960 4,4310 4,4933
TEST 1 6,7560 6,1390 5,8350 6,2433
TEST 2 7,3880 7,3350 7,3120 7,3450
TEST 3 5,5270 4,9900 5,0610 5,1927
TEST 4 5,4480 4,9820 5,1060 5,1787
TEST 5 6,2860 6,4050 5,8160 6,1690
TEST 6 5,9840 5,6290 5,7440 5,7857

Con estos resultados podemos observar que:

  • Efectivamente, el test de control es el más lento, incluye generación del nombre aleatorio del parámetro, el bucle y la carga del programa (PHP), pero más o menos partimos de que el tiempo mínimo para esta prueba son cerca de 4.5 segundos. El tiempo de obtención de dato, será más o menos la diferencia entre el tiempo total del test y los 4.5 segundos aproximadamente obtenidos aquí.
  • TEST 1: error_reporting(E_NONE) nos entorpecería en tareas de depuración (cuando realmente deseemos tenerlo en otro valor), de todas formas no es una opción, comparado con los demás. Deberíamos estar pendientes del valor de error_reporting() cuando queramos depurar el script, no ahorraríamos mucho código y tampoco es el mejor con respecto al tiempo.
  • TEST 2: Estaría bien que un carácter nos solucionara la vida, pero no es así… conseguimos el peor tiempo de todos. Se ve que a PHP le sienta mal cuando no encuentra una variable…
  • TEST 3 y TEST 4: Parece que un if…else nos puede ayudar a hacer nuestro código más rápido, un poco más elegante y funcionar mejor. También vemos que el operador ? puede ayudar. Y vemos, además, que el tiempo entre if…else y entre ? es el mismo (la diferencia es despreciable en este caso)
  • TEST5: Visto lo visto con TEST3 y TEST4 en los que escribimos más que con TEST2, vamos a crear una función auxiliar, a ver cómo se comporta con respecto a lo que tenemos. Comprobamos que el uso de una función, hace nuestro código más claro (punto positivo) y aún tarda menos que TEST1 y TEST2 aunque tarda algo más que TEST3 y TEST4. TEST5 tarda aproximadamente lo que TEST1.
  • TEST6: Parece que el hecho de no copiar el valor de una variable de verdad influye significativamente en el tiempo cerca de medio segundo; podemos seguir utilizando un método elegante con nuestra función auxiliar y al mismo tiempo aproximarnos a un tiempo mínimo.
  • Sé que 500000 iteraciones puede parecer exagerado aunque en momentos de carga de servidor y cuando esperamos que nuestra página tenga un número importante de visitas tal vez se agradezca un poco de agilidad, sobre todo en recepción de formularios (con variables $_POST). De todas formas, queda claro con estas pruebas que el hecho de poner una @ en un comando o asignación, sólo hace que PHP se trague los errores, pero es un método lento, con lo que podemos empezar a optimizar nuestro código por ahí.

Compilando y linkando a mano con GCC

Sábado, 11 de Septiembre de 2010 Gaspar Fernández Sin comentarios

GCC compila y linka automáticamente, nos devuelve un ejecutable directamente:

$ gcc -o ejecutable fuente.c

pero en realidad, aparte de pre-procesar y compilar, enlaza algunas bibliotecas del sistema para que nuestro ejecutable funcione bien. Sólo por jugar un poco, veamos, más o menos (depende del sistema) cómo obtener el ejecutable a mano, es decir, compilamos por un lado, y linkamos por otro.

Primero, creamos un programa sencillo, un hello.c que contenga lo siguiente:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char *argv[])
{
   printf("Hola mundo!!\n");

   return 0;
}

Ahora obtenemos el fichero objeto, nuestro código compilado de la siguiente manera:

$ gcc -c hello.c

Veremos que se ha generado el fichero hello.o ; éste fichero aún no lo podremos ejecutar, ya que no tenemos las librerías del sistema necesarias para ello.

Ahora obtendremos el fichero ejecutable con el linkador:

ld -o hello -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtbegin.o -L/usr/lib/gcc/i686-pc-linux-gnu/4.2.3 hello.o /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtend.o /usr/lib/crtn.o -lc

Debemos cambiar /usr/lib/gcc/i686-pc-linux-gnu/4.2.3 por la ruta correspondiente en nuestro equipo; los archivos necesarios se encontrarán en en el directorio de GCC, tras seleccionar la arquitectura y SO correspondiente (en mi caso i686-pc-linux-gnu), y dentro de ese directorio encontraremos otro correspondiente a la versión de GCC (en mi caso la 4.2.3).

Más o menos lo que vemos en el proceso de linkado es lo siguiente:

  • -o hello : es el nombre del ejecutable
  • -m elf_i386 : es el tipo de ejecutable y la arquitectura. También puede ser elf_x86_64 si tenemos las bibliotecas necesarias.
  • -dynamic-linker /lib/ld-linux.so.2: es el cargador dinámico
  • /usr/lib/crt1.o /usr/lib/crti.o: son dos objetos encargados de la inicialización de memoria del programa
  • /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtbegin.o : indica cómo tiene que empezar el programa.
  • /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtend.o : indica cómo tiene que terminar el programa
  • hello.o : es el objeto de nuestro programa.
  • -L/usr/lib/gcc/i686-pc-linux-gnu/4.2.3 : hacemos que el linkador busque bibliotecas en el directorio indicado.
  • /usr/lib/crtn.o : realiza tareas de limpieza de memoria.
  • -lc: incluye la librería de C estándar.

Los archivos que empiezan por crt* normalmente se encargan de poner los datos que nos entran en su sitio; parece que el hecho de coger los parámetros que nos pasan por la línea de comandos y colocarlos en memoria para que nuestro programa los lea es tarea fácil, y que luego nuestro programa devuelva una respuesta; y además, los programas no se ejecutan siempre en la misma posición de memoria, depende de la memoria que haya libre, eso lo suele controlar el S.O. pero nuestros ejecutables tienen que estar preparados… en definitiva, hay muchos agentes involucrados.

Una vez hecho esto, tenemos nuestro hola mundo preparado para ser ejecutado.

Debo agradecer a Carlos, que me dio la idea de escribir este artículo.

Cosas que damos por hechas en C/C++: int main(int argc, char *argv[])

Martes, 27 de Abril de 2010 Gaspar Fernández Sin comentarios

Esta serie de posts está dedicada a tod@s mis alumn@s de clases particulares de programación. Iré añadiendo información de diferentes niveles, dificultades, colores y sabores. Espero que les parezca interesante.

Fue en el primer año de carrera, cuando en la asignatura Fundamentos de Programación y luego en Laboratorio de Programación cuando empezábamos a hacer los primeros programas en C; yo hasta entonces no me había atrevido a programar nada en C, prefería Pascal, al menos llegué con una buena base de programación.

El IDE que utilizábamos (Dev-C++) directamente escribía las primeras líneas de programa:

#include

using namespace std;

int main(int argc, char *argv[])
{

}

Comprendo que en 4 meses hay que enseñar a mucha gente a defenderse con la programación, y no es objeto explicarlo todo detalladamente, aunque cuando alguien preguntaba siempre decían: “Eso hay que ponerlo siempre” es más, nada más empezar el curso, poca gente se va a enterar de qué va el asunto

Bien, empezamos por el include (vale, esto sí que lo contaban mis profesores, porque a veces teníamos que incluir alguna cosa más), incluir funciones y clases hechas por otras personas, y no nos tiene que importar cómo están hechas por dentro.

Un concepto más peliagudo y extenso es el de la línea siguiente: using namespace std (nos metemos a pelear con los espacios de nombres; no quiero ser demasiado extenso, pero imaginemos que hay dos casas en una calle, y cada casa aloja a tres personas: Jose, Virginia y Antonio en la casa A; y Alfonso, Manuela y Jose en la casa B. ¿Cómo distinguimos a Jose de la casa A de Jose de la casa B? Suena a pregunta tonta: Pues casaA::Jose y casaB::Jose, son dos personas diferentes.
Lo mismo podemos hacer en C++, podemos tener funciones que se llaman igual en espacios de nombres (o casas) diferentes, y para referirnos a ellos tenemos que decir primero en qué casa están, para que luego el compilador sepa ubicar a cada uno.
Pero como las pulsaciones de teclado de los desarrolladores son oro (sabemos que tenemos que escribir mucho, si podemos ahorrar algo mejor), y sabemos que muchas de las clases y funciones que vamos a utilizar pertenecen a la librería estándar (o std), es decir, estarán en la mansión std decimos:
using namespace std;
o lo que es lo mismo: si no te digo nada, y llamo a alguien que no encuentras, búscalo en std.

Llegamos al main(), podemos definirlo de muchas formas: muchos utilizan void main() (y gcc se queja), otros int main() (que es perfectamente válido), pero algunas veces se introduce toda esa parafernalia rara de int argc, char *argv[], que es impronunciable, y hace daño a la vista, e incluso si lo intentamos escribir las primeras veces, no nos saldrá igual.
Estos parámetros son para los argumentos del programa, es decir, para los parámetros que introduzcamos en la línea de comandos. Para muchos usuarios que vienen de Windows, les resulta raro entender por qué un programa requiere parámetros, pues bien: si abrimos Mi PC, nos vamos a una carpeta y abrimos un documento, cómo sabe Microsoft Office que hemos abierto este documento? Se lo hemos pasado como parámetro sin querer :)
Ahora bien, y esto requiere un poco de imaginación si estamos empezando:
argc es un número que indica el número de parámetros que tiene el ejecutable:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char *argv)
{
  printf("Me has pasado: %d parámetros\n", argc);

  return 0;
}

Veamos qué pasa al ejecutarlo:

$ ./params
Me has pasado: 1 parámetros
$ ./params param1 param2 “parametro 3″
Me has pasado: 4 parámetros
$ ./params param1 param2 “parametro 3″ “parametro 4″
Me has pasado: 5 parámetros

Nos sobra uno no? Veamos cuáles son esos parámetros:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;

  printf("Me has pasado: %d parámetros\n", argc);

  for (i=0; i<argc; i++)
    {
      printf ("Parámetro %d: %s\n",i,argv[i]);
    }

  return 0;
}

Salida:

$ ./params param1 param2 “parametro 3″ “parametro 4″
Me has pasado: 5 parámetros
Parámetro 0: ./params
Parámetro 1: param1
Parámetro 2: param2
Parámetro 3: parametro 3
Parámetro 4: parametro 4

Vemos que el parámetro 0 es el propio nombre del ejecutable y comprobamos que si cambiamos el nombre del ejecutable, podemos saberlo con ese parámetro 0. Los demás podemos averiguarlos recorriendo la variable argv. argv es un Array de punteros de tipo char o lo que es lo mismo un array de cadenas de caracteres puesto que cada parámetro es una cadena de caracteres.

Ahora la pregunta del millón, si utilizamos un array, para qué necesitamos argc ¿? Podríamos averiguar el número de elementos del mismo. La respuesta es que no, de ese Array desconocemos el número de elementos que tiene (depende de lo que nos pase el usuario), y si tenemos un método para calcular el número de parámetros que tenemos, que seguro que podremos encontrarlo, será mucho más rápido disponer de ese valor (y escribimos menos)

Visita otras webs de la red