estructuras - dsi.fceia.unr.edu.ar · 1 estructuras una estructura es una colección de una o más...

31
1 Estructuras Una estructura es una colección de una o más variables de tipos posiblemente diferentes, agrupados bajo un solo nombre para un manejo conveniente. Las estructuras se conocen como records” en algunos otros lenguajes, principalmente Pascal. Las estructuras ayudan a organizar datos complicados, en particular dentro de programas grandes debido a que, le permiten a un grupo de variables relacionadas tratarlas como una unidad en lugar de hacerlo como entidades separadas. Un ejemplo tradicional de estructura es el registro de una nómina: un empleado está descripto por un conjunto de atributos tales como nombre, domicilio, número de CUIL, salario, etc. Algunos de estos atributos pueden, a su vez, ser estructuras: un nombre tiene varios componentes, como los tiene un domicilio y, aún un salario. Otro ejemplo más típico en C procede de las gráficas: un punto es un par de coordenadas, un rectángulo es un par de puntos y, otros casos semejantes. El principal cambio realizado por el estándar ANSI C es la definición de la asignación de estructuras – las estructuras se pueden copiar y asignar, pasar a funciones y ser regresadas por ellas. Esto se manejó así por muchos compiladores durante varios años pero, las propiedades están ahora definidas en forma precisa. Las estructuras y los arreglos automáticos ahora también pueden inicializarse. Conceptos básicos sobre estructuras Definamos algunas estructuras propias para graficación. El objeto básico es un punto, del cual supondremos que tiene una coordenada x y, una coordenada y, ambas enteras. Los dos componentes pueden colocarse en una estructura declarada así: struct punto{ int x; int y; }; La palabra reservada struct presenta la declaración de una estructura, que es una lista de declaraciones entre llaves. Un nombre optativo, llamado rótulo de estructura, puede seguir a la palabra struct (como aquí lo hace punto). El rótulo da nombre a esta clase de estructura y, en adelante, puede utilizarse como una abreviatura para la parte de declaraciones entre llaves. Las variables nombradas dentro de la estructura se llaman miembros. Un miembro de estructura o rótulo y una variable ordinaria (esto es, no miembro) pueden tener el mismo nombre sin conflicto

Upload: others

Post on 09-Oct-2019

15 views

Category:

Documents


0 download

TRANSCRIPT

1

Estructuras

Una estructura es una colección de una o más variables de tipos posiblemente diferentes,

agrupados bajo un solo nombre para un manejo conveniente. Las estructuras se conocen como

“records” en algunos otros lenguajes, principalmente Pascal. Las estructuras ayudan a organizar

datos complicados, en particular dentro de programas grandes debido a que, le permiten a un

grupo de variables relacionadas tratarlas como una unidad en lugar de hacerlo como entidades

separadas.

Un ejemplo tradicional de estructura es el registro de una nómina: un empleado está descripto por

un conjunto de atributos tales como nombre, domicilio, número de CUIL, salario, etc. Algunos de

estos atributos pueden, a su vez, ser estructuras: un nombre tiene varios componentes, como los

tiene un domicilio y, aún un salario. Otro ejemplo más típico en C procede de las gráficas: un

punto es un par de coordenadas, un rectángulo es un par de puntos y, otros casos semejantes.

El principal cambio realizado por el estándar ANSI C es la definición de la asignación de estructuras

– las estructuras se pueden copiar y asignar, pasar a funciones y ser regresadas por ellas. Esto se

manejó así por muchos compiladores durante varios años pero, las propiedades están ahora

definidas en forma precisa. Las estructuras y los arreglos automáticos ahora también pueden

inicializarse.

Conceptos básicos sobre estructuras

Definamos algunas estructuras propias para graficación. El objeto básico es un punto, del cual supondremos que tiene una coordenada x y, una coordenada y, ambas enteras.

Los dos componentes pueden colocarse en una estructura declarada así:

struct punto{ int x; int y; };

La palabra reservada struct presenta la declaración de una estructura, que es una lista de

declaraciones entre llaves. Un nombre optativo, llamado rótulo de estructura, puede seguir a la

palabra struct (como aquí lo hace punto). El rótulo da nombre a esta clase de estructura y, en

adelante, puede utilizarse como una abreviatura para la parte de declaraciones entre llaves.

Las variables nombradas dentro de la estructura se llaman miembros. Un miembro de estructura o

rótulo y una variable ordinaria (esto es, no miembro) pueden tener el mismo nombre sin conflicto

2

puesto que, siempre se pueden distinguir por el contexto. Además, en diferentes estructuras

pueden encontrarse los mismos nombres de miembros, aunque por cuestiones de estilo se

deberían de usar los mismos nombres sólo para objetos estrechamente relacionados.

Una declaración struct define un tipo. La llave derecha que termina la lista de miembros puede

ser seguida por una lista de variables, como se hace para cualquier tipo básico, Esto es,

struct punto{...}x, y, z;

Es sintácticamente análogo a

int x, y, z;

En el sentido de que cada proposición declara a x,y y z, como variable del tipo nombrado y

causa que se les reserve espacio contiguo.

Una declaración de estructura que no está seguida por una lista de variables no reserva espacio de

almacenamiento sino que, simplemente describe una plantilla o la forma de una estructura. Sin

embargo, si la declaración está rotulada, el rótulo se puede emplear posteriormente en

definiciones de instancias de la estructura. Por ejemplo, dada la declaración anterior de punto,

struct punto pt;

Define una variable pt que es una estructura de tipo struct punto. Una estructura se puede

inicializar al seguir su definición con una lista de inicializadores, cada uno mediante una expresión

constante, para los miembros:

struct punto maxpt={320, 200};

Se hace referencia a un miembro de una estructura en particular, en una expresión, con una

construcción de la forma: nombre-estructura.miembro.

El operador miembro de estructura “.” conecta al nombre de la estructura con el nombre del

miembro. Por ejemplo, para imprimir las coordenadas del punto pt,

printf("%d, %d", pt.x, pt.y);

O para calcular la distancia del origen (0,0) a pt,

double dist, sqrt(double); dist=sqrt((double)pt.x * pt.x + (double) pt.y * pt.y);

3

Una estructura automática también se puede inicializar por asignación o llamando a una función

que regresa una estructura del tipo adecuado.

// Estructura arbitraria que consume // un cantidad de memoria no trivial typedef struct { int x; int y; char *z; int a[4]; }initStruct; static initStruct staticStruct = {1,2,"Hello", {3,4,5,6}}; // initAuto es una estructura "readonly" usada para inicializar // autoStruct, se inicializa en tiempo de compilación //si las variables de tipo struct no necesitan reinicializarse cada vez que son //usadas conviene declararlas static static initStruct initAuto = {7,8,"World",{9,10,11,12}}; /* Si necesito crear una variable de tipo estructura automática puedo inicializarla con los valores de la estática recién creada (este tipo de variables se inicializa en tiempo de ejecución, para ello se genera una secuencia de asignaciones campo a campo, esta operación puede ser relativamente rápida pero puede consumir bastante memoria en caso de estructuras muy grandes), esta es una forma de reducir el tamaño de memoria usada en la inicialización de una estructura automática*/ initStruct autoStruct = initAuto;

Las estructuras pueden anidarse. Una representación de un rectángulo puede definirse mediante

un par de puntos que denotan las esquinas diagonalmente opuestas:

struct rect{ struct punto pt1; struct punto pt2; };

La estructura rect contiene dos estructuras punto. Si declaramos pantalla como:

struct rect pantalla;

4

Entonces,

pantalla.pt1.x

Se refiere a la coordenada x del miembro pt1 de pantalla.

Estructuras y funciones

Las únicas operaciones legales sobre una estructura son: copia o asignación como unidad, tomar

su dirección con & y tener acceso a sus miembros. La copia y la asignación incluyen pasarlas como

argumentos a funciones y también regresar valores de funciones. Las estructuras no se pueden

comparar. Una estructura se puede inicializar con una lista de valores constantes de miembros;

una estructura automática también se puede inicializar con una asignación.

Investiguemos las estructuras escribiendo algunas funciones para manipular puntos y rectángulos.

Hay por lo menos tres acercamientos posibles: pasar separadamente los componentes, pasar una

estructura completa o pasar un puntero a ella. Cada uno tiene sus puntos buenos y malos.

La primera función, creapunto, toma dos enteros y regresa una estructura punto:

/*creapunto: crea un punto con coordenadas x e y */ struct punto creapunto (int x, int y) { struct punto temp; temp.x=x; temp.y=y; return temp; }

Nótese que no hay conflicto entre el nombre del argumento y el miembro con el mismo nombre;

incluso la reutilización de los nombres refuerza el vínculo.

creapunto ahora se puede usar para inicializar dinámicamente cualquier estructura o, para

proporcionar los argumentos de la estructura a una función:

struct rect pantalla; struct punto medio; struct punto creapunto(int, int); pantalla.pt1=creapunto(0,0); pantalla.pt2=creapunto(XMAX,YMAX); medio=creapunto((pantalla.pt1.x + pantalla.pt2.x)/2,(pantalla.pt1.y+pantalla.pt2.y)/2);

El siguiente paso es un conjunto de funciones para hacer operaciones aritméticas sobre los

puntos.

5

Por ejemplo,

/*sumapuntos: suma dos puntos*/ struct punto sumapuntos(struct punto p1, struct punto p2) { p1.x+=p2.x; p1.y+=p2.y; return p1; }

Aquí, tantos los argumentos como el valor de retorno son estructuras. Incrementamos los

componentes en p1, en lugar de utilizar explícitamente una variable temporal, para hacer énfasis

en que los parámetros de la estructura son pasados por valor como cualquier otro.

Como otro ejemplo, la función enrectangulo prueba si un punto está dentro de un rectángulo,

donde hemos adoptado la convención de que un rectángulo incluye sus lados izquierdo e inferior

pero no sus lados superior y derecho:

/*enrectangulo: regresa 1 si p está en r, 0 sino lo está*/ int enrectangulo (struct punto p, struct rect r) { return p.x>=r.pt1.x && p.x<r.pt2.x && p.y >=r.pt1.x && p.y<r.pt2.y; }

Esto supone que el rectángulo está representado en una forma estándar en donde las

coordenadas pt1 son menores que las coordenadas pt2. La siguiente función regresa un

rectángulo, garantizando que está en forma canónica:

#define min(a,b) ((a) < (b) ? (a) : (b)) #define max(a,b) ((a) > (b) ? (a) : (b)) /*canonrect: pone en forma canónica las coordenadas de un rectángulo*/ struct rect canonrect(struct rect r) { struct rect temp; temp.pt1.x=min(r.pt1.x, r.pt2.x); temp.pt1.y=min(r.pt1.y, r.pt2.y); temp.pt2.x=max(r.pt1.x, r.pt2.x); temp.pt2.x=max(r.pt1.x, r.pt2.x);

return temp; }

Si una estructura grande va a pasarse como argumento a una función, generalmente es más

eficiente pasar un puntero que copiar la estructura completa. Los punteros a estructuras se

comportan como los punteros a variables ordinarias.

6

La declaración

struct punto *pp;

Dice que pp es un puntero a una estructura de tipo struct punto. Si pp apunta a una estructura

punto, *pp es la estructura y (*pp).x y (*pp).y son los miembros. Para emplear pp, se podría

escribir, por ejemplo,

struct punto origen, *pp; pp=&origen; printf("El origen es (%d, %d)\n", (*pp).x, (*pp).y);

Los paréntesis son necesarios en (*pp).x debido a que la precedencia del operador miembro de

estructura “.”, es mayor que la de *. La expresión *pp.x significa *(pp.x), lo cual es ilegal

debido a que x no es un puntero.

Los punteros a estructuras se usan con tanta frecuencia que se ha proporcionado una notación

alternativa como abreviación. Si p es un puntero a estructura, entonces

p->miembro de estructura

se refiere al miembro en particular. (El operador -> es un signo menos seguido inmediatamente

por >). De esta manera podríamos haber escrito

printf("El origen es (%d, %d)\n", pp->x, pp->y);

Tanto el operador “.”, como el “->” se asocian de izquierda a derecha, de modo que si tenemos

struct rect r, *rp=r;

Entonces estas cuatro expresiones son equivalentes:

r.pt1.x (r.pt1).x rp->pt1.x (rp->pt1).x

Los operadores de estructuras “.” y, “ ->”, junto con () para llamadas a funciones y [] para

subíndices, están arriba de la jerarquía de precedencias y se asocian estrechamente. Por ejemplo,

dada la declaración

struct { int len; char *str; }*p;

7

Entonces

++p->len

Incrementa a len, no a p, puesto que los paréntesis implícitos son ++(p->len). Los paréntesis

se pueden emplear para alterar la asociación: (++p)->len incrementa a p, antes de tener acceso

a len, y (p++)->len incrementa a p después del acceso (Este último conjunto de paréntesis es

innecesario).

De la misma manera, *p->str obtiene cualquier cosa a la que str apunte; *p->str++

incrementa a str después de acceder a lo que apunta (exactamente como *s++); (*p->str)++

incrementa cualquier cosa a la que str apunte; y *p++->str incrementa a p después de acceder

a lo que str apunta.

Pueden ver un ejemplo de uso de punteros y estructuras en

http://c.learncodethehardway.org/book/ex16.html,

http://www2.elo.utfsm.cl/~lsb/elo320/labs/tiposbasicos/estructuras_fechas.c y, otro de uso de

las dos notaciones posibles para acceder a los miembros de la estructura a través de punteros

(extraído de http://iie.fing.edu.uy/ense/asign/str/curso-c):

#include <stdio.h> #include <math.h> struct punto { int x; int y; }; struct rectangulo { struct punto pto_a; struct punto pto_b; }; double area (struct rectangulo *); double per (struct rectangulo *); void main() { struct rectangulo r1 = { 3, 4, 5, 6 }; printf("\n\nESTRUCTURAS: rectangulo (pto_a, pto_b)\n"); printf("Punto pto-a: x=%d, y=%d\n", r1.pto_a.x, r1.pto_a.y); printf("Punto pto-b: x=%d, y=%d\n", r1.pto_b.x, r1.pto_b.y); printf("Area del rectangulo: %f\n", area( &r1 )); printf("Perimetro del rectangulo: %f\n\n", per( &r1 )); getchar(); }

8

/* calculo del area */ double area(struct rectangulo *p) { return ( abs ((*p).pto_b.x - (*p).pto_a.x ) * abs( (*p).pto_b.y - (*p).pto_a.y ) ); } /* calculo del perimetro */ double per(struct rectangulo *p) { return ( 2 * ( abs (p->pto_b.x - p->pto_a.x ) + abs( p->pto_b.y - p->pto_a.y ) ) ); }

Arreglos de estructuras

Consideremos escribir un programa, extraído de

http://www.ib.cnea.gov.ar/~servos/CursoC/estructuras.htm, para mantener la información

necesaria para describir un intervalo de la recta real y, la cantidad de cuentas que caen en ese

intervalo como para armar un histograma.

#include <stdlib.h> #include <stdio.h> struct Intervalo { double Lower, /* Límite inferior del intervalo */ Upper; /* Límite superior del intervalo */ int Counter; /* Cantidad de puntos dentro */ }; #define NUM_INTER 100 main() { struct Intervalo Inter[NUM_INTER]; int Index; double Delta = RAND_MAX/NUM_INTER; /* Inicialización del vector de estructuras */ for (Index=0; Index<NUM_INTER; Index ++) { Inter[Index].Counter = 0; Inter[Index].Lower = Delta*Index; Inter[Index].Upper = Delta*(Index+1); } for (Index=0; Index<10000; Index++) Inter[(int)(rand()/Delta)].Counter++; /* impresión de resultados */ for (Index=0; Index<NUM_INTER; Index++) printf("[%lf,%lf]=%d\n", Inter[Index].Lower, Inter[Index].Upper, Inter[Index].Counter);}

9

La declaración struct Intervalo Inter[NUM_INTER] define un arreglo Inter de

estructuras de tipo Intervalo y reserva espacio de almacenamiento para 100 elementos. Cada

elemento del arreglo es una estructura. Esto también se podría escribir como

struct Intervalo { double Lower, /* Límite inferior del intervalo */ Upper; /* Límite superior del intervalo */ int Counter; /* Cantidad de puntos dentro */ } Inter[NUM_INTER];

Veamos este otro ejemplo:

#include <stdio.h> #include <stddef.h> struct Student { int id; char name[32]; }; int main() { struct Student s[] = {{101, "John"},{102, "Doe"},{103, "Peter"}}; int i, num; num = sizeof(s)/sizeof(struct Student); printf("El numero de elementos del vector es: %d\n", num); for(i=0; i<num; i++) { printf("s[%d].id = %d\n", i, s[i].id); printf("s[%d].name = %s\n", i, s[i].name); } return 0; }

Como puede verse la inicialización del arreglo Student es exactamente análogo al de cualquier

otro tipo, es decir, la definición seguida por una lista de inicializadores entre llaves:

struct Student s[] = {{101, "John"},{102, "Doe"},{103, "Peter"}};

Los inicializadores se listan en parejas correspondientes a los miembros de las estructuras. Las

llaves internas no son necesarias cuando los inicializadores son variables simples o cadenas de

caracteres y, cuando están todos presentes:

struct Student s[] = {101, "John",102, "Doe",103, "Peter"};

Otro ejemplo más cercano al hardware, el de un arreglo de datos asociados a determinados sensores de presión:

struct SENSOR_DESC { unsigned char gain ; unsigned char offset ; unsigned char temp_coeff ; unsigned char span ; unsigned char amp_gain ; };

10

Que podemos agrupar en arreglos de sensores que comparten esta información e inicializarlos con valores hexadecimales:

struct SENSOR_DESC sensor_database[4] = { {0x20,0x40,0x50,0xA4,0x21}, {0x33,0x52,0x65,0xB4,0x2F}, {0x30,0x50,0x48,0xC4,0x3A}, {0x32,0x56,0x56,0xC5,0x28} } ;

Como es usual, el número de entradas en un arreglo se calculará si los inicializadores están

presentes y el [ ] se deja vacío. El número de elementos del arreglo puede, en el ejemplo de los

estudiantes, contarse manualmente pero es mucho más fácil y seguro que lo haga la máquina,

especialmente si la lista de estudiantes está sujeta a cambios.

El tamaño en bytes de un arreglo está completamente determinado en tiempo de compilación, el

mismo es el tamaño de una entrada multiplicado por el número de elementos que contiene, así

que el número de elementos es, para este ejemplo,

Tamaño de s / tamaño de struct Student

C proporciona un operador unario en tiempo de compilación llamado sizeof que se puede

emplear para calcular el tamaño de cualquier objeto. Las expresiones

sizeof objeto y sizeof (nombre de tipo)

dan un entero igual al tamaño en bytes del tipo u objeto especificado. Estrictamente, sizeof

produce un valor entero sin signo cuyo tipo size_t está definido en el archivo de cabecera

<stddef.h>. Un objeto puede ser una variable, arreglo o estructura. Un nombre de tipo puede

ser el de un tipo básico como int o double o, un tipo derivado como una estructura o un

puntero.

En nuestro caso el número de elementos es el tamaño del arreglo dividido entre el tamaño de un

elemento. Este cálculo se puede usar en una proposición #define:

#define NUMELEMEN (sizeof s/sizeof (struct Student))

Otra forma de calcularlo es dividiendo el tamaño del arreglo por el tamaño de un elemento

específico:

#define NUMELEMEN (sizeof s / sizeof s[0])

Esto tiene la ventaja de que no necesita modificarse si el tipo cambia.

11

Punteros a estructuras

Para ilustrar algunas de las consideraciones involucradas con apuntadores y arreglos de

estructuras, veamos el ejemplo de uso de estructuras para manejo de matrices (arreglos de dos

dimensiones) usando arreglos de una dimensión, extraído de

http://www.ic.unicamp.br/~ra069320/PED/MC102/1s2008/Apostilas/Cap08.pdf y modificado:

#include <stdio.h> #include <stdlib.h> struct matriz { int lin; int col; int* v; }; struct matriz* crea (int m, int n) { struct matriz* mat = (struct matriz*) malloc(sizeof(struct matriz)); mat->lin = m; mat->col = n; mat->v = (int*) malloc(m*n*sizeof(int)); return mat; } void libera (struct matriz* mat) { free(mat->v); free(mat); } int accede (struct matriz* mat, int i, int j) { int k; /* índice del elemento del vector */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Acesso no valido!\n"); exit(1); } k = i*mat->col + j; return mat->v[k]; } void valorelemento (struct matriz* mat, int i, int j, int v) { int k; /* índice del elemento del vetor */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Operacion no valida!\n"); exit(1); } k = i*mat->col + j; mat->v[k] = v; } void main() { int a=2, b=2,i,j;

12

struct matriz *p=crea(2,2);//matriz identidad valorelemento(p,0,0,1); valorelemento(p,0,1,0); valorelemento(p,1,0,0); valorelemento(p,1,1,1); for(i=0;i<a;i++){ for(j=0;j<b;j++) printf("%d ",accede(p,i,j)); printf("\n"); } libera(p); }

Si p es un apuntador a una estructura, la aritmética con p toma en cuenta el tamaño de la

estructura, así p++ incrementa p con la cantidad correcta para obtener el siguiente elemento del

arreglo de estructuras. Sin embargo, no hay que suponer que el tamaño de una estructura es la

suma de los tamaños de sus miembros. Debido a requisitos de alineación para diferentes objetos,

podría haber “huecos” no identificados dentro de una estructura. Así, por ejemplo, si un char es

de un byte y un int de cuatro bytes, la estructura

struct {

char c;

int i;

};

Bien podría requerir 8 bytes, no cinco. El operador sizeof regresa el valor apropiado. En general,

una instancia de una estructura tiene el alineamiento impuesto por el campo o miembro más

ancho (en términos de sizeof). Los compiladores lo hacen así, como la forma más simple de

asegurarse que todos los miembros de la estructura estén auto alineados para obtener un acceso

más rápido a los mismos y, que implique menos código de máquina.

En C la dirección de una estructura se corresponde con aquella del primer campo o miembro; si se

tiene un campo cuya longitud en bytes sea mayor que la del siguiente miembro, se añaden bytes

de relleno (internal padding), lo mismo se hace luego del último campo (trailing padding) para

lograr el alineamiento de la estructura con el ancho de la palabra del procesador. Una forma de

reducir el tamaño de una estructura se obtiene, si se puede, reordenando los campos de mayor a

menor tamaño.

Por ejemplo, la siguiente estructura utilizada para representar una lista enlazada:

struct foo10 { char c;//1 byte struct foo10 *p;//4 bytes short x;//2 bytes };

13

Ocupa 12 bytes al ejecutarla en una PC, añadiendo 5 bytes de relleno; si reordenamos sus campos

de mayor a menor tamaño:

struct foo11 { struct foo11 *p; short x; char c; };

Ocupa 8 bytes, y se añade un solo byte de relleno.

Typedef

C proporciona una facilidad llamada typedef para crear nuevos tipos de datos. Por ejemplo, la

declaración

typedef int Longitud;

hace del nombre Longitud un sinónimo de int. El tipo Longitud puede emplearse en

declaraciones, casts, etc., exactamente de la misma manera en que lo podría ser int.

Longitud len, maxlen;

Longitud *lengths[];

De modo semejante, la declaración

typedef char *Cadena;

hace a Cadena un sinónimo para char* o puntero a carácter, que puede usarse en declaraciones

y casts:

Cadena p, lineptr[MAXLINES], alloc(int);

int strcmp(Cadena, Cadena);

p= (Cadena) malloc (100);

Nótese que el tipo que se declara en un typedef aparece en la posición de un nombre de

variable, no justo después de la palabra typedef. Sintácticamente typedef es como las clases de

almacenamiento extern, static, etc. Hemos empleado nombres con mayúscula para los

typedef para destacarlos.

Se debe destacar que una declaración typedef no crea un nuevo tipo en ningún sentido;

simplemente agrega un nuevo nombre para algún tipo ya existente. Tampoco representa alguna

nueva semántica: las variables declaradas de esta manera tienen exactamente las mismas

propiedades que las variables cuyas declaraciones se escriben explícitamente. En efecto, typedef

es como #define, excepto que al ser interpretado por el compilador puede realizar sustituciones

textuales que están más allá de las capacidades del preprocesador. Por ejemplo,

14

typedef int (*AAF)(char *, char *);

crea el tipo AAF, de puntero a función con dos argumentos de tipo char * y que retorna un int,

el cual puede usarse en contextos como

AAF strcmp;

Además de las razones puramente estéticas, hay dos razones principales para emplear typedef.

La primera es parametrizar un programa contra los problemas de portabilidad. Si se emplea

typedef para tipos de datos que pueden ser dependientes de la máquina, cuando un programa

se traslada, sólo los typedef requieren de cambios. Una situación común es usar nombres de

typedef para varias cantidades enteras y, entonces hacer un conjunto apropiado de selecciones

de short, int y long para cada máquina. Tipos como size_t de la biblioteca estándar son

ejemplos.

Los registros asociados a periféricos en sistemas embebidos suelen estar localizados en direcciones fijas de memoria, por ello es muy común utilizar algunos campos de relleno dentro de la estructura para ajustarse a dichas direcciones, tal es caso del campo control en esta estructura

typedef unsigned int uint16_t; typedef struct { uint16_t count; /* Offset 0 */ uint16_t maxCount; /* Offset 2 */ uint16_t _reserved1; /* Offset 4 */ uint16_t control; /* Offset 6 */ } volatile timer_t;

En este tipo de aplicaciones es muy común el uso de operadores a nivel de bits:

timer_t *pTimer = (timer_t *)(0xABCD0123); if (pTimer->control & 0x08)//chequeo bits pTimer->control |= 0x10; //pongo a uno algunos bits pTimer->control &= ~(0x04);//poner a 0 algunos bits pTimer->control ^= 0x80;//conmuto algunos bits

El segundo propósito de typedef es proporcionar mejor documentación para un programa –un

tipo llamado Treeptr puede ser más fácil de entender que uno declarado sólo como un apuntador

a una estructura complicada, como puede ser un árbol binario.

15

Este último ejemplo pertenece a la librería de línea de comandos de un microcontrolador y

permite controlar programas y dispositivos con simples comandos de texto de manera similar a la

terminal de linux y la linea de comandos de Windows. Los comandos pueden transmitirse al

microcontrolador mediante un puerto serie, un socket TCP/IP o una conexión USB. Es interesante

puesto que incorpora un campo con el nombre del comando (a través de un puntero) y el nombre

una función que será invocada cuando se ingrese un comando:

#define MAX_COMMAND_LEN (10) #define COMMAND_TABLE_SIZE (4) typedef struct { char const *name; void (*function)(void); } command_t; void commandsHelp(void); void commandsLed(void); void commandsBuzzer(void);

command_t const gCommandTable[COMMAND_TABLE_SIZE] = { {"HELP", commandsHelp,},//Los commandos se reciben por Puerto serie {"LED", commandsLed, }, {"BUZZER", commandsBuzzer, }, {NULL, NULL } };

Estructuras autoreferenciadas

Veamos el siguiente ejemplo (extraído de https://eva.fing.edu.uy/course/view.php?id=637) de uso

de estructuras que, contienen miembros que son punteros a la misma estructura (ojo que es ilegal

que una estructura contenga una instancia de sí misma), en este caso, para armar una armar una

matriz como si fuese una pila (el último elemento ingresado es el primero que extraigo)

conteniendo un carácter en cada posición de la misma.

//archivo lista_celdas.h

#ifndef LISTA_CELDAS_H #define LISTA_CELDAS_H //cada posición en la matriz indicado por fila columna typedef struct { unsigned fila; unsigned columna; } posicion_t;

16

//la celda contiene una posición en la matriz y el caracter que almacena typedef struct { posicion_t posicion; char simbolo; } celda_t; //arma la matriz con nodos que contienen una celda y un puntero a la siguiente celda typedef struct nodo{ celda_t celda; struct nodo * siguiente;//declaración recursiva de nodo } nodo_t; typedef nodo_t * lista_t; typedef nodo_t * lpos_t; //crea lista vacía lista_t crear_lista(); //agrega nueva celda a la lista void agregar_elemento(celda_t celda, lista_t * lista); void imprimir_lista(lista_t); #endif

//archivo lista_celdas.c

#include "lista_celdas.h" #include <stdlib.h> #include <stdio.h> lista_t crear_lista(){ return NULL; } void agregar_elemento(celda_t celda, lista_t * ptrlista){ //espacio para un nuevo nodo nodo_t * nuevo = (nodo_t *) malloc (sizeof (nodo_t)); nuevo->celda = celda; //el siguiente nodo es el que está en la lista, para el primer nodo es NULL nuevo->siguiente = *ptrlista; //guardo en la lista el nuevo nodo *ptrlista = nuevo; } void imprimir_lista(lista_t lista){ //trabajo con punteros lpos_t pos = lista; //recorre la lista desde el último ingresado hasta el primero ingresado (pila) while (pos != NULL) { celda_t celda = pos->celda; posicion_t posicion = celda.posicion; printf("[fila=%d, col=%d, simbolo=%c]", posicion.fila, posicion.columna, celda.simbolo ); pos = pos->siguiente; } printf("\n"); }

17

//archivo main.c

#include "lista_celdas.h" #include <stdio.h> int main() { celda_t celda; lista_t lista = crear_lista(); celda.posicion.fila = 1; celda.posicion.columna = 2; celda.simbolo = '*'; agregar_elemento(celda, &lista); celda.posicion.fila = 3; celda.posicion.columna = 2; celda.simbolo = 'o'; agregar_elemento(celda, &lista); imprimir_lista(lista); return 0; }

Uniones

Una unión es una variable que puede contener (en momentos diferentes) objetos de diferentes

tipos y tamaños y, el compilador hace el seguimiento del tamaño y requisitos de alineación. Las

uniones proporcionan una forma de manipular diferentes clases de datos dentro de una sola área

de almacenamiento, sin incluir en el programa ninguna información dependiente de la máquina.

El uso más común de uniones en sistemas embebidos es para permitir un rápido acceso a bytes

individuales de un int o long. Un ejemplo podría ser el de contadores de tiempo real de 16 o 32

bits:

union clock { long real_time_count ; // Reservo 4 bytes int real_time_words[2] ; // Reservo 4 bytes como un array de int char real_time_bytes[4] ; // Reservo 4 bytes un array de char }c;

Otro ejemplo adaptado de

http://www.freescale.com/files/soft_dev_tools/doc/white_paper/CWPORTINGWP.pdf, muestra

cómo escribir código que sea más portable usando directivas del preprocesador que tienen en

cuenta la forma de ordenamiento de los bytes en memoria (endianness).

#ifdef INTEL #define ENDIAN 0 #else #define ENDIAN 1 #endif #define VERSIONMASK 0x0F

18

union { short headerFlags; unsigned char flagByte[2]; } flagsBuff; flagsBuff.headerFlags = 0x3132; /* Directive fixes byte-ordering */ versionNumber = flagsBuff.flagByte[ENDIAN ? 0 : 1] & VERSIONMASK;

Este es el propósito de una “unión” –una sola variable que puede legítimamente guardar uno de

varios tipos. La sintaxis se basa en las estructuras.

La variable será suficientemente grande como para mantener al mayor de los tipos: el tamaño

específico depende de la implantación. Cualquiera de estos tipos puede ser asignado a c y,

después empleado en expresiones mientras que el uso sea consistente: el tipo recuperado debe

ser el tipo que se almacenó más recientemente. Es responsabilidad del programador llevar el

registro del tipo que está almacenado actualmente en una unión; si algo se almacena como un

tipo y se recupera como otro, el resultado depende de la implantación.

Las uniones, aparte de usarse para simbolizar diferentes representaciones de datos, se usan para

almacenar información condicionada, por ejemplo:

typedef union { int unidades;//artículos que se venden por unidad float kgs;//artículos que se venden por peso } cantidad;

Sintácticamente, se tiene acceso a los miembros de una unión con

Nombre-unión.miembro

O puntero-unión->miembro

Precisamente como a las estructuras. En el siguiente ejemplo se tiene una variable utipo que se

emplea para llevar el registro del tipo actualmente almacenado en la unión, normalmente esta

variable se chequea en estructuras switch/case o if/else-if if/else

#include <stdio.h> #include <string.h> #define INT 1 #define FLOAT 2 #define STRING 3 union u_tag { /* estructura de uni¢n */ int ival; float fval; char *sval; };

19

void impru(union u_tag, int); void main() { union u_tag udato; /* udato es variable de tipo u_tag */ int utipo; /* para indicar el tipo de miembro */ printf("\n\nTipo union. Impresi¢n de distintos tipos de datos.\n"); utipo = INT; udato.ival = 35; impru(udato, utipo); utipo = FLOAT; udato.fval = 5.123; impru(udato, utipo); utipo = STRING; strcpy (udato.sval, "Esto es una cadena"); impru(udato, utipo); } /* imprime seg£n el tipo de par metro */ void impru(union u_tag udato, int utipo) { if (utipo == INT) printf(" Valor entero: %d\n", udato.ival); else if (utipo == FLOAT) printf("Valor punto flotante: %f\n", udato.fval); else if (utipo == STRING) printf(" Valor cadena: %s\n", udato.sval); else printf("ERROR: tipo no asignado\n"); return; }

Las uniones pueden presentarse dentro de estructuras y arreglos y, viceversa. La notación para

tener acceso a un miembro de una unión en una estructura (o viceversa) es idéntica a la de las

estructuras anidadas. Por ejemplo, dos formas distintas de representar una fecha del presente

año,

typedef union { char date[25]; struct { short int month; short int day; } dayValues; } DAY;

Una función que usa esta estructura, a modo de ejemplo para apreciar cómo se accede a los

miembros de la misma

20

#define YEAR 2016 typedef union { char fecha[25]; struct { short int mes; short int dia; } dayValues; } DAY; void convertDays(DAY *days) { int loop, cantfechas; short int dia, mes; const char *nombresmes[] = {"enero","febrero","marzo","abril","mayo","junio", "julio","agosto","septiembre","octubre", "noviembre","diciembre"}; cantfechas=sizeof(days)/sizeof(DAY); // ciclo a través de los días del array y almaceno la fecha como string for (loop = 0; loop < cantfechas; loop++) { dia = days[loop].dayValues.dia; mes = days[loop].dayValues.mes; sprintf(days[loop].fecha,"%s %d, %d",nombresmes[mes],dia,YEAR); } }

Una unión es una estructura en la cual todos los miembros tienen un desplazamiento cero a partir de la base, la estructura es suficientemente grande para mantener al miembro “más ancho” y, la alineación es la apropiada para todos los tipos de la unión. Están permitidas las mismas operaciones sobre las uniones como sobre las estructuras: asignación o copia como una unidad, tomar la dirección y, hacer el acceso a un miembro.

ANSI C provee una macro cuya definición está en stddef.h llamada offsetof() que retorna el offset

(la distancia desde el comienzo de la estructura) de un miembro dentro de una estructura o unión;

su implementación no es 100% portable. En el siguiente ejemplo usamos la macro para saber

dónde reside el campo b.h dentro de la union llamada UFO:

#include <stdio.h> #include <stddef.h> typedef union { int i;//todos los campos de la unión tienen offset cero float f; char c; struct { float g; double h;//en PC offset de h es 8 } b; } UFOO;

21

void main() { printf("Offset of 'h' is %u", offsetof(UFOO, b.h)); }

Una unión sólo se puede inicializar con un valor del tipo de su primer miembro, así que la unión

DAY descripta anteriormente sólo se puede inicializar con un valor que sea un array de char de

longitud 25.

Es muy común el uso de uniones emparejado con un valor discreto (discriminador o etiqueta) que

indique el miembro activo de la unión, a este tipo de estructura se lo suele llamar unión

discriminada o etiquetada (tagged). La etiqueta normalmente es una enumeración aunque podría

ser un tipo integral. Veamos el siguiente ejemplo ilustrativo

typedef struct Flight { enum { PASSENGER, CARGO } type;//vuelo de pasajeros o de cargas union { int npassengers;//nro.de pasajeros double tonnages; // las unidades de carga, pueden no ser toneladas } cargo; } Flight; Flight flights[ 1000 ]; flights[ 42 ].type = PASSENGER; flights[ 42 ].cargo.npassengers = 150; flights[ 20 ].type = CARGO; flights[ 20 ].cargo.tonnages = 356.78;

Campos de bits

Cuando el espacio de almacenamiento es escaso, puede ser necesario empaquetar varios objetos

dentro de una sola palabra de máquina; un uso común es un conjunto de banderas de un bit como

typedef struct permisos { unsigned int lectura :1; unsigned int escritura :1; unsigned int ejecucion :1; } permisos_t;

La forma usual en que esto (codificar información con un conjunto de banderas de un bit dentro

de un char o int) se realiza es definiendo un conjunto de “máscaras” correspondientes a las

posiciones relevantes de bits, como en:

22

#define LECTURA 1

#define ESCRITURA 2

#define EJECUCION 4

O

enum {LECTURA=1,ESCRITURA=2,EJECUCION=4};

El acceso a los bits viene a ser cosa de “jugar” con los operadores de corrimiento,

enmascaramiento y complemento.

int i = LECTURA | EJECUCION;

Aunque estas expresiones se dominan fácilmente como alternativa, C ofrece la capacidad de

definir y tener acceso a campos de una palabra, más directamente que por medio de operadores

lógicos de bits. Un campo de bits, o simplemente campo, es un conjunto de bits adyacentes dentro

de una unidad de almacenamiento definida por la implantación, al que llamaremos “palabra”. La

sintaxis para la definición y acceso a campos está basada en estructuras.

Esto define una variable llamada permisos_t que contiene tres campos de un bit. El número

que sigue al carácter dos puntos representa el ancho del campo en bits. Los campos son

declarados unsigned int para asegurar que sean cantidades sin signo. Los campos individuales son

referidos en la misma forma que para otros miembros de estructuras. Los campos se comportan

como pequeños enteros y pueden participar en expresiones aritméticas, como lo hacen otros

enteros.

#include <stdio.h>

typedef struct permisos {

unsigned int lectura :1;

unsigned int escritura :1;

unsigned int ejecucion :1;

} permisos_t;

#define LECTURA 1

#define ESCRITURA 2

#define EJECUCION 4

int main() {

struct permisos p = {1,0,1};

int i = LECTURA | EJECUCION;

printf("%ld\n", sizeof(permisos_t));

printf ("p=%u,%u,%u\n", p.lectura, p.escritura,

p.ejecucion);

printf ("i=%u,%u,%u,%u\n", i & LECTURA , i & ESCRITURA, i

& EJECUCION,i);

return 0;

}

Otro ejemplo, extraído de http://www.csee.wvu.edu/~cukic/CS350/Spring98/C_Ch10.txt:

23

#include <stdio.h> struct bitCard {//juego de cartas españolas unsigned face : 4;//puedo representar hasta 16 nros, en este caso se necesita sólo hasta 13 unsigned suit : 2;//representa hasta 4, palo de la carta unsigned color : 1;//puedo representar dos colores }; typedef struct bitCard Card; void fillDeck(Card *); void deal(Card *); main() { Card deck[52];//mazo de 52 cartas fillDeck(deck); deal(deck); return 0; } void fillDeck(Card *wDeck) { int i; for (i = 0; i <= 51; i++) { wDeck[i].face = i % 13; wDeck[i].suit = i / 13; wDeck[i].color = i / 26; } } /* Imprime info cartas en formato de columnas */ /* Columna 1 contiene las cartas con valor de 0-25 subscriptas con k1 */ /* Columna 2 contiene las cartas con valor de 26-51 subscriptas con k2 */ void deal(Card *wDeck) { int k1, k2; for (k1 = 0, k2 = k1 + 26; k1 <= 25; k1++, k2++) { printf("Card:%3d Suit:%2d Color:%2d ", wDeck[k1].face, wDeck[k1].suit, wDeck[k1].color); printf("Card:%3d Suit:%2d Color:%2d\n", wDeck[k2].face, wDeck[k2].suit, wDeck[k2].color); } }

Casi todo acerca de los campos es dependiente de la implantación. El que un campo pueda traslapar el límite de una palabra se define por la implantación. Los campos no necesitan tener nombres; los campos sin nombre (dos puntos y su amplitud solamente) se emplean para llenar espacios. El ancho especial 0 puede emplearse para obligar a la alineación al siguiente límite de palabra.

24

Los campos se asignan de izquierda a derecha en algunas máquinas y de derecha a izquierda en

otras. Esto significa que aunque los campos son útiles para el mantenimiento de estructuras de

datos definidas internamente, la pregunta de qué punta viene primero tiene que considerarse

cuidadosamente cuando se seleccionan datos definidos externamente; los programas que

dependen de tales cosas no son portables. Se denomina endianness al atributo de un sistema que

indica si un dato multibyte (por ejemplo, enteros, flotantes, etc.) se almacena en memoria con su

byte más significativo en las direcciones de memoria más bajas o en las más altas. En el primer

caso se dice que la arquitectura del procesador es de tipo big endian y en el segundo se trata de

una tipo little endian. El problema es similar a los idiomas en los que se escriben de derecha a

izquierda, como el árabe, o el hebreo, frente a los que se escriben de izquierda a derecha, pero

trasladado de la escritura al almacenamiento en memoria de los bytes. Esta nomenclatura

proviene de la novela Los viajes de Gulliver de Jonathan Swift, donde estos nombres

correspondían a dos facciones guerreras liliputienses y, se puede entender como "de comienzo

por el extremo pequeño" (Little endian) y "de comienzo por el extremo mayor" (big endian),

aunque es propenso a confundirse con "acaba en pequeño" y "acaba en grande" respectivamente.

Su etimología proviene de un juego de palabras en inglés con los términos compuestos little-end-

in y big-end-in.

Usando este criterio el sistema big-endian adoptado por Motorola y en la mayoría de las arquitecturas RISC, consiste en representar los bytes en el orden "natural": así el valor hexadecimal 0x4A3B2C1D se codificaría en memoria en la secuencia {4A, 3B, 2C, 1D}. En el sistema little-endian adoptado por Intel y varias plataformas con arquitecturas CISC, el mismo valor se codificaría como {1D, 2C, 3B, 4A}, de manera que de este modo se hace más intuitivo el acceso a datos, porque se efectúa fácilmente de manera incremental de menos relevante a más relevante (siempre se opera con incrementos de contador en la memoria), en un paralelismo a "lo importante no es como empiezan las cosas, sino como acaban."

En el siguiente ejemplo extraído de http://www.ibm.com/developerworks/aix/library/au-

endianc/:

#include <stdio.h> #include <string.h> int main (int argc, char* argv[]) { FILE* fp; struct { char one[4]; int two; char three[4]; } data; /* Inicializo estructura */ strcpy (data.one, "foo"); data.two = 0x01234567; strcpy (data.three, "bar"); /* Escribo datos a un archivo binario */ fp = fopen ("output", "wb");

25

if (fp) { fwrite (&data, sizeof (data), 1, fp); fclose (fp); } }

Se copian los datos de una estructura en un archivo binario, el mismo puede compilarse

perfectamente en distintas máquinas pero la salida que produce es diferente según el endianness,

usando la herramienta hexump para examinar el contenido del archivo binario:

En una máquina big endian

00000000 66 6f 6f 00 12 34 56 78 62 61 72 00

|foo..4Vxbar.|

0000000c

Y en una Little endian

00000000 66 6f 6f 00 78 56 34 12 62 61 72 00

|foo.xV4.bar.|

0000000c

Si el contenido del archivo se envía de una máquina que maneja big endian a otra que maneja Little endian, se leería el contenido en orden inverso al grabado. Como otro ejemplo de la importancia de determinar el orden de almacenamiento de variables multibytes, considérese la siguiente estructura correspondiente a manejo de paquetes de datos del protocolo USB:

struct setup { char bmRequestType, bRequest; short wValue, wIndex, wLength; };

Bajo este protocolo los datos son transferidos e interpretados en un orden little-endian. Si esta estructura forma parte de un programa escrito para una máquina Intel, posteriormente es portado a una arquitectura ARM de tipo big endian, se deberían intercambiar los valores de a bytes tal como ocurriría si se tendría redefinida de esta forma la estructura:

struct setup { short wLength, wIndex, wValue; char bRequest, bmRequestType; };

lo cual puede implicar realizar un trabajo importante cambiando el código. En http://www.ibm.com/developerworks/aix/library/au-endianc/ se indican distintas formas de revertir el orden de los bytes que componen una variable, por ejemplo, para el caso de un entero proponen, entre otras, el uso de un puntero a un arreglo de caracteres, lo cual puede extrapolarse a otro tipo de datos: short reverseInt (char *c) { int i; char *p = (char *)&i;

26

if (is_bigendian()) { p[0] = c[0]; p[1] = c[1]; p[2] = c[2]; p[3] = c[3]; } else { p[0] = c[3]; p[1] = c[2]; p[2] = c[1]; p[3] = c[0]; } return i; }

En el código anterior se utiliza una forma sencilla de determinar la endianness, chequeando la

disposición en memoria de una constante predefinida. Se sabe que la disposición de una variable

de 32 bits de valor 1 es 00 00 00 01 en big-endian and 01 00 00 00 para little-endian. Por tanto,

podemos escribir

const int i = 1; #define is_bigendian() ( (*(char*)&i) == 0 )

En el caso de protocolos de comunicaciones y sus stacks, deben definir su propia endianness, de otra forma dos nodos de distintas endianness serían incapaces de comunicarse correctamente. Por ejemplo, todas las capas del protocolo TCP/IP son big endian; todos los valores transmitidos, tales como una dirección de IP, un checksum, etc., deben enviarse y recibirse comenzando por el byte más significativo. Si una PC cuya dirección de IPv4 es 192.0.1.2 (esta notación decimal con puntos debe trasladarse a un entero de 32 bits) desea comunicarse sobre Internet con un servidor basado en SPARC. Sin ninguna manipulación adicional, el procesador 80x86 de la PC convertirá esta dirección en el entero Little endian 0x020100C0 y transmitirá los bytes en el orden 02 01 00 C0, del otro lado el servidor SPARC recibirá los bytes en el orden 02 01 00 C0, reconstruirá los bytes en el entero big endian 0x020100c0 y malinterpretará la dirección de IP como 2.1.0.192. Si el stack TCP/IP se ejecuta en un procesador Little endian, en tiempo de ejecución deberían reordenarse los bytes de cada campo de dato multi-byte. De forma tal que la pila de protocolos sea portable en tiempo de compilación debería de alguna forma decidirse si se reordenan o no los distintos bytes de datos. Existen distintas funciones que realizan estas conversiones de orden de los bytes de un host a un orden requerido por la red: #include <netinet/in.h>

htons() /* Orden de Host a Orden de Red (short) */ htonl() /* Orden de Host a Orden de Red (long) */ ntohs() /* Orden de Red a Orden de Host (short) */ ntohl() /* Orden de Red a Orden de Host (long) */

Los campos sólo se pueden declarar como enteros; por portabilidad, se debe especificar

explícitamente si son signed o unsigned, salvo los de longitud uno que, sólo pueden ser

27

unsigned. No son arreglos y no tienen direcciones, de modo que el operador & no puede

aplicarse a ellos.

Los formatos de datos impuestos externamente, como interfaces hacia dispositivos de hardware,

frecuentemente requieren la capacidad de tomar partes de una palabra. Un ejemplo extraído de

http://balaiotecnologico.blogspot.com.ar/2010/11/trabalhando-com-campos-de-bits.html, utiliza

campos de bits para representar los datos procedentes de una balanza electrónica que

implementa un protocolo específico y, transmite a través de un puerto serie dos bytes

representando en uno de ellos el estado actual y en el otro el peso que está sobre ella. La imagen

ilustra el significado de cada bit presente en uno de los dos bytes indicando su estado, el cual será

representado mediante campos de bits; para representar los datos de la balanza se usa una

estructura común:

typedef struct { unsigned int flagDecimal : 3; /* Ocupa 3 bits */ unsigned int pesoNegativo : 1; /* Ocupa 1 bit */ unsigned int pesoEmMovto : 1; unsigned int saturacao : 1; unsigned int sobrecarga : 1; unsigned int bit7 : 1; } TWStatus1; typedef struct { char STX; double peso; double tara; TWStatus1 status; char ETX; } TWDadosBalanca;

Normalmente se usan uniones conjuntamente con campos de bits para representar y acceder a los

registros de hardware. En la figura extraída de

28

http://www2.electron.frba.utn.edu.ar/~afurfaro/Clases/Info1/Clase22-Unions.pdf se puede

observar una hoja de datos describiendo un ejemplo de este tipo de registros

En https://vidaembebida.wordpress.com/2014/01/07/libreria-para-manejo-de-bitfields-campos-

de-bits-avr-gcc/ podemos apreciar el uso de campos de bits para el manejo de los registros de un

microcontrolador ATmega16. En este ejemplo se conectan 4 leds a los 4 bits más significativos del

puerto B (PB0-PB3) y 2 push buttons conectados la PB4 y PB5 del mismo puerto B, al presionar un

push button se desplazará el led encendido de derecha a izquierda, y al presionar el otro se

desplazará en sentido contrario.

#define F_CPU 8000000L #include <avr/io.h> #include <util/delay.h> #include "sfrbits.h" //estructura definida para manipular los Leds typedef union Leds_t{ byte value : 4; struct{ byte led1 : 1; //para el led 1. PB0 byte led2 : 1; //para el led 2. PB1 byte led3 : 1; //para el led 3. PB2 byte led4 : 1; //para el led 4. PB3 byte btn1pull : 1; //para activar pull-up en el boton 1. byte btn2pull : 1; //para activar pull-up en el boton 12 byte : 1; //sin uso. byte : 1; //sin uso. };}Leds;

29

//estructura definida para manipuilar los botones typedef struct Btns_t{ byte : 1; //sin uso. byte : 1; //sin uso. byte : 1; //sin uso. byte : 1; //sin uso. byte left : 1; //Para el boton a la izquierda. byte right : 1; //para el boton a la derecha. byte : 1; //sin uso. byte : 1; //sin uso. }Btns; #define LEDS SFR_STRUCT_BITS(Leds,PORTB) //Mapeamos el PORTB a la estructura Leds #define BTNS SFR_STRUCT_BITS(Btns,PINB) //Mapeamos el PORTB a la estructura Btns int main(void) { byte valant; //establecemos la direccion de los pines como salida para los leds //es equivalente a DDRBbits.NLOW=0xF DDRBbits.B0=OUTPUT; DDRBbits.B1=OUTPUT; DDRBbits.B2=OUTPUT; DDRBbits.B3=OUTPUT; //establecemos la direccion de los pines como entradas para los botones //es equivalente a DDRBbits.NHIGH=0x3 DDRBbits.B4=INPUT; DDRBbits.B5=INPUT; //Activamos pull-ups en los botones LEDS.btn1pull=HIGH; LEDS.btn2pull=HIGH; LEDS.value=0; //apagamos todos los leds. LEDS.led1=HIGH; //encendemos el primer led while(1) //cilco infinito { valant=LEDS.value; //guardamos el valor de los leds while(BTNS.left==LOW && BTNS.right==LOW) //mientras se presionan los dos botones LEDS.value=0xF; //encendemos todos los leds LEDS.value=valant; //desplazamos a la izquieda if(BTNS.left==LOW && LEDS.led4==LOW) { LEDS.led4=LEDS.led3; LEDS.led3=LEDS.led2; LEDS.led2=LEDS.led1; LEDS.led1=0; } //desplazamos a la derecha /*https://github.com/alfreedom/Vida-Embebida/blob/master/Librerias%20AVR/sfrbits.h*/

30

if(BTNS.right==LOW && LEDS.led1==LOW) { LEDS.led1=LEDS.led2; LEDS.led2=LEDS.led3; LEDS.led3=LEDS.led4; LEDS.led4=0; } _delay_ms(50); //retardo anti-rebotes } return 0; }

En el caso de sistemas embebidos es muy común usar las uniones conteniendo campos de bits para hacer el código más portable permitiendo el acceso al registro completo:

typedef unsigned char uint8_t; #define TIMER_COMPLETE (0x08) #define TIMER_ENABLE (0xC0) union { uint8_t byte;//representa timer status register struct { uint8_t bit0 : 1; uint8_t bit1 : 1; uint8_t bit2 : 1; uint8_t bit3 : 1; uint8_t nibble : 4; } bits; } foo;

En lugar de acceder a los bits individuales el registro representado mediante esta unión puede escribirse como un todo, para ello es muy útil combinar el uso de unión con operadores a nivel de bits:

foo.byte = (TIMER_COMPLETE | TIMER_ENABLE);

En http://www.sase.com.ar/2012/files/2012/09/Prog-en-C-para-sistemas-embebidos-en-

MSP430.pdf se aconseja no usar campos de bits de tamaño mayor a 1. Por ejemplo dada la

siguiente estructura de campos de bits

union bits{ struct{ unsigned int b1:1; unsigned int b2:2; unsigned int b3:3; unsigned int b4:4; unsigned int b6:6; }b; unsigned int w; }bf;

31

Las operaciones sobre campos de un bit pueden requerir una sola instrucción de máquina

En C: bf.b.b1 = 1; En assembler: bis.w #0x1,&bf

Las operaciones sobre campos de más de un bit requieren varias instrucciones de máquina. En C:

bf.b.b3 = 5;

En assembler: mov.w &bf,R15 ; copia and.w #0xFFC7,R15 ; borra bis.w #0x28,R15 ; setea mov.w R15,&bf ; copia

La misma operación implementada sin campos de bits es más eficiente. En C:

w &= 0x00c7; // borra 3 bits w |= 5 << 3; // escribe el 5

En assembler: and.w #0xC7,&w ; borra bis.w #0x28,&w ; escribe