introducción al lenguaje c. - departamento de electrónicalsb/elo320/clases/ap2.pdf · su objetivo...

93
1 Profesor Leopoldo Silva Bijit 20-01-2010 Apéndice 2 Introducción al lenguaje C. Al inicio se efectúa un breve repaso del lenguaje. A continuación se expone con mayor detalle los tipos básicos y su manipulación; conceptualizando en el diseño de macros y funciones. Más adelante se profundiza en la interfaz de entrada salida, y en el diseño de rutinas matemáticas. 1. Funciones. 1.1. Abstracción de acciones y expresiones. Una función es una abstracción de una expresión. Su objetivo es la realización de un grupo de acciones, que se reconocen por el nombre de la función. De este modo si el lenguaje no dispone de una determinada acción que se desee, se la puede desarrollar como un grupo de las instrucciones que el lenguaje ya posee, e invocar la realización de esta nueva acción por su nombre. Luego de esto puede seguir construyéndose nuevas funciones empleando las ya definidas, en el mismo sentido que ya Euclides desenvolvió para el desarrollo de la geometría a través de sus Elementos. De esta forma un gran programa puede estudiarse como un conjunto de funciones. Y el desarrollo del programa como el diseño de las funciones individuales. Si bien el lenguaje C tiene acciones muy primitivas, tiene una biblioteca (en inglés: library) muy amplia. Las acciones básicas deben ser simples ya que el objetivo del lenguaje es lograr una compilación eficiente; es decir, que la traducción a assembler sea siempre posible y eficiente con los repertorios clásicos de los procesadores. La biblioteca es un conjunto de funciones desarrolladas por especialistas y hábiles programadores que suelen estar compiladas. Cuando se dispone de los programas fuentes de la biblioteca se tiene un excelente material para aprender a programar. Un ejemplo de la biblioteca estándar es la de entrada salida. El programador, por ejemplo, puede desplegar un resultado en la salida mediante una invocación a la función printf.

Upload: vuongthuy

Post on 19-Sep-2018

222 views

Category:

Documents


0 download

TRANSCRIPT

1

Profesor Leopoldo Silva Bijit 20-01-2010

Apéndice 2

Introducción al lenguaje C.

Al inicio se efectúa un breve repaso del lenguaje. A continuación se expone con mayor detalle

los tipos básicos y su manipulación; conceptualizando en el diseño de macros y funciones. Más

adelante se profundiza en la interfaz de entrada salida, y en el diseño de rutinas matemáticas.

1. Funciones.

1.1. Abstracción de acciones y expresiones.

Una función es una abstracción de una expresión.

Su objetivo es la realización de un grupo de acciones, que se reconocen por el nombre de la

función.

De este modo si el lenguaje no dispone de una determinada acción que se desee, se la puede

desarrollar como un grupo de las instrucciones que el lenguaje ya posee, e invocar la realización

de esta nueva acción por su nombre.

Luego de esto puede seguir construyéndose nuevas funciones empleando las ya definidas, en el

mismo sentido que ya Euclides desenvolvió para el desarrollo de la geometría a través de sus

Elementos.

De esta forma un gran programa puede estudiarse como un conjunto de funciones.

Y el desarrollo del programa como el diseño de las funciones individuales.

Si bien el lenguaje C tiene acciones muy primitivas, tiene una biblioteca (en inglés: library) muy

amplia. Las acciones básicas deben ser simples ya que el objetivo del lenguaje es lograr una

compilación eficiente; es decir, que la traducción a assembler sea siempre posible y eficiente

con los repertorios clásicos de los procesadores.

La biblioteca es un conjunto de funciones desarrolladas por especialistas y hábiles

programadores que suelen estar compiladas. Cuando se dispone de los programas fuentes de la

biblioteca se tiene un excelente material para aprender a programar.

Un ejemplo de la biblioteca estándar es la de entrada salida. El programador, por ejemplo,

puede desplegar un resultado en la salida mediante una invocación a la función printf.

2 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

#include <stdio.h>

int x;

printf(" x= %d \n", x);

El nombre de la función recuerda que imprime con formato; es decir con determinado patrón

que se establece mediante el string de formato, que es el primer argumento de la función. En el

ejemplo mueve hacia la salida todos los caracteres del string, hasta encontrar un %. Luego de

acuerdo a la letra, después de cada signo % determina cómo imprimir el resto de los

argumentos. En este caso imprime el valor del entero almacenado en x, en forma decimal (por la

letra d).

1.2. Prototipo, definición, invocación.

Lo que se necesita para emplear una función de la biblioteca es conocer sus argumentos y el tipo

del valor de retorno, y la función que realiza. Esta especificación se conoce como el prototipo

de la función.

El conjunto de instrucciones que forman la función se denomina definición de la función.

La acción de usar la función, se conoce como invocación, y se hace de tal modo que los

argumentos actuales deben estar en correspondencia de número, tipo y orden de ocurrencia con

los parámetros o argumentos formales que tiene la definición de la función.

Tomemos un ejemplo del cálculo. Y calculemos la función: f(x) = 3 x2 +5

Entonces la definición, muestra: un argumento real y el tipo del valor de retorno, que también es

real.

float f ( float x) /* definición */

{

return ( 3*x*x + 5);

}

Luego de definida la función, se la puede invocar; es decir, si la invocación está más adelante,

en el texto del programa, que su definición.

float w, y;

y = 4.2;

w = f(y -3.0) + 3.3*y ; /* invocación */

Nótese que el argumento actual debe ser una expresión que tome un valor de tipo float. Y que

el valor de tipo float, retornado por la función puede emplearse, también dentro de una

expresión.

Antes de invocar, se calcula el valor del argumento actual (en este caso: 4.2-3.0) y con este

valor se inicializa la variable x (argumento actual) en el espacio de memoria asociado a la

función. Dentro del cuerpo de acciones de la función se realizan los cálculos y mediante la

Apéndice 2. Introducción al lenguaje C. 3

Profesor Leopoldo Silva Bijit 20-01-2010

palabra return, se devuelve el valor calculado. Este valor reemplaza a la función en la expresión

en que es invocada.

Si se desea invocar a la función con un argumento entero, debe especificarse una conversión

explícita de tipo, mediante un cast.

int j=0;

j = 5; w = f( (float) j*2 )

Si la invocación se realiza antes, en el texto del programa, que su definición, es preciso emplear

un prototipo de la función antes de invocarla. Nótese el punto y coma que termina el prototipo.

float f( float) ; /* prototipo */

Cuando se desea diseñar una función que sólo sea una agrupación de acciones, se define su

retorno vacío (void). También si no tiene argumentos, debe especificarse void, en el lugar de

los argumentos.

void asterisco(void)

{ putchar('*'); }

Como esta función usa una función de biblioteca, antes de definirla se debe incluir <stdio.h>

que contiene el prototipo de putchar.

Para invocarla:

asterisco( );

Una función con retorno vacío es una abstracción de procedimiento o acción y no de expresión.

1.3. Alcances del lenguaje C.

Parte importante de la habilidad que debe desarrollar un programador consiste en la forma en

que diseña las funciones que le permitirán resolver un determinado problema. Algunos

programadores generan sus propias bibliotecas, que pueden reutilizar en otros proyectos.

Por otro lado cuando la magnitud del problema es mayor, y deben resolverlo mediante un

equipo de programadores, la especificación de las funciones permite establecer la división del

trabajo. Todos deben conocer los prototipos.

Este modelo de programación considera las funciones como operaciones sobre los datos.

También es preciso que los miembros del equipo conozcan las estructuras de datos que

manipularán las funciones.

Sin embargo, el modelo anterior, aparentemente consistente, no se comportó bien en el tiempo.

En la actualidad se conciben las estructuras de datos y las acciones que las manipulan como un

todo, denominado objeto.

Si una persona desea aprender a programar es recomendable que se inicie con un lenguaje

orientado a objetos.

4 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

La exposición del lenguaje C sigue empleándose como una descripción abstracta de las

capacidades de un procesador.

1.4. Paso de argumentos por valor.

Entonces, las funciones en C, tienen un diseño muy limitado. Sólo se le pueden pasar los

valores de los argumentos, y sólo se dispone de un valor de retorno.

Esto es muy limitado, ya que por ejemplo si el problema se puede modelar con vectores o

matrices, se requiere un vector o matriz de retorno. La función sólo puede retornar un valor de

un tipo determinado.

En ANSI C, se decidió permitir el retorno de una estructura. Lo cual permite retornar todos los

miembros de ésta.

El uso de punteros, que modelan las capacidades de direccionamiento indirecto o en base a

registros, permite pasar valores de punteros como argumentos y también retornar un valor de un

puntero. Posibilitando lo que se denomina paso por referencia y también el retorno de una

variable que apunta a una zona de la memoria donde pueden ubicarse múltiples valores de

retorno.

1.5. Paso por referencia.

Veamos un ejemplo de paso por referencia.

Deseamos incrementar en uno dos variables, en una sola operación, que identifican contadores.

int cnt1=0, cnt2=0, cnt=0;

La función:

int cnt( int i)

{ return (i++); }

sólo puede incrementar un contador a la vez.

Para incrementar dos contadores, se requiere:

cnt1=cnt(cnt1); cnt2=cnt(cnt2); /* cnt1++, cnt2++; */

El diseño:

void cnt( int * c1, int *c2)

{ (*c1)++; (*c2)++}

nos permite incrementar dos contadores:

cnt( &cnt1, &cnt2);

o bien cnt(&cnt1, &cnt3);

Apéndice 2. Introducción al lenguaje C. 5

Profesor Leopoldo Silva Bijit 20-01-2010

Al invocar se pasan los valores de las direcciones de las variables; en el cuerpo de la función

mediante indirección se escribe en las variables. Se pasa una referencia a las variables.

1.6. Frame.

Otro concepto relevante en el uso de funciones es el espacio de memoria que ésta ocupa para

almacenar sus variables. Este espacio se denomina frame y logra mantener en direcciones

contiguas de memoria a las variables que puede emplear la función mientras se ejecuta su

código.

Lo que se desea es tener localidad espacial. Es decir que las instrucciones que ejecuta la

función estén en direcciones contiguas de memoria y también las variables que ésta emplee.

Esto permite el empleo eficiente de las memorias caché, actualmente presentes en todos los

diseños actuales de procesadores.

Otra consideración de importancia es que sólo es necesario disponer del frame mientras la

función esté en ejecución, lo cual permite reutilizar las celdas ocupadas.

Las variables locales, las definidas dentro del cuerpo de acciones de la función, se guardan en el

frame. Los argumentos también se guardan en el frame. Si almacenamos en el frame la

dirección de la instrucción a la que debe retornarse, luego de ejecutada la función, podremos

invocar a funciones dentro de una función.

La organización anterior permite diseñar funciones recursivas; es decir, funciones que se

llamen a sí mismas. Cada vez que se llame a una función se crea un frame.

También se denomina diseño de funciones reentrantes a las que pueden ser llamadas de

diferentes partes y conservar su propio espacio de variables (en el frame).

Entonces, antes de llamar a una función se introducen en el frame, los valores de los argumentos

actuales en celdas contiguas de memoria; luego se introduce la dirección de retorno, y

finalmente se llama a la función. El código de la función crea el espacio para las variables

locales en el frame, en celdas contiguas. Antes de salir de la función, se retorna el valor; luego

se recupera la dirección de retorno y finalmente se desarma el frame.

Los procesadores mantienen en dos registros especiales la dirección de la próxima instrucción a

ejecutar (program counter PC) y la dirección de la última celda ocupada por el frame (stack

pointer SP).

Con esta disciplina el compilador lo único que requiere para traducir a código de máquina el

texto de una función es su prototipo. Ya que debe empujar dentro del frame los valores de los

argumentos, a su vez conoce ahora a los argumentos por los desplazamientos de éstos relativos

al stack pointer. Empleando mecanismos de direccionamiento indirecto o relativos a registros

puede leer y escribir en el espacio asignado a los argumentos. Lo mismo puede decirse de la

forma en que puede leer o escribir en las variables locales.

Sin embargo dotar a un lenguaje con la capacidad de invocar a funciones tiene un costo. Se

requieren varias instrucciones para crear el espacio, copiar los valores de los argumentos, iniciar

6 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

las variables locales, y luego otra serie de instrucciones para desarmar el frame. Además,

obviamente, toda esta actividad requiere un tiempo para su realización.

No es razonable crear funciones cuyo código sea menor o similar al número de instrucciones

requeridas para administrar el frame. En estos casos se emplean macros; los cuales permiten la

abstracción de llamar por un nombre, sin el costo del frame.

Ojalá las cosas fueran simples....

Sin embargo este esquema que se ve simple y consistente, no es eficiente. Los accesos a

memoria siguen siendo el freno en la ejecución de programas. Las nuevas arquitecturas han

introducido máquinas con un número elevado de registros, los cuales pueden ser leídos y

escritos en menos tiempo que las celdas de las memorias caché.

El programador en assembler puede organizar sus funciones de tal modo de emplear

preferentemente registros. Lo cual logra funciones más rápidas.

Los compiladores actuales para un procesador determinado tienen una política para el uso de los

registros; intentan pasar un número fijo de argumentos y retornar un número fijo de valores en

registros, también intentan emplear registros para las variables locales. Y sólo emplean el frame

para los argumentos que no puedan almacenarse en registros.

Más aún: clasifican los registros en temporales y salvables. Usan indiscriminadamente los

temporales, para desarrollar el cuerpo de la función, y los salvables los emplean para las

variables locales. Y sólo salvan en el stack los valores de los registros salvables que sean

modificados por la función.

1.7. Algunos conceptos básicos

Como en cualquier lenguaje existe un aspecto léxico (vocabulario, las palabras que se pueden

escribir); un aspecto sintáctico (reglas gramaticales, para escribir correctamente); y finalmente

un aspecto semántico (que asigna un significado a las construcciones).

Un lenguaje de programación permite escribir programas que instruyen al computador sobre el

conjunto organizado de acciones (algoritmo) que deben efectuarse sobre los datos.

Las acciones y los datos deben describirse con rigurosidad para que puedan ser correctamente

interpretados por un autómata.

1.7.1. Datos.

Los datos básicos tienen un tipo asociado.

El tipo establece el conjunto de valores que puede tomar un dato de ese tipo y las acciones que

pueden efectuarse sobre valores de ese tipo.

Los tipos básicos son: enteros, reales, carácter y strings (secuencia de caracteres).

Enteros con signo.

En C, los enteros se representan en una palabra de la memoria, y sus valores dependen del

ancho de palabra que tenga la memoria (en la actualidad, 16 ó 32 bits)

Apéndice 2. Introducción al lenguaje C. 7

Profesor Leopoldo Silva Bijit 20-01-2010

Se emplea representación complemento a dos para representar números con signo.

Suponiendo un largo de palabra de la memoria de 3 bits, para simplificar la explicación:

Representación

interna

Equivalente Decimal

Complemento a dos.

Equivalente Decimal

Complemento a uno.

Entero sin

Signo

000 +0 +0 0

001 +1 +1 1

010 +2 +2 2

011 +3 +3 3

100 -4 -3 4

101 -3 -2 5

110 -2 -1 6

111 -1 -0 7

Para obtener un número en complemento a uno, basta cambiar sus unos por ceros y sus ceros

por unos. Si tenemos: 010 (el decimal 2) su complemento uno es 101 (con equivalente decimal

menos dos).

El complemento a dos es el complemento a uno más uno. (Sumarle uno en binario)

Si tenemos: 010, su complemento 1 es 101 y al sumarle uno queda:

101

+ 1

____

110

Nótese que en complemento a uno existen dos ceros; pero el rango de representación es

simétrico.

En complemento a dos, existe un cero, pero con rango de representación asimétrico.

Para 16 bits, en C-2, el rango de representación de enteros con signo es desde -(2^15) hasta

+(2^15 )-1

Lo cual equivale al intervalo desde –32.768 hasta +32.767

Definiciones de Datos.

a) Para declarar una variable entera, por ejemplo, la variable i como entera, se anota:

int i;

Esto reserva una palabra de la memoria y la reconoce con el nombre simbólico i.

Si se escribe: i = 5;

Se guarda, en representación interna: 0000.0000.0000.0101 en la dirección i, si el largo de la

palabra es 16 bits. Y 0000.0000.0000.0000.0000.0000.0000.0101 si es de 32 bits. Los

separadores cada cuatro cifras binarias son virtuales, permiten leer el número en hexadecimal.

8 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Más sencillamente se puede decir que quedó guardado un número entero 5 en la variable i.

Si, más adelante, se efectúa: i = i +1; quedará almacenado 6.

( en C se puede anotar: i++)

Pero si i tiene almacenado 32767 y se suma uno a i, quedará almacenado: -32768.

Cuestión que no debería sorprender, si se recuerda que se representan los números en

complemento a dos, y la aritmética es modular (en C no suele haber indicación de rebalse).

b) Si deseamos definir, dos variables enteras, i y j como enteros, puede anotarse:

int i;

int j;

más sencillamente, puede emplearse una lista:

int i, j;

c) También puede definirse y autoiniciarse con un valor:

int i = 5 ; /* define e inicia i */

Enteros sin signo.

unsigned int i = 3500u; /* 0 < i < 65535(en 16 bits) . Puede anotarse 3500U*/

Enteros Largos.

Ocupan el doble de largo que un entero común.

long int j = 123L;

/*Note la l o L, que se agrega al final, para especificar que el valor es entero largo. */

Rango en 16 bits. long int: -2.147.483.648 hasta +2.147.483.647

Largos sin signo.

unsigned long int i = 123ul; /*se agrega ul o UL Máximo 4.294.967.295 en 32 bits*/

Números Reales. ( float )

Representan números reales con una mantisa y un exponente.

Rango de representación entre 3.4e-38 hasta 3.4e+38 para el flotante de precisión simple.

Sólo se tienen 6 cifras decimales significativas, y se pueden escribir en formato fijo (123.456) o

bien en formato exponencial ( 1.23456e+2).

float x; /* define flotante */

double y; /*Define un real en doble precisión. (10 cifras decimales de precisión).*/

long double z; /* real de precisión extendida. */

Apéndice 2. Introducción al lenguaje C. 9

Profesor Leopoldo Silva Bijit 20-01-2010

Carácter.

Es un tipo adecuado para representar un carácter o símbolo alfanumérico (los que pueden

digitarse en un teclado).

La definición:

char ch = ‟a‟ ; /* define e inicia ch con el valor ASCII del símbolo a.*/

‟ ‟ el espacio equivale a 0x20

‟0‟ equivale a 0x30 ; el ‟1‟ a 0x31, ….

‟A‟ equivale a 0x41; ‟B‟ a 0x42; ….

‟a‟ equivale a 0x61: ….

Los caracteres toman valores enteros, y se definen entre comillas simples.

Por ejemplo:

int x = 0;

x= ‟1‟ - 1 ; /* toma valor 0x30 ; es decir 48 decimal. */

Algunos valores de caracteres no imprimibles:

„\n‟ es la forma de representar la función de newline. (El cursor se posiciona en el inicio de la

línea siguiente.

„\t‟ su envío hacia la salida produce una tabulación.

„\b‟ hace sonar una señal audible ( bell )

Strings.

Se los define como un arreglo de caracteres. La definición del arreglo, se indica con el valor

entero de las componentes entre paréntesis de tipo corchete.

Char a[10]; /*crea espacio para ingresar 9 caracteres. */

La última componente del arreglo, a[9] se inicia con valor nulo.

También se puede definir e iniciar: char a[ ] = “ este es un string “;

Se emplea una dimensión muda del arreglo, y ésta se ajusta automáticamente al largo de la

secuencia de caracteres encerrados entre comillas dobles.

La forma más empleada por los programadores en C, es mediante un puntero a carácter:

char *a = “ este es un string “;

En la representación interna se agrega un „\0‟ al final. (equivale a 0x00, y se denomina carácter

nulo)

1.7.2. Acciones.

La manera básica de organizar las acciones es hacerlo en una de las tres siguientes formas:

Secuencia.

10 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Se realiza primero una acción y luego la siguiente:

Se anota:

{ acción1 ; acción2; }

Alternativa.

Si la condición es verdadera se realiza la acción1; si es falsa: la acción2.

Se anota:

if ( condición ) accion1; else acción2;

Repetición.

Mientras la condición sea verdadera se repite la acción.

Se anota:

while (condición ) acción;

Se ha comprobado que cualquier diagrama de flujo puede ser representado usando las tres

construcciones básicas anteriores. Una programación que emplee estos principios se denomina

estructurada. Permite leer un programa sin tener que volver hacia atrás.

For.

int i=10;

for (i=10; i>0 ; i--) putchar( ‟*‟);

putchar( ‟\n‟);

La construcción del for(inicio; condición; reinicio) ejecuta la expresión de inicio; luego evalúa

la condición, y si es verdadera(valor diferente de cero) ejecuta el bloque asociado; finalmente

evalúa la expresión de reinicio y vuelve a evaluar la condición y así sucesivamente hasta que

ésta es falsa(valor cero), situación en que termina el for.

Abstracción.

Una acción adicional es poder invocar a una función.

Como se verá una función permite agrupar a un número de instrucciones bajo un nombre.

Dotándola de una interfaz con el resto del programa, pasándole argumentos como valores de

entrada y tomando la función un valor de retorno.

Dado un problema, encontrar las funciones que permitan resolverlo es fundamental en

programación.

1.7.3. Entrada. Salida.

En el lenguaje C, no existen acciones de entrada y salida. Estas están desarrolladas como

invocaciones a funciones de biblioteca.

printf(“Escribe este texto hacia la salida estándar”);

printf(“\nEscribe este texto hacia la salida estándar”); /*avanza línea y escribe el texto*/

Apéndice 2. Introducción al lenguaje C. 11

Profesor Leopoldo Silva Bijit 20-01-2010

printf(“Escribe este texto hacia la salida estándar\n”); /*escribe el texto y avanza línea*/

Si i es de tipo entero:

printf(“%d”,i); /*escribe el entero en decimal*/

printf(“%o”,i); /*escribe el entero en octal*/

printf(“%x”,i); /*escribe el entero en hexadecimal*/

printf(“%6d”,i); /*escribe el entero en decimal, con largo de campo igual a 6*/

printf(“abcd%defgh”, i); /*escribe string abcd, el entero en decimal, y el string efgh*/

scanf(“%d”,&i) /*espera leer un entero en decimal, y lo almacena en variable i */

Debe notarse el empleo de & antes de la variable en la que se está depositando el valor.

Ejemplos.

#include <stdio.h>

int main(void)

{ int i,j,k; i=4; j=8; k=i*j;

printf("%d%d%d", i, j, k); /* en decimal */

printf("\n");

printf("i = %d j = %d k = %d\n", i, j, k); /* con string intercalados */

printf("-> i = %d(decimal) j = %o(octal) k = %x(hexadecimal)\n",i,j,k);

printf("--> i = %d j = %d k = %d\n", i, j, k);

return(0);

}

12 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

2. Tipo char.

2.1. Valores.

Primero describiremos los valores que pueden tomar los elementos de tipo char.

Es un tipo básico del lenguaje. Las variables y constantes de tipo char ocupan un byte.

El tipo unsigned char tiene el rango 0 a 255.

El tipo char (o signed char, esto es por defecto) tiene el rango –128 a 127.

A las variables de tipo char se las puede tratar como si fueran de tipo entero, ya que son

convertidas automáticamente a ese tipo cuando aparecen en expresiones.

Una constante de tipo carácter se define como un carácter encerrado entre comillas simples. El

valor de una constante de tipo carácter es el valor numérico de ese carácter en la tabla o código

de caracteres. En la actualidad la tabla más empleada es el código ASCII.

2.1. Definición de variables y constantes de tipo char.

char ch; declara una variable de tipo char.

ch = „c‟; asigna la constante de tipo carácter c a la variable ch.

2.2. Caracteres ASCII.

ASCII son las iniciales de American Standard Code for Information Interchange.

Internamente, un carácter, se representa por una secuencia binaria de 8 bits. Un valor

perteneciente al código ASCII es la representación numérica de un carácter como '1' o '@' o de

una acción de control.

Se tienen 32 caracteres de control, que no son imprimibles o visualizables. En general puede

especificarse un carácter por su valor numérico equivalente expresado en octal, mediante '\ooo'

donde una, dos o las tres o deben ser reemplazadas por un dígito octal (dígitos entre 0 y 7). La

secuencia binaria de 8 unos seguidos, equivale a 377 en octal.

Alternativamente pueden emplearse dos cifras hexadecimales para representar un carácter, del

siguiente modo: '\xhh' La x indica que uno o los dos dígitos siguientes deben ser reemplazados

por una cifra hexadecimal (dígitos 0 a 9, y las letras A, B, C, D, F). La secuencia binaria de 8

unos seguidos, equivale a FF en hexadecimal.

Apéndice 2. Introducción al lenguaje C. 13

Profesor Leopoldo Silva Bijit 20-01-2010

H D H D H D H D H D H D H D H D

00 NULL 00 10 DEL 16 20 32 30 0 48 40 @ 64 50 P 80 60 ` 96 70 p 112

01 SOH 01 11 DC1 17 21 ! 33 31 1 49 41 A 65 51 Q 81 61 a 97 71 q 113

02 STX 02 12 DC2 18 22 " 34 32 2 50 42 B 66 52 R 82 62 b 98 72 r 114

03 EXT 03 13 DC3 19 23 # 35 33 3 51 43 C 67 53 S 83 63 c 99 73 s 115

04 EOT 04 14 DC4 20 24 $ 36 34 4 52 44 D 68 54 T 84 64 d 100 74 t 116

05 ENQ 05 15 NAK 21 25 % 37 35 5 53 45 E 69 55 U 85 65 e 101 75 u 117

06 ACK 06 16 SYN 22 26 & 38 36 6 54 46 F 70 56 V 86 66 f 102 76 v 118

07 BEL 07 17 ETB 23 27 ' 39 37 7 55 47 G 71 57 W 87 67 g 103 77 w 119

08 BS 08 18 CAN 24 28 ( 40 38 8 56 48 H 72 58 X 88 68 h 104 78 x 120

09 TAB 09 19 EM 25 29 ) 41 39 9 57 49 I 73 59 Y 89 69 i 105 79 y 121

0a LF 10 1a SUB 26 2a * 42 3a : 58 4a J 74 5a Z 90 6a j 106 7a z 122

0b VT 11 1b ESC 27 2b + 43 3b ; 59 4b K 75 5b [ 91 6b k 107 7b { 123

0c FF 12 1c FS 28 2c , 44 3c < 60 4c L 76 5c \ 92 6c l 108 7c | 124

0d CR 13 1d GS 29 2d - 45 3d = 61 4d M 77 5d ] 93 6d m 109 7d } 125

0e SO 14 1e RS 30 2e . 46 3e > 62 4e N 78 5e ^ 94 6e n 110 7e ~ 126

0f SI 15 1f US 31 2f / 47 3f ? 63 4f O 79 5f _ 95 6f o 111 7f del 127

Figura A2.1. Tabla ASCCI.

Todos los valores de la tabla anterior son positivos, si se representan mediante un byte, ya que el

bit más significativo es cero.

Los caracteres que representan los dígitos decimales tienen valores asociados menores que las

letras; y si se les resta 0x30, los cuatro bits menos significativos representan a los dígitos

decimales en BCD (Binary Coded Decimal). Las letras mayúsculas tienen códigos crecientes en

orden alfabético, y son menores en 0x20 que las letras minúsculas.

En español suelen emplearse los siguientes caracteres, que se anteceden por su equivalente

decimal: 130 é, 144 É, 154 Ü, 160 á, 161 í, 162 ó, 163 ú, 164 ñ, 165 Ñ, 168 ¿, 173 ¡.

Los valores de éstos tienen el octavo bit (el más significativo en uno), y forman parte de los 128

caracteres que conforman un código ASCII extendido.

Los caracteres de control han sido designados por tres letras que son las primeras del significado

de la acción que tradicionalmente e históricamente se les ha asociado.

Por ejemplo el carácter FF (Form Feed) con valor 0x0c, se lo emplea para enviar a impresoras, y

que éstas lo interpreten con la acción de avanzar el papel hasta el inicio de una nueva página

(esto en impresoras que son alimentadas por formularios continuos).

Los teclados pueden generar caracteres de control (oprimiendo la tecla control y una letra). Por

ejemplo ctrl-S y ctrl-Q generan DC3 y DC1 (también son conocidos por X-on y Xoff), y han

sido usados para detener y reanudar largas salidas de texto por la pantalla de los terminales).

Varios de los caracteres se han usado en protocolos de comunicación, otros para controlar

modems.

14 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

2.3. Secuencias de escape.

Algunos de los caracteres, debido a su frecuente uso, tienen una representación por secuencias

de escape. Se escriben como dos caracteres, pero representan el valor de uno de control. Los

más usados son:

\n representa a nueva línea (new line o line feed).

En Unix esto genera un carácter de control, en archivos de texto en PC, se generan dos: 0x0D

seguido de 0x0A.

\t tabulador horizontal.

\0 Nul representa el carácter con valor cero. El que se emplea como terminador de string.

Si se representan como constantes de tipo char, se definen entre comillas simples.

Por ejemplo:

#define EOS '\0' /* End of string */

Estas secuencias de escape pueden incorporarse dentro de strings. El \n (backslash n) suele

aparecer en el string de control de printf, para denotar que cuando se lo encuentre, debe

cambiarse de línea en el medio de salida.

Dentro de un string, suelen emplearse las siguientes secuencias para representar los caracteres ",

', \. Que no podrían ser usados ya que delimitan strings o caracteres o son parte de la secuencia

de escape.

\\ para representar la diagonal invertida

\" para representar la comilla doble, dentro del string.

\' para representar la comilla simple dentro del string.

Ejemplo:

Char esc = '\\';

"O\'Higgins" en un string.

2.4. Archivos de texto y binarios.

El siguiente texto,

se representa internamente

según:

45 6C 20 73 69 67 75 69 65 6E 74 65 20 74 65 78 74 6F 2C 20 0D 0A

73 65 20 72 65 70 72 65 73 65 6E 74 61 20 69 6E 74 65 72 6E 61 6D 65 6E 74 65 20 0D 0A

73 65 67 FA 6E 3A 0D 0A

La representación hexadecimal de los caracteres que forman el texto, muestra los dos caracteres

de control que representan el fin de línea (0x0D seguido de 0x0A). Cada carácter gráfico es

representado por su valor numérico hexadecimal. La primera representación (externa) se emplea

para desplegar la información en pantallas e impresoras; la segunda es una representación

interna (se suele decir binaria, pero representada en hexadecimal) y se emplea para almacenar

Apéndice 2. Introducción al lenguaje C. 15

Profesor Leopoldo Silva Bijit 20-01-2010

en memoria o en medios magnéticos u ópticos. Normalmente existen comandos (type, cat) y

programas (notepad, word) para desplegar archivos de texto.

2.5. Expresiones.

Un carácter en una expresión es automáticamente convertido a entero.

Así entonces la construcción de expresiones que involucren variables o constantes de tipo

carácter son similares a las que pueden plantearse para enteros. Sin embargo las construcciones

más frecuentes son las comparaciones.

La especificación de tipo char es signed char.

Puede verificarse cómo son tratados los enteros con signo negativo por el compilador que se

está empleando, observando los resultados de: printf(" %c\n",-23); Debe producir la letra

acentuada: é.

Un compilador moderno también debería imprimir las letras acentuadas, por ejemplo:

printf(" %c\n",'é');

El siguiente par de for anidados muestra 8 renglones de 16 caracteres cada uno, con los

caracteres que tienen valores negativos (el bit más significativo del byte es uno).

for (i = -128; i<0; i++)

{ for (j = 0; j<16; j++) printf("%c ", i++); putchar('\n'); }

Observando la salida, la que dependerá del compilador empleado, pueden comprobarse los

caracteres (con valores negativos) que serán representados gráficamente.

Cuando se desea tratar el contenido de un byte (independiente del valor gráfico) debe emplearse

unsigned char.

Si se desea obtener el entero i que es representado por un carácter c, conviene emplear:

i = c –'0'; en lugar de: i = c - 48; o i = c - 0x30; ya que el código generado resulta

independiente del set de caracteres (ASCII, EBCDIC). La primera expresión asume que los

valores de los caracteres, que representan dígitos decimales, están asociados a enteros

consecutivos y ordenados en forma ascendente.

Es preferible escribir: i = (int) (c – '0'); que destaca que se está efectuando un conversión de

tipos. Pero lo anterior no suele encontrarse en textos escritos por programadores

experimentados.

Si se desea comparar si el carácter c es igual a una determinada constante, conviene la

expresión: (c == 'b') en lugar de (c == 98).

La expresión ('a' - 'A') toma valor (97 – 65) = 32. Valor que expresado en binario es:

00100000 y en hexadecimal 0x20.

16 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Si con esta máscara se efectúa un or con una variable c de tipo carácter: c | ('a' - 'A')

la expresión resultante queda con el bit en la quinta posición en uno(esto conviniendo, como es

usual, que el bit menos significativo ocupa la posición cero, el más derechista).

La expresión: c |= ('a' - 'A') si c es una letra la convierte en letra minúscula.

La expresión: c & ~ ('a' - 'A') forma un and con la máscara binaria 11011111 y la expresión

resultante deja un cero en el bit en la quinta posición.

La palabra máscara recuerda algo que se pone delante del rostro, y en este caso es una buena

imagen de la operación que realiza. Nótese que con un or con una máscara pueden setearse

determinadas posiciones con unos; y con un and, con una máscara, pueden dejarse determinados

bits en cero.

Las expresiones anteriores pueden emplearse para convertir letras minúsculas a mayúsculas y

viceversa.

for (c='a'; c<='z'; c++) { bloque }

permite ejecutar repetidamente un bloque de acciones, variando c desde la a hasta la z cada vez

que se realiza el bloque.

Esto asume que las letras recorridas en orden alfabético, están ordenadas en una secuencia

numérica ascendente.

En la parte de incremento del for, la conversión automática a entero no requiere un casteo

explícito: ( (int) c )++

La condición:

(c != ' ' && c != '\t' && c!= '\n')

es verdadera(toma valor 1) si c no es un separador.

Por De Morgan, la condición:

(c == ' ' || c == '\t' || c== '\n') es verdadera si c es separador.

Más adelante se ilustran otros ejemplos de expresiones de tipo char.

2.6. Entrada-Salida

En el lenguaje Pascal la entrada y salida son acciones. En C, son invocaciones a funciones o

macros de la biblioteca estándar.

Las siguientes descripciones son abstracciones (simplificaciones) del código que efectivamente

se emplea.

int putchar (int c)

{ return putc (c, stdout); }

Convierte c a unsigned char y lo escribe en la salida estándar (stdout); retorna el carácter escrito,

o EOF, en caso de error.

Apéndice 2. Introducción al lenguaje C. 17

Profesor Leopoldo Silva Bijit 20-01-2010

int getchar (void)

{ return getc (stdin); }

Lee desde la entrada estándar el siguiente carácter como unsigned char y lo retorna convertido a

entero; si encuentra el fin de la entrada o un error retorna EOF.

La entrada estándar normalmente es el teclado, y la salida la pantalla. Sin embargo pueden

redirigirse la entrada y la salida desde o hacia archivos.

El valor de la constante EOF predefinida en <stdio.h> es un valor entero, distinto a los valores

de los caracteres que pueden desplegarse en la salida, suele ser –1. EOF recuerda a end of file

(debió ser fin del stream o flujo de caracteres). Por esta razón getchar, retorna entero, ya que

también debe detectar el EOF.

La condición: ( (c = getchar( ) ) != EOF ) con c de tipo int.

obtiene un carácter y lo asigna a c (la asignación es una expresión que toma el valor del lado

izquierdo); este valor es comparado con el EOF, si son diferentes, la condición toma valor 1,

que se interpreta como valor verdadero. Los paréntesis son obligatorios debido a que la

precedencia de != es mayor que la del operador =.

Sin éstos, la interpretación sería:

c = (getchar( ) != EOF), lo cual asigna a c sólo valores 0 ó 1.

Para copiar el flujo de entrada hacia la salida, puede escribirse:

while ((c = getchar( )) != EOF) putchar(c);

Para contar en n las veces que se presenta el carácter t, puede escribirse:

while ((c = getchar( )) != EOF) if (c == t ) n++;

Entrada y salida con formato.

La siguiente función ilustra el uso de printf para caracteres:

void print_char (unsigned char c)

{

if (isgraph(c)) printf("'%c'\n", c); else printf("'\\%.3o'\n", c);

}

Dentro del string de control del printf, una especificación de conversión se inicia con el carácter

% y termina con un carácter. El carácter c indica que una variable de tipo int o char se imprimirá

como un carácter. Nótese que los caracteres que están antes y después de la especificación de

conversión se imprimen de acuerdo a su significado.

Los siguientes valores del argumento actual son imprimibles (isgraph retorna verdadero).

print_char(0x40); imprime en una línea: '@'

print_char(65); imprime en una línea: 'A'

18 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Los siguientes valores del argumento actual se imprimen como tres cifras octales.

print_char(06); imprime en una línea: '\006'

print_char(0x15); imprime en una línea: '\025'

Entre el % y el carácter de conversión a octal o, pueden encontrarse otros especificadores:

- especifica ajuste a la izquierda.

n.m donde n es el número del ancho mínimo del campo y m el número máximo de caracteres

que será impreso. Si el campo es más ancho, se rellena con espacios.

Si el string de control del printf que se ejecuta asociado al else se modifica a:

"'\\x%.2x'\n" Se pasa hacia la salida '\x y luego dos caracteres hexadecimales

debido al carácter de conversión x.

Con esta modificación:

print_char( '\n'); imprime en una línea: '\x0a'

print_char(0x15); imprime en una línea: '\x15'

Debe notarse que el ancho y el máximo número de caracteres se refieren a la variable o

expresión que será convertida.

printf("'%4.1c'\n", 'A' +1) imprime ' B'

printf("'%-4.1c'\n",'3' -1) imprime '2 '

2.7. Funciones.

Las siguientes funciones transforman letras minúsculas a mayúsculas y viceversa.

char toupper(register int c)

{

if((char)c <= 'z' && (char)c >= 'a') c &= ~('a' - 'A');

return (char)c;

}

char tolower(int c)

{

if((char)c <= 'Z' && (char)c >= 'A') c |= ('a' - 'A');

return (char)c;

}

La siguiente es una función que retorna verdadero si el carácter es imprimible:

int isgraph (int c)

{

return((unsigned char)c >= ' ' && (unsigned char)c <= '~');

}

La siguiente es una función que retorna verdadero si el carácter es un dígito decimal:

Apéndice 2. Introducción al lenguaje C. 19

Profesor Leopoldo Silva Bijit 20-01-2010

int isdig(int c)

{

return((unsigned char)c >= '0' && (unsigned char)c <= '9');

}

La función recursiva printd imprime un número decimal.

Primero el signo, luego la cifra más significativa. Lo logra reinvocando a la función con un

argumento que trunca, mediante división entera la última cifra. De este modo la primera función

(de las múltiples encarnaciones) que termina, es la que tiene como argumento n a la cifra más

significativa, que es menor que 10, imprimiendo dicho valor, a través de la conversión de entero

a carácter. Es necesario tomar módulo 10, para imprimir las cifras siguientes.

Igual resultado se logra con: printf("%d", n).

void printd(int n)

{ if (n<0) putchar('-') n= -n;

if(n/10) printd(n/10);

putchar(n % 10 + '0');

}

La función prtbin imprime en binario un entero de 16 bits.

void prtbin(int i)

{ int j;

for (j=15; j>=0; j--) if(i &(1<<j) ) putchar('1'); else putchar('0');

}

La función prtstr imprime un string. Igual resultado se logra con printf("%s", s).

void prtstr(char *s)

{ while(*s) putchar(*s++); }

La traducción a assembler de putchar ocupa alrededor de 10 instrucciones, y la utilización de

printf emplea algunos cientos de instrucciones. Algunas de las rutinas anteriores pueden ser

útiles cuando no se dispone de una gran cantidad de memoria, como en el caso de

microcontroladores.

2.8. Macros.

Es un mecanismo que permite el reemplazo de símbolos en el texto fuente. Lo efectúa el

preprocesador antes de iniciar la compilación.

Pueden ser con y sin argumentos formales.

Cuando no se emplean argumentos, permite asignar un valor a una constante. Esto puede

emplearse para mejorar la legibilidad de un programa.

Se escriben:

#define <token> <string>

20 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Todas las ocurrencias del identificador <token> en el texto fuente serán reemplazadas por el

texto definido por <string>. Nótese que no hay signo igual, y que no se termina con punto y

coma.

También las emplea el sistema para representar convenientemente y en forma estándar algunos

valores.

Por ejemplo en limits.h figuran entre otras definiciones, las siguientes:

#define CHAR_BIT 8

#define SCHAR_MAX 127

#define SCHAR_MIN (-128)

#define UCHAR_MAX 255

En float.h, se definen entre otras:

#define DBL_MIN 2.2250738585072014E-308

#define FLT_MIN 1.17549435E-38F

En ctype.h, se encuentran entre otras:

#define _IS_SP 1 /* is space */

#define _IS_DIG 2 /* is digit indicator */

#define _IS_UPP 4 /* is upper case */

#define _IS_LOW 8 /* is lower case */

#define _IS_HEX 16 /* [0..9] or [A-F] or [a-f] */

#define _IS_CTL 32 /* Control */

#define _IS_PUN 64 /* punctuation */

En values.h, se encuentran entre otras:

#define MAXINT 0x7FFF

#define MAXLONG 0x7FFFFFFFL

#define MAXDOUBLE 1.797693E+308

#define MAXFLOAT 3.37E+38

#define MINDOUBLE 2.225074E-308

Si se desean emplear constantes predefinidas por el sistema, debe conocerse en cual de los

archivos del directorio include están definidas. Y antes de que sean usadas debe indicarse en una

línea la orden de inclusión, para que el preprocesador incorpore el texto completo de ese archivo

en el texto fuente previo al proceso de compilación.

Por ejemplo:

#include <ctype.h>

Los paréntesis de ángulo indican que el archivo está ubicado en el subdirectorio include. Si se

desea tener archivos definidos por el usuario, el nombre del archivo que debe incluirse debe

estar encerrado por comillas dobles.

Apéndice 2. Introducción al lenguaje C. 21

Profesor Leopoldo Silva Bijit 20-01-2010

2.9. Macros con argumentos.

Se suelen emplear para definir macroinstrucciones (de eso deriva su nombre), es decir una

expresión en base a las acciones primitivas previamente definidas por el lenguaje. La macro se

diferencia de una función en que no incurre en el costo de invocar a una función (crear un frame

con espacio para los argumentos y variables locales en el stack, salvar registros y la dirección de

retorno; y luego recuperar el valor de los registros salvados, desarmar el frame y seguir la

ejecución).

Su real efectividad está limitada a situaciones en las que el código assembler que genera es

pequeño en comparación con el costo de la administración de la función equivalente. O también

cuando se requiere mayor velocidad de ejecución, no importando el tamaño del programa

ejecutable.

#define <macro> ( <arg1>, ... ) <string>

Los identificadores arg1, ... son tratados como parámetros del macro. Todas las instancias de los

argumentos son reemplazadas por el texto definido para arg1,.. cuando se invoca a la macro,

mediante su nombre. Los argumentos se separan por comas.

Por ejemplo:

#define ISLOWER(c) ('a' <= (c) )&& (c) <= 'z')

#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))

Si en el texto fuente aparece ISLOWER( 'A') dentro de una expresión, antes de la compilación

el preprocesador cambia el texto anterior por: ('a' <= ('A') && ('A') <= 'z'). El objetivo de esta

macro es devolver un valor verdadero (valor numérico 1) si el carácter c tiene un valor numérico

entre los valores del código asociados al carácter 'a' y al carácter 'z'; en caso contrario, la

expresión lógica toma valor falso (valor numérico 0).

Si aparece TOUPPER('d') éste es reemplazado por el texto:

(('a' <= ('d') && ('d') <= 'z') ? 'A' + (('d') - 'a') : ('d'))

TOUPPER convierte a mayúsculas un carácter ASCII correspondiente a una letra minúscula.

La definición debe estar contenida en una línea. En caso de que el string sea más largo, se

emplea el carácter \ al final de la línea.

#define ctrl(c) ((c) \

< ' ') /* string continua desde línea anterior */

Lo cual es equivalente a:

#define isctrl(c) ((c) < ' ')

Macro que toma valor 1 si el carácter c es de control.

El siguiente macro sólo puede aplicarse si se está seguro que el argumento representa un

carácter que es una letra. Entre 0x41 y 0x51. También da resultado correcto si la letra es

minúscula (entre 0x60 y 0x7a).

22 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

#define TOLOWER(c) ( (c) | 0x20)

Otros ejemplos de macros:

#define isascii(c) ( !( (c) &~0x7F))

#define toascii(c) ( (c) & 0x7F)

Nótese que en el string que define el texto que reemplaza al macro, los argumentos se colocan

entre paréntesis.

2.10. Biblioteca. ctype.c

Prototipos en include/ctype.h

El diseño de la biblioteca ctype se efectúa mediante una tabla de búsqueda, en la cual se

emplean macros para clasificar un carácter. La tabla es un arreglo de bytes(unsigned char) en los

que se codifica en cada bit una propiedad del carácter asociado.

El nombre de cada macro pregunta si el carácter es de cierta clase, por ejemplo si es símbolo

alfanumérico el nombre es isalnum. Cada macro retorna un valor diferente de cero en caso de

ser verdadero y cero en caso de ser falso.

Se define el concepto asociado a cada bit.

#define _U 0x01 /* Upper. Mayúsculas */

#define _L 0x02 /* Lower. Minúsculas */

#define _N 0x04 /* Número decimal */

#define _S 0x08 /* Espacio */

#define _P 0x10 /* Puntuación */

#define _C 0x20 /* Control */

#define _X 0x40 /* Hex */

En funciones de biblioteca, se suelen preceder los identificadores por un _(underscore o línea de

subrayado); de este modo se evita el alcance de nombres con identificadores definidos por el

usuario (siempre que éste no emplee como primer símbolo para sus identificadores el subrayado

_).

El siguiente arreglo contiene la información de atributos de cada carácter, indexada por su valor

numérico ascii +1.

const unsigned char _ctype_[129] = {

0, /*retorna falso para EOF */

_C, _C, _C, _C, _C, _C, _C, _C,

_C, _C| _S, _C| _S, _C| _S, _C| _S, _C| _S, _C, _C, /* bs, tab, lf, vt, ff, cr, so, si */

_C, _C, _C, _C, _C, _C, _C, _C,

_C, _C, _C, _C, _C, _C, _C, _C,

_S, _P, _P, _P, _P, _P, _P, _P, /* space, !, ", #, $, %, &, ' */

_P, _P, _P, _P, _P, _P, _P, _P, /* (, ), *, +, , , -, . , / */

_N, _N, _N, _N, _N, _N, _N, _N, /* 0, 1, 2, 3, 4, 5, 6, 7 */

Apéndice 2. Introducción al lenguaje C. 23

Profesor Leopoldo Silva Bijit 20-01-2010

_N, _N, _P, _P, _P, _P, _P, _P, /* 8, 9, :, ;, <, =, >, ? */

_P, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U, /*@,A,B,C,D,E,F,G */

_U, _U, _U, _U, _U, _U, _U, _U, /* H, I, J, K, L, M, N, O */

_U, _U, _U, _U, _U, _U, _U, _U, /* P, Q, R, S, T, U, V, W */

_U, _U, _U, _P, _P, _P, _P, _P, /* X, Y, Z, [, \, ], [ , ^, _ */

_P, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L, /* `, a, b, c, d, e, f, g */

_L, _L, _L, _L, _L, _L, _L, _L, /* h, i, j, k, l, m, n, o */

_L, _L, _L, _L, _L, _L, _L, _L, /* p, q, r, s, t, u, v, w */

_L, _L, _L, _P, _P, _P, _P, _C /* x, y, z, {, |, }, ~, DEL */

};

Si el valor entero del carácter es –1(EOF), al sumarle 1, resulta índice 0 para la tabla de

búsqueda. Si se buscan los atributos en la entrada 0 del arreglo; se advierte que tiene definido

valor cero, resultando con retornos falsos de los macros para EOF.

Se escoge para EOF un valor numérico diferente a los imprimibles.

isascii está definida para valores enteros. El resto de los macros están definidos sólo cuando

isaccii es verdadero o si c es EOF.

#define isascii(c) ( (unsigned)(c) < 128)

#define iscntrl(c) (_ctype_[(unsigned char) (c) + 1]&_C)

#define isupper(c) (_ctype_[(unsigned char) (c) + 1]&_U)

#define islower(c) (_ctype_[(unsigned char) (c) + 1]&_L)

#define isalpha(c) (_ctype_[(unsigned char) (c) + 1]&(_U | _L))

#define isdigit(c) (_ctype_[(unsigned char) (c) + 1]&_N)

#define isxdigit(c) (_ctype_[(unsigned char) (c) + 1]&(_N | _X))

#define isalnum(c) (_ctype_[(unsigned char) (c) + 1]&(_U | _L | _N))

#define isspace(c) (_ctype_[(unsigned char) (c) + 1]&_S)

#define ispunct(c) (_ctype_[(unsigned char) (c) + 1]&_P)

#define isprint(c) (_ctype_[(unsigned char) (c) + 1]&(_P | _U | _L | _N | _S))

#define isgraph(c) (_ctype_[(unsigned char) (c) + 1]&(_P | _U | _L | _N))

Los caracteres considerados gráficos no contemplan la categoría espacio (_S); pero sí están

considerados en la de imprimibles.

En la categoría espacios se consideran los caracteres de control: tab, lf, vt, ff, cr

Las letras de las cifras hexadecimales se consideran en mayúsculas y minúsculas.

Con el macro siguiente, que pone en uno el bit en posición dada por bit:

# define _ISbit(bit) (1 << (bit))

La definición de atributos puede efectuarse según:

#define _U _ISbit(0) /* Upper. Mayúsculas */

#define _L _ISbit(1) /* Lower. Minúsculas */

#define _N _ISbit(2) /* Número decimal */

#define _S _ISbit(3) /* Espacio */

#define _P _ISbit(4) /* Puntuación */

24 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

#define _C _ISbit(5) /* Control */

#define _X _ISbit(6) /* Hex */

Los códigos de biblioteca suelen ser más complejos que los ilustrados, ya que consideran la

portabilidad. Por ejemplo si el macro que define un bit en determinada posición de una palabra,

se desea usar en diferente tipo de procesadores, se agrega texto alternativo de acuerdo a la

característica.

Las órdenes de compilación condicional seleccionan, de acuerdo a las condiciones, la parte del

texto fuente que será compilada.

Por ejemplo: Si se desea marcar uno de los bits de una palabra de 16 bits, el macro debe

considerar el orden de los bytes dentro de la palabra.

# if __BYTE_ORDER == __BIG_ENDIAN

# define _ISbit(bit) (1 << (bit))

# else /* __BYTE_ORDER == __LITTLE_ENDIAN */

# define _ISbit(bit) ((bit) < 8 ? ((1 << (bit)) << 8) : ((1 << (bit)) >> 8))

# endif

3. Strings.

Se describen las rutinas que manipulan strings, cuyos prototipos se encuentran en string.h

#include <string.h>

typedef unsigned size_t; /* define tipo requerido por sizeof */

#define NULL 0 /* terminador nulo */

3.1. Definición de string.

3.1.1. Arreglo de caracteres.

La siguiente definición reserva espacio para un string como un arreglo de caracteres.

La definición de un string como arreglo de caracteres, debe incluir un espacio para el carácter

fin de string (el carácter NULL). Quizás es mejor definir el terminador de string como null, para

evitar confusiones con el valor de un puntero nulo.

char string[6]; /*crea string con espacio para 6 caracteres. Índice varía entre 0 y 5 */

string[5] = NULL;

Apéndice 2. Introducción al lenguaje C. 25

Profesor Leopoldo Silva Bijit 20-01-2010

string

\0

Figura A2.2. Arreglo de caracteres.

El nombre del string es un puntero constante que apunta al primer carácter del string. Por ser

constante no se le puede asignar nuevos valores o modificar.

3.1.2. Puntero a carácter.

La definición de un string como un puntero a carácter, puede ser inicializada asignándole una

constante de tipo string. La que se define como una secuencia de cero o más caracteres entre

comillas dobles; el compilador agrega el carácter „\0‟ automáticamente al final.

Si dentro del string se desea emplear la comilla doble debe precedérsela por un \.

En caso de escribir, en el texto de un programa, un string de varias líneas, la secuencia de un \ y

el retorno de carro(que es invisible en la pantalla) no se consideran parte del string.

char * str1 = "abcdefghi"; /* tiene 10 caracteres, incluido el NULL que termina el string.*/

Un argumento de tipo puntero a carácter puede ser reemplazado en una lista de parámetros, en

la definición de una función por un arreglo de caracteres sin especificar el tamaño. En el caso

del ejemplo anterior: char str1[ ]. La elección entre estas alternativas suele realizarse según sea

el tratamiento que se realice dentro de la función; es decir, si las expresiones se elaboran en base

a punteros o si se emplea manipulación de arreglos.

3.2. Strcpy.

Copia el string fuente en el string destino.

Se detiene la copia después de haber copiado el carácter nulo del fin del string.

Retorna la dirección del string destino.

char *strcpy(char * destino, register const char * fuente)

{ register char * cp= destino;

while(*cp++ = *fuente++) continue;

return destino;

}

26 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Figura A2.3. Copia de strings.

El diagrama ilustra los punteros fuente y cp, después de haberse realizado la copia del primer

carácter. Se muestra el movimiento de copia y el de los punteros.

Cuando el contenido de *fuente es el carácter NULL, primero lo copia y la expresión resultante

de la asignación toma valor cero, que tiene valor falso para la condición, terminando el lazo

while.

La instrucción continue puede aparecer en el bloque de acciones de un while, do o for. Su

ejecución lleva a reevaluar la condición de continuación del bloque de repetición más interno

(en caso de bloques anidados). En el caso de la función anterior podría haberse omitido la

instrucción continue; ya que un punto y coma se considera una acción nula.

El operador de postincremento opera sobre un left value(que recuerda un valor que puede

colocarse a la izquierda de una asignación). Un lvalue es un identificador o expresión que está

relacionado con un objeto que puede ser accesado y cambiado en la memoria.

El uso de estos operadores en expresiones produce un efecto lateral, en el sentido que se

efectúan dos acciones. Primero se usa el valor del objeto en la expresión y luego éste es

incrementado en uno.

El operador de indirección ( el *) y el operador ++ tienen la misma precedencia, entonces se

resuelve cuál operador recibe primero el operando mediante su asociatividad, que en el caso de

los operadores unarios es de derecha a izquierda. Es decir *fuente++ se interpreta según: ( *

(fuente++) ) . La expresión toma el valor del puntero fuente y lo indirecciona, posteriormente

incrementa en uno al puntero. En la expresión (* fuente) ++, mediante el uso de paréntesis se

cambia la asociatividad, la expresión toma el valor del objeto apuntado por fuente, y luego

incrementa en uno el valor del objeto, no del puntero.

Puede evitarse la acción doble relacionada con los operadores de pre y postincremento, usando

éstos en expresiones que sólo contengan dichos operadores. En el caso de la acción de

repetición: while(*cp++ = *fuente++) continue;

Puede codificarse: while( *cp = *fuente) {cp++, fuente++};

Sin embargo los programadores no suelen emplear esta forma. Adicionalmente no producen

igual resultado, ya que en la primera forma los punteros quedan apuntando una posición más

allá de los caracteres de fin de string; la segunda forma deja los punteros apuntando a los

terminadores de los strings. Ambas formas satisfacen los requerimientos de srtcpy.

fuente cp

destino

Apéndice 2. Introducción al lenguaje C. 27

Profesor Leopoldo Silva Bijit 20-01-2010

La primera forma sólo tendría ventajas si el procesador tiene mecanismos de direccionamientos

autoincrementados, y si el compilador emplea dichos mecanismos al compilar la primera forma.

Cuando en la lista de parámetros de una función aparece la palabra reservada const precediendo

a una variable de tipo puntero, el compilador advierte un error si la función modifica la variable

a la que el puntero apunta. Además cuando se dispone de diferentes tipos de memorias (RAM,

EEPROM o FLASH) localiza las constantes en ROM o FLASH. Si se desea que quede en un

segmento de RAM, se precede con volatile, en lugar de const.

No se valida si el espacio a continuación de destino puede almacenar el string fuente sin

sobreescribir en el espacio asignado a otras variables. Este es un serio problema del lenguaje, y

se lo ha empleado para introducir código malicioso en aplicaciones que no validen el rebalse de

buffers.

Ejemplo:

#include <string.h>

#include <stdio.h>

char string[10]; /*crea string con espacio para 10 caracteres */

char * str1 = "abcdefghi"; /* tiene 10 caracteres, incluido el NULL que termina el string.*/

int main(void)

{ strcpy(string, str1); printf("%s\n", string);

return 0;

}

3.3. Strncpy.

strncpy copia n caracteres desde el string fuente hacia el string destino. Si el string fuente tiene

menos de n caracteres rellena con nulos hasta completar la copia de n caracteres.

Si el string fuente tiene n o más caracteres el string destino no queda terminado en un nulo.

char *strncpy(register char * destino, register const char * fuente, register size_t n )

{ register char * cp = destino;

while( n ) { n--; if (! (*cp++ = *fuente++) ) break; }

while( n--) *cp++ = 0;

return destino;

}

La sentencia break termina el bloque de repetición (más interno, si existen estructuras

repetitivas anidadas), y pasa a ejecutar la instrucción siguiente al bloque.

Si se copia el fin del string fuente se activa el break y comienza el segundo while que produce el

relleno de nulos. Si se copian n caracteres en el primer while, el segundo no se efectúa, ya que

se entra a éste con un valor cero de n.

28 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

3.4. Strcat.

Concatena una copia del string fuente luego del último carácter del string destino.

El largo del string resultante es la suma: strlen(dest) + strlen(src).

Retorna un puntero al string destino, que ahora contiene la concatenación.

char *strcat(register char * destino, register const char * fuente)

{ register char *cp= destino;

while(*cp) cp++;

while(*cp++ = *fuente++) continue; /*

return destino;

}

El primer while deja el puntero cp apuntando al carácter de fin del string destino. Luego el

segundo while efectúa la copia, sobreescribiendo el NULL del string destino, con el primer

carácter del string fuente.

La función también concatena un string fuente nulo.

Ejemplo:

#include <string.h>

#include <stdio.h>

char destino[25];

char *espacio = " ", *fin = "Final", *destino = "Inicio";

int main(void)

{ strcat(destino, espacio); strcat(destino, fin);

printf("%s\n", destino);

return 0;

}

Figura A2.4. Concatena strings.

3.5. Strncat.

strncat concatena al final del string destino a lo más n caracteres del string fuente.

cp

destino

fuente

Apéndice 2. Introducción al lenguaje C. 29

Profesor Leopoldo Silva Bijit 20-01-2010

char *strncat(register char * destino, register const char * fuente, register size_t n )

{ register char * cp = destino;

while(*cp) cp++; /*apunta al final del string destino */

while( n && (*cp++ = *fuente++) ) n--;

if( n == 0) *cp = 0;

return destino;

}

Si fuente tiene menos de n caracteres, el segundo operador del and copia el fin de string. Si el

string fuente tiene n o más caracteres, es el primer operando del and es el que da por terminado

el segundo while; saliendo de éste con un valor cero de n. Es para este último caso que está el if

final que escribe el terminador del string destino.

El máximo largo del string destino resultante es strlen(destino al inicio) + n.

Agrega una porción del string fuente a continuación del string destino.

3.6. Strlen.

Largo de un string.

Retorna el número de caracteres del string s. No cuenta el carácter de terminación.

size_t strlen(const char * s)

{ register const char * cp= s;

while(*cp++) continue;

return (cp-1) - s;

}

El while termina cuando *cp tiene valor cero. Pero debido al operador de postincremento, al

salir del while, cp queda apuntado una posición más allá de la posición que ocupa el NULL.

Entonces cp-1, apunta al NULL. Y la resta de punteros, produce un entero como resultado; éste

da el número de caracteres del string. Si a s se le suman 5, se tiene el valor del puntero que

apunta al terminador del string, en el caso del ejemplo que se ilustra a continuación; entonces

(cp-1) – s resulta 5 en este caso.

Figura A2.5. Largo string.

3.7. Strcmp.

Compara dos strings.

s

\0 cp

30 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

La comparación comienza con el primer carácter de cada string y continua con los caracteres

subsecuentes hasta que los caracteres correspondientes difieren o hasta que se llegue al final de

uno de los strings.

int strcmp(register const char * s1, register const char * s2)

{ register signed char r;

while( !( r = *s1 - *s2++) && *s1++) continue;

return r;

}

Retorna un valor entero:

Menor que cero si s1 < s2

Igual a cero si s1 == s2

Mayor que cero si s1 > s2

Si los caracteres *s1 y *s2 son iguales, r es cero; y el valor lógico del primer operando del and

es verdadero. Si son diferentes, termina el lazo de repetición. Si el valor de *s2 es mayor que el

valor de *s1, r será negativo; implicando que el string s2 es "mayor" que el string s1. Si *s2 es

el carácter NULL, r será positivo si *s1 no es cero, terminando el while; implicando que s1 >

s2. Si *s1 es el carácter NULL, r será negativo si *s2 no es cero, terminando el while;

implicando que s1 < s2.

3.8. Strncmp.

Strncmp compara hasta n caracteres de los strings s1 y s2.

La comparación comienza con el primer carácter de cada string y continua con los caracteres

siguientes hasta que éstos difieran o hasta que se hayan revisado n.

int strncmp(register const char * s1, register const char * s2, size_t n)

{

while( n--) { if(*s1 == 0 || *s1 != *s2) return (*s1 - *s2); s1++; s2++; }

return 0;

}

Para los primeros n caracteres, si los caracteres difieren o se llegó al fin del string s1 se retorna

la resta del valor entero asociado a los caracteres.

Si s2 es más corto que s1, los caracteres difieren ya que *s2 es cero, y el retorno será positivo;

indicando s1 > s2.

3.9. Strstr.

Strstr encuentra la primera ocurrencia de un substring s2 en un string s1.

Si encuentra s2 en s1, retorna un puntero al carácter de s1 en que se inicia el substring que es

igual a s2; si no lo encuentra retorna un puntero nulo.

char *strstr(register const char * s1, register const char * s2)

{

while(s1 && *s1)

Apéndice 2. Introducción al lenguaje C. 31

Profesor Leopoldo Silva Bijit 20-01-2010

{ if(strncmp(s1, s2, strlen(s2)) == 0) return (char *)s1;

s1 =strchr(s1+1, *s2);

}

return (char *) 0;

}

La condición del if es verdadera si s2 está contenido en s1, con retorno exitoso. Si no lo

encuentra busca el primer carácter de s2(mediante strchr) a partir del siguiente carácter de s1; si

lo encuentra avanza s1 hasta esa coincidencia; si no lo encuentra s1 será un puntero nulo, dando

término al while(ya que el primer operando del and será falso). También termina el while si s1

es un string nulo.

3.10. Strchr.

Busca la primera ocurrencia de un carácter c en el string s.

Si lo encuentra retorna un puntero al carácter c; en caso contrario, si c no está presente en s,

retorna un puntero nulo.

char *strchr(register const char * s, int c)

{ while( *s ) { if( *s == (char) c ) return (char *) s; s++;}

return (char *) 0;

}

El tipo char es tratado como entero con signo de 8 bits(-128 a +127) y es promovido

automáticamente a tipo entero. Sin embargo se emplea un conversión explícita de entero a char

mediante el molde o cast: (char) c

Debido a que el parámetro formal se trata como puntero a una constante string(para evitar que la

función modifique a s), se debe efectuar una conversión del tipo de puntero para el retorno, se

pasa a puntero a carácter (char *) el puntero a string constante: const char *.

La búsqueda de c en s es hacia adelante (de izquierda a derecha, o de arriba hacia abajo).

Si s apunta al terminador del string, la expresión *s tiene valor cero, y la condición del while es

falsa, por lo tanto no busca el terminador del string.

El fin de string(terminador nulo) es parte del string. Si se desea que la búsqueda strchr(s, 0)

retorne un puntero al terminador nulo del string s, debe efectuarse la siguiente modificación:

char *strchr(register const char * s, int c)

{ while( *s ) { if( *s == (char) c ) return (char *) s; s++;}

if( *s == (char) c ) return (char *) s;

return (char *) 0;

}

La rutina modificada puede buscar el terminador del string incluso en un string nulo.

32 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

3.11. Strrchr.

strrchr encuentra la última ocurrencia del carácter c en el string s. Si encuentra c en s, retorna

un puntero al carácter encontrado; en caso contrario, un puntero nulo. La búsqueda la efectúa

en reversa.

char *strrchr(register const char * s, int c)

{ register const char * cp = s;

while(*s) s++; /* s queda apuntado al terminador */

while(s != cp) { s--; if(*s == (char)c ) return (char *)s;}

return (char *) 0;

}

Figura A2.6. Punteros después de primer while.

El diagrama ilustra los punteros una vez terminado el primer while.

El terminador nulo es considerado parte del string. Si se desea buscar el terminador del string

debe modificarse la rutina anterior, o utilizar strchar.

3.12. Strpbrk.

strpbrk busca en el string s1 la primera ocurrencia de cualquier carácter presente en s2; retorna

un puntero al primer encontrado, en caso que ningún carácter de s2 esté presente en s1 retorna

un puntero nulo.

char *strpbrk(register const char * s1, register const char * s2)

{

while(*s1) { if(strchr(s2, *s1)) return (char *)s1; s1++; }

return (char *)0;

}

3.13. Strcspn.

strcspn encuentra el segmento inicial del string s1 que no contiene caracteres que se encuentren

presentes en s2. Retorna el largo del segmento encontrado.

size_t strcspn(register const char * s1, register const char * s2)

{ register size_t i=0;

while(*s1 && !strchr(s2, *s1) ) {s1++; i++;}

cp

\0 s

Apéndice 2. Introducción al lenguaje C. 33

Profesor Leopoldo Silva Bijit 20-01-2010

return i;

}

A partir del primer carácter de s1 se revisa que éste no esté presente entre los caracteres que

forman s2.

3.14. Strspn.

strspn encuentra el segmento inicial del string s1 que solamente contiene caracteres que se

encuentren presentes en s2. Retorna el largo del segmento encontrado.

size_t strspn(register const char * s1, register const char * s2)

{ register size_t i = 0;

while(*s1 && strchr(s2, *s1) ) { s1++; i++;}

return i;

}

Se mantiene realizando el bloque mientras encuentre los caracteres de s1 en s2 y no sea fin de

string s1.

3.15. Strtok.

Strtok busca el primer substring de s1 que está antes del string s2 que se considera un

separador.

Se considera que s1 es un string formado por una secuencia de cero o más símbolos(tokens)

separados por el string s2.

La función strtkn permite obtener todos los tokens mediante llamados subsiguientes al primero.

Para esto el primer llamado debe retornar un puntero al primer token, y escribir un carácter

NULL en el string s1, inmediatamente después del último carácter del token que se retorna.

Los siguientes llamados deben realizarse con un puntero nulo en el primer argumento. El

separador s2 puede ser diferente para cada una de las siguientes invocaciones. Si no se

encuentran símbolos la función debe retornar un puntero nulo.

Como es una rutina que puede llamarse varias veces, su diseño debe incluir una variable estática

que permanezca entre llamados. Ya que los argumentos y variables locales sólo existen mientras

la función esté activa, debido a que se mantienen en un frame en el stack.

Figura A2.7. Strtok

s2 s2 s2 t1 t2 t2

sp

s1

34 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

El primer llamado a strtrk, debe retornar s1, colocando un nulo en el primer carácter de s2, y

fijar la posición de la estática sp inmediatamente después del terminador recién insertado.

Los llamados subsiguientes llevan un NULL en el primer argumento, correspondiente al puntero

al string s1; esto le indica a la función que debe emplear ahora sp para seguir buscando tokens.

El primer if, si s1 es un puntero nulo, fija s1 en el valor guardado en la estática sp por la

invocación anterior. Si el primer llamado se efectúa sobre un string nulo, debe estar inicializada

la estática sp.

El segundo if, retorna un puntero nulo, si el llamado inicial es con un puntero nulo, o si se agotó

la búsqueda de símbolos en llamados subsiguientes (encuentra sp con valor nulo).

char *strtok(register char * s1, register const char * s2)

{ static char * sp = NULL;

if(!s1) s1 = sp;

if(!s1) return NULL;

s1 += strspn(s1, s2); /* salta separador */

if(!*s1) return sp = NULL;

sp = s1 + strcspn(s1, s2);

if(*sp) *sp++ = '\0'; else sp = NULL;

return s1; /* puntero al token encontrado */

}

Mediante la función strspn se saltan los caracteres pertenecientes al separador s2, y s1 queda

apuntado al primer carácter siguiente al separador s2.

El tercer if, si se llegó al final del string, fija sp en puntero nulo, y retorna un puntero nulo.

No se efectúa la acción del tercer if, si quedan caracteres por escanear.

Mediante la función strcspn se fija sp una posición más allá del último carácter del token

encontrado.

El cuarto if, fija sp en puntero nulo, si se agotó la búsqueda (se efectúa el else); en caso,

contrario si quedan caracteres por escanear, coloca un carácter nulo para finalizar el token

encontrado, y avanza sp una posición más adelante.

El tipo del argumento de s1 no puede ser un puntero constante, ya que se escribe en el string.

Si por ejemplo el string s1 tiene el valor "abc, dd,efg" y si el separador s2 es el string ","; se

encuentra, después del primer llamado el token: abc. Luego del segundo (con primer argumento

NULL) el token: dd. Finalmente el símbolo: efg.

3.16. Strdup.

strdup crea un duplicado del string s, obteniendo el espacio con un llamado a malloc.

El espacio requerido es de (strlen(s) + 1) bytes, para incluir el terminador, el que es insertado

por strcpy.

Si malloc no puede asignar espacio retorna puntero nulo.

Apéndice 2. Introducción al lenguaje C. 35

Profesor Leopoldo Silva Bijit 20-01-2010

La función retorna un puntero al duplicado; en caso que falle malloc retorna un puntero nulo. El

programador es responsable de liberar el espacio ocupado cuando ya no sea necesario,

empleando la función free.

char *strdup(register const char * s)

{ register char * cp;

if(cp = (char *) malloc(strlen(s)+1)) strcpy(cp, s);

return cp;

}

Con las siguientes definiciones:

char *duplicado;

char *string = "1234";

Un ejemplo de uso es: duplicado = strdup(string);

Y para liberar el espacio: free(duplicado);

El prototipo de malloc es: void *malloc(size_t size);

Malloc retorna un puntero de tipo void o un puntero genérico. Se emplean cuando el compilador

no puede determinar el tamaño del objeto al cual el puntero apunta. Por esta razón los punteros

de tipo void deben ser desreferenciados mediante casting explícito.

El siguiente ejemplo muestra que a un puntero tipo void se le puede asignar la dirección de un

entero o un real. Pero al indireccionar debe convertirse el tipo void al del objeto que éste

apunta.

int x;

float r;

void *p;

p = &x; * (int *) p = 2;

p = &r; *(float *)p = 1.1;

Bloques de memoria.

El conjunto de funciones cuyos prototipos se encuentran en string.h también incluye un grupo

de funciones que manipulan bloques de memoria.

Los bloques son referenciados por punteros de tipo void, en la lista de argumentos. Luego en las

funciones se definen como variables locales punteros a caracteres, que son iniciados con los

valores de los punteros genéricos amoldados a punteros a carácter.

3.17. Memcpy.

memcpy Copia un bloque de n bytes desde la dirección fuente hacia la dirección destino.

36 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

void *memcpy(void * destino, const void * fuente, register size_t n)

{ register char *d = (char *)destino; register const char *s= (char *)fuente;

while(n--) *d++ = *s++;

return destino;

}

Si fuente y destino se traslapan la conducta de memcpy es indefinida. Ya que no se puede

sobreescribir el bloque fuente, que se trata como puntero genérico constante. Dentro de la

función se emplean punteros locales a caracteres.

3.18. Memccpy.

Copia no más de n bytes desde el bloque apuntado por fuente hacia el bloque apuntado por

destino, deteniéndose cuando copia el carácter c.

Retorna un puntero al bloque destino, apuntando al byte siguiente donde se copió c. Retorna

NULL si no encuentra a c en los primeros n bytes del bloque fuente.

void *memccpy (void *destino, const void *fuente, int c, size_t n)

{

register const char *s = (char *)fuente;

register char *d = (char *)destino;

register const char x = c;

register size_t i = n;

while (i-- > 0) if ((*d++ = *s++) == x) return d;

return NULL;

}

Se emplean locales de tipo registro para acelerar la copia. Nótese que los punteros de tipo void

de los argumentos son los valores iniciales de los registros con punteros a caracteres.

3.19. Memmove.

Con memmove, si los bloques apuntados por fuente y destino se traslapan, los bytes ubicados en

la zona de traslapo se copian correctamente.

void *memmove(void * destino, void * fuente, register size_t n)

{ register char *d= (char *)destino; register char *s = (char *)fuente;

if(s < d && s+n >=d) { s += n; d += n; do *--d = *--s; while(--n); }

else if(n) do *d++ = *s++; while(--n);

return destino;

}

La condición de traslapo del bloque s sobre el bloque d, se ilustra en el diagrama de más a la

derecha. En este caso se efectúa la copia en reversa. Se sobreescriben los últimos elementos del

bloque apuntado por s; es decir los que primero fueron copiados.

La negación lógica de la condición de traslapo, por De Morgan, es: (s>=d || s+n<d )

Apéndice 2. Introducción al lenguaje C. 37

Profesor Leopoldo Silva Bijit 20-01-2010

Los dos casos del or se ilustran en las dos figuras de más a la izquierda. En el caso que ilustra la

figura central, no hay traslapo.

En el caso s>=d, si d+n>=s, se produce traslapo y se sobreescriben las primeras posiciones del

bloque s, las que primero fueron copiadas (cuando se ejecuta el else, avanzado los punteros

hacia direcciones cada vez mayores).

Figura A2.8 Memmove.

Debido a que el bloque fuente puede ser sobrescrito, no puede ser un puntero vacío constante.

Los punteros a carácter s y d, son iniciados con los valores amoldados (cast) a punteros a

carácter de fuente y destino.

3.20. Memcmp.

memcmp compara los primeros n bytes de los bloques s1 y s2, como unsigned chars.

int memcmp(const void *s1, const void *s2, size_t n)

{ int i;

register const unsigned char *a1, *a2;

a1 = (unsigned char *)s1; a2 = (unsigned char *)s2;

while(n--) if( i = (int)(*a1++ - *a2++) ) return i;

return 0;

}

Valor de retorno menor que cero si s1 < s2

Valor de retorno igual a cero si s1 == s2

Valor de retorno mayor que cero si s1 > s2

d

s

n

s

d

d

s

n

s>=d s + n <d s < d && s+n >= d

38 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

3.21. Memset.

Rellena n bytes del bloque s con el byte c. Retorna puntero genérico al bloque.

void *memset(void * s, int c, register size_t n)

{ register char * p = (char *)s;

while(n--) *p++ = (char) c;

return s;

}

3.22. Movimientos de bloques, dependientes del procesador.

Efectuar movimientos de bloques orientados al carácter es ineficiente. Por esta razón las

funciones de movimiento tratan de mover palabras.

Primero se mueven los bytes parciales de una palabra, luego se pueden mover palabras

alineadas; para finalmente, copiar los bytes presentes en la última palabra.

Estas funciones, implementadas en base a macros para mejorar la velocidad, son dependientes

del procesador. Se requiere conocer el ancho de la palabra y el ordenamiento de los bytes dentro

de la palabra (little o big endian).

El siguiente ejemplo introduce en un entero de 32 bits, el carácter '4' en el byte más

significativo, luego el '3', después el '2', y el carácter '1' en el byte menos significativo. Luego

convierte la dirección de i en un puntero a carácter, e imprime el string de largo 4. En el string el

byte con la menor dirección queda más a la izquierda, y el byte con dirección mayor queda a la

derecha.

unsigned long int i;

if (sizeof (i) == 4)

{ i = (((((('4' << 8) + '3') << 8) + '2') << 8) + '1');

printf ("Orden de los Bytes = %.4s\n", (char *) &i);

} else printf (" \nNo es una máquina de 32 bits");

Los strings asociados reflejan el ordenamiento de los bytes dentro de la palabra. Suelen

denominarse:

LITTLE ENDIAN "1234"

BIG ENDIAN "4321"

PDP ENDIAN "3412"

El siguiente texto ilustra una función de movimiento por bloques, mostrando el grado de

complejidad de estas rutinas. Se invoca a varios macros, de los cuales sólo se da el nombre.

rettype

memmove (a1const void *a1, a2const void *a2, size_t len)

{

unsigned long int dstp = (long int) dest;

unsigned long int srcp = (long int) src;

Apéndice 2. Introducción al lenguaje C. 39

Profesor Leopoldo Silva Bijit 20-01-2010

/* This test makes the forward copying code be used whenever possible.

Reduces the working set. */

if (dstp - srcp >= len) /* *Unsigned* compare! */

{

/* Copy from the beginning to the end. */

/* If there not too few bytes to copy, use word copy. */

if (len >= OP_T_THRES)

{

/* Copy just a few bytes to make DSTP aligned. */

len -= (-dstp) % OPSIZ;

BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

/* Copy whole pages from SRCP to DSTP by virtual address

manipulation, as much as possible. */

PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);

/* Copy from SRCP to DSTP taking advantage of the known

alignment of DSTP. Number of bytes remaining is put

in the third argument, i.e. in LEN. This number may

vary from machine to machine. */

WORD_COPY_FWD (dstp, srcp, len, len);

/* Fall out and copy the tail. */

}

/* There are just a few bytes to copy. Use byte memory operations. */

BYTE_COPY_FWD (dstp, srcp, len);

}

else

{

/* Copy from the end to the beginning. */

srcp += len;

dstp += len;

/* If there not too few bytes to copy, use word copy. */

if (len >= OP_T_THRES)

{

/* Copy just a few bytes to make DSTP aligned. */

len -= dstp % OPSIZ;

BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ);

/* Copy from SRCP to DSTP taking advantage of the known

alignment of DSTP. Number of bytes remaining is put

in the third argument, i.e. in LEN. This number may

40 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

vary from machine to machine. */

WORD_COPY_BWD (dstp, srcp, len, len);

/* Fall out and copy the tail. */

}

/* There are just a few bytes to copy. Use byte memory operations. */

BYTE_COPY_BWD (dstp, srcp, len);

}

RETURN (dest);

}

4. Rutinas de conversión.

4.1. De enteros a caracteres. Ltoa. Long to Ascii.

Pasar de un número en representación interna a una secuencia de caracteres, permite desplegar

en la salida los valores de las variables de un programa.

La siguiente rutina convierte un entero largo, en representación interna, en una secuencia de

dígitos. Se dispone, como argumento de la función, de la base numérica en la que los números

se representarán en forma externa.

La función ocupa un buffer estático de 65 bits, lo cual permite convertir enteros de 64 bits en

secuencias binarias. Se considera en el buffer espacio para el signo y el terminador del string.

Para enteros de 16 bits, el rango de representación es: [-32768.. 32767] el cual requiere

de 5 char para representar mediante dígitos decimales.

Para enteros de 32 bits: [-2147483648 .. +2147483647] se requieren 10 chars para

dígitos.

Para enteros de 64 bits: [-9223372036854775808..+9223372036854775807] se

requieren 19 char para dígitos decimales. Para imprimir en binario se requieren 63

dígitos binarios.

El procedimiento consiste en sacar el módulo base del número, esto genera el último carácter

del número; es decir el menos significativo. Luego se divide en forma entera por la base,

quedando el resto; del cual se siguen extrayendo uno a uno los dígitos.

Por ejemplo para el entero 123 en base decimal, al sacar módulo 10 del número se obtiene el

dígito de las unidades, que es 3. Al dividir, en forma entera por la base, se obtiene el número de

decenas; es decir, 12. Sacando módulo 10 se obtiene 2; y al dividir por la base se obtiene el

número de centenas.

Apéndice 2. Introducción al lenguaje C. 41

Profesor Leopoldo Silva Bijit 20-01-2010

Si la base que se pasa como argumento es menor o igual a cero, se asume base decimal. Si la

base es mayor que 36, también se asume base decimal. Con base 36 se tienen los 10 dígitos

decimales y todas las letras como los dígitos del sistema.

La función retorna un puntero al primer carácter de la secuencia. Al inicio de la rutina se apunta

al final del buffer, a la posición de las unidades, ya que los dígitos se generan a partir de las

unidades.

El carácter de fin de string se coloca automáticamente al definir el buffer estático, ya que éste es

inicializado con ceros.

La función permite desplegar secuencias binarias y hexadecimales.

Para convertir un número entero a un carácter de un dígito decimal se suma el valor del carácter

„0‟; el valor asociado a „0‟ es 0x30, al „1‟ está asociado el 0x31. Para números mayores que 9

se suma el valor del carácter „7‟ (que es 55 decimal); de esta manera para 10, se obtiene: 10 +

55 = 65 que es el equivalente a „A‟.

#define INT_DIGITOS 63

static char buf[INT_DIGITOS + 2];

/* Buffer para INT_DIGITS dígitos, signo - y fin de string '\0' */

char * ltoa(long int i, unsigned int base)

{

char *p = buf + INT_DIGITOS + 1; /* apunta a posición de unidades */

int dig, signo=0;

if (i<0) {signo=1;i = -i;}

if(base<=0 || base>36) base=10; /*evita división por cero */

do { dig=(i%base); if (dig <=9) *--p = '0' + dig; else *--p= '7'+ dig ; i /= base;}

while (i != 0);

if(signo) *--p = '-';

return p;

}

Para convertir enteros se emplea la misma rutina anterior, invocando con una conversión

explícita del entero a largo.

char * itoa(int i, unsigned int base)

{

return (ltoa((long)i, base));

}

La siguiente rutina permite el despliegue del string, mediante putchar.

#include <stdio.h>

42 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

void prtstr(char * p)

{

while(*p) putchar(*p++);

}

El siguiente ejemplo ilustra el uso de las rutinas de conversión.

int main(void)

{

int i=-31;

long int l= -2147483647L;

prtstr( itoa(i,32) ); putchar('\n');

prtstr( ltoa(l,2) ); putchar('\n');

return (0);

}

4.2. De secuencias de caracteres a enteros.

Si bien esta rutina efectúa el trabajo inverso de la anterior, su diseño es más complejo, ya que

debe operar con datos suministrados por un ser humano. La anterior saca algo que está en

formato interno y que está bien especificado.

En la conversión de un string en un long integer, debe asumirse que el usuario provee una

secuencia de caracteres que tiene el siguiente formato:

[blancos*] [signo] [0] [x|X] [ddd]

Los corchetes indican elementos opcionales, el asterisco la repetición de cero o más veces. De

esta forma podrían digitarse caracteres espacios o tabuladores antes de la secuencia; podría

indicarse el signo +, y también declarar números octales si el primer dígito es cero, o

hexadecimales si los primeros dígitos son 0x ó 0X.

En el diseño se decide terminar de leer cuando encuentra un carácter que no cumpla el formato.

Para permitir seguir analizando la secuencia de entrada se decide pasar a la función, además de

un puntero al inicio de la secuencia a analizar, y de la base, la referencia a un puntero a carácter.

Al salir de la función se escribe, en la variable pasada por referencia, el valor del puntero que

apunta al carácter que detuvo el scan, por no cumplir el formato. Esto debido a que en C, sólo se

puede retornar un valor desde la función.

Además la función debe resolver que el valor del número ingresado no exceda el mayor

representable. Como los números se representan con signo en complemento a la base, se tendrán

valores máximos diferentes(en la unidad) para el máximo positivo y el máximo negativo.

Por ejemplo para enteros largos de 32 bits, el rango asimétrico de representación en base

decimal es [-2147483648..2147483647].

Apéndice 2. Introducción al lenguaje C. 43

Profesor Leopoldo Silva Bijit 20-01-2010

Entonces debe considerarse que cuando se tenga ingresada la secuencia 214748364(corte), si el

siguiente dígito(límite) es mayor que 7, para positivos; y mayor que 8, para negativos se

tendrá rebalse. Y se debe notificar con el código de error estándar: error de rango.

Los valores máximos de las representaciones se almacenan en limits.h, mediante estas

constantes se pueden calcular la secuencia de corte y el dígito límite, que permiten determinar si

se sobrepasa o no el rango de representación.

En el código siguiente se incorporan el texto de las funciones, macros y constantes, y se

comentan la inclusión de los archivos que las contienen.

Los macros de ctype.h se han simplificado, ya que suelen estar implementados mediante una

tabla de búsqueda.

En la variable local acc se va formando el número. La variable any se coloca en uno si se han

consumido caracteres desde la secuencia de entrada; y se le da un valor negativo si se produce

rebalse.

Como se lee la secuencia de izquierda a derecha, el núcleo del algoritmo consiste en multiplicar

el número acumulado en acc, por la base y luego sumarle el valor numérico del dígito.

Por ejemplo, la secuencia 123 en decimal, es procesada según:

0*10 + 1 = 1

1*10 + 2 = 12

12*10 + 3 = 123

Nótese que el segundo argumento de la función es un puntero a un puntero a carácter. En caso

que a la función se le pase un puntero no nulo, retornará la posición donde se detuvo el scan, o

la dirección de inicio de la secuencia si se excede el rango de representación.

El código de la rutina pertenece a la Universidad de California.

/*#include <limits.h>*/

#define LONG_MAX 0x7FFFFFFFL

#define LONG_MIN ((long)0x80000000L)

/*#include <ctype.h>*/

#define islower(c) (('a' <= (c) ) && ((c) <= 'z'))

#define isupper(c) (('A' <= (c) ) && ((c) <= 'Z'))

#define isdigit(c) (('0' <= (c) ) && ((c) <= '9'))

#define isspace(c) ((c) == ' ' || (c) == '\t' || (c)== '\n')

#define isalpha(c) ((islower(c))||(isupper(c)))

/*#include <errno.h>*/

#define ERANGE 34 /* Resultado demasiado grande. Rebalse de representación. */

extern int errno;

/* Copyright (c) 1990 The Regents of the University of California.*/

long strtol(const char *nptr, char **endptr, int base)

{ register const char *s = nptr;

44 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

register unsigned long acc;

register int c;

register unsigned long cutoff;

register int neg = 0, any, cutlim;

do {c = *s++;} while (isspace(c)); /* salta blancos*/

if (c == '-') { neg = 1; c = *s++;} /* si negativo, registra signo en neg */

else if (c == '+') c = *s++; /* salta signo + */

if ( (base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X') )

{c = s[1];s += 2; base = 16;} /*si base es 0 ó 16 consume 0x ó 0X y lee hex*/

if (base == 0) base = c == '0' ? 8 : 10;

/*si la base es cero, si el primer digito es cero lee octal, sino asume decimal */

/*Calcula el corte y el dígito límite, a partir de los máximos */

cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX;

cutlim = (int)(cutoff % (unsigned long) base);

cutoff /= (unsigned long) base;

for (acc = 0, any = 0; ; c = *s++) {

if (isdigit(c)) c -= '0';

else if (isalpha(c)) c -= (isupper(c)) ? 'A' - 10 : 'a' - 10; else break;

if (c >= base) break;

if (any < 0 || acc > cutoff || acc == cutoff && c > cutlim) any = -1;

else {any = 1; acc *= base; acc += c;}

}

if (any < 0) { acc = neg ? LONG_MIN : LONG_MAX; errno = ERANGE;}

else if (neg) acc = -acc;

if (endptr != 0) *endptr = any ? (char *)(s - 1) : (char *)nptr;

return (acc);

}

Las funciones atoi y atol se implementan en base a strtol.

En éstas, la base es 10, y no se pasa una referencia a un puntero a carácter.

/* Convierte un string en un entero. Ascii to integer. */

int atoi (const char *nptr)

{ return (int) strtol (nptr, (char **) NULL, 10); }

/* Convierte un string en un entero largo. Ascii to long */

long int atol (const char *nptr)

Apéndice 2. Introducción al lenguaje C. 45

Profesor Leopoldo Silva Bijit 20-01-2010

{ return strtol (nptr, (char **) NULL, 10); }

El siguiente ejemplo ilustra el uso de strtol, y la forma de emplear el segundo argumento.

Nótese que al invocar se pasa la dirección de un puntero a carácter; y que al salir, endptr queda

apuntando al carácter que no es válido en la secuencia.

/* strtol ejemplo */

#include <stdio.h>

int main(void)

{

char *string = "87654321guena", *endptr;

long lnumber;

/* strtol converts string to long integer */

lnumber = strtol(string, &endptr, 10);

printf("string = %s long = %ld\n", string, lnumber);

printf(" endptr = %s \n", endptr);

return 0;

}

Nótese que el segundo argumento actual, en la invocación, es &endptr; es decir, la dirección de

un puntero. Entonces en la definición de la función el tipo del segundo argumento debe un

puntero a puntero a carácter; es decir: char * * endptr.

4.3. De dobles a caracteres.

Resulta útil una función que convierta un número en punto flotante en una base b1 en otro

número punto flotante en base b2. Si b1 es 2 y b2 es 10, se convierte un número real en

representación interna en externa.

Se dice que en un sistema de base b, la mantisa m está normalizada si:

1/b <= m < 1

El algoritmo consiste en alternar las multiplicaciones (o divisiones) de m por b1, con las

divisiones (o multiplicaciones) por b2, de tal forma que m se mantenga en el rango:

1/b <= m < b con b = b1*b2

El algoritmo es ineficiente, pues el número de operaciones es proporcional a e1; además

introduce cierto error, debido a las numerosas operaciones de truncamiento para mantener la

mantisa dentro del rango anterior.

Debido a que la función entrega dos valores, se decide pasar por referencia el exponente e2.

46 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

/*Dado un flotante m1*pow(b1, e1) se desea obtener m2*pow(b2, e2) */

double convierta(double m, int e1, int b1, int *e2, int b2)

{

*e2=0;

if (e1>=0)

while (e1>0)

{ m*=b1;e1--;

while(m>=1) {m/=b2;(*e2)++;}

}

else

do

{ m/=b1;e1++;

while( m <(1/b2) ) {m*=b2;(*e2)--;}

}

while (e1!=0);

return(m);

}

Un ejemplo de uso se ilustra a continuación:

Se definen algunas variables:

double number, mantisa, m2;

int e1, int e2=0;

El siguiente llamado a la rutina frexp, declarada en math.h, retorna la mantisa y el exponente de

un número flotante doble, según:

mantisa * pow(2, exponente)

Con: 0.5 =< mantisa < 1

mantisa = frexp(number, &e1);

El llamado a convierta escribe en m2 y e2, el número doble mantisa e1.

m2 = convierta(mantisa, e1, 2, &e2, 10);

printf("m2 = %lf \n", m2);

printf("e2 = %d \n", e2);

4.4. Imprime mantisa.

La función prtmantisa imprime la parte fraccionaria o mantisa normalizada de un número u en

punto flotante de doble precisión, mediante putchar; empleando como base numérica a base, y

sacando un número de dígitos igual a ndig.

Sólo acepta bases positivas menores o iguales que 36; en caso de estar fuera de rango asume

base decimal.

Apéndice 2. Introducción al lenguaje C. 47

Profesor Leopoldo Silva Bijit 20-01-2010

void prtmantisa(double u, int ndig, int base)

{

int i=0; int v;

putchar('.');

if(base<=0 || base>36) base=10;

for(i=0; i<ndig; i++)

{ u*=base; v=(int)u;

if (v <=9) putchar(v+'0'); else putchar(v+'7');

u-=v;

}

}

Multiplica la mantisa por la base, quedando de esta forma un número a la izquierda del punto

decimal. Dicho dígito puede obtenerse en v, truncando el doble; esto se logra mediante el cast

explícito a entero. Luego se puede enviar hacia la salida el carácter, considerando una

conversión a carácter, que toma en cuenta bases mayores a la decimal.

Antes de volver a seguir desplegando caracteres, le quita la parte entera al doble; de este modo

al inicio del bloque, u siempre es un número fraccionario puro.

Los siguientes ejemplos ilustran el uso de la función:

prtmantisa(mantissa, 18, 10); putchar('\n');

prtmantisa(mantissa, 52, 2); putchar('\n');

prtmantisa(mantissa, 8, 16); putchar('\n');

Nótese que no tiene sentido invocar la impresión binaria con más de 52 bits, ya que ese es el

número de bits de un doble. Tampoco tiene sentido invocar la impresión hexadecimal de la

mantisa con más de 7 cifras.

Puede verificarse que tampoco tiene sentido solicitar salidas en base decimal con más de 18

dígitos, ya que ésta es la precisión de un doble.

La impresión del exponente, mediante putchar, puede lograrse empleando itoa.

4.5. Rutinas más eficientes para convertir un número punto flotante binario a punto flotante

decimal.

4.5.1. Potencias de 10.

La siguiente función calcula una potencia de 10, mediante un switch.

/*Calcula pow(10, e) con 0 < e < 309 */

double ten(unsigned int e)

{

double t=1.0;

int i=0;

if (e<309)

while (e!=0)

48 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

{ if ( e&1)

switch (i)

{ case 0: t*=1.0e1;break;

case 1: t*=1.0e2;break;

case 2: t*=1.0e4;break;

case 3: t*=1.0e8;break;

case 4: t*=1.0e16;break;

case 5: t*=1.0e32;break;

case 6: t*=1.0e64;break;

case 7: t*=1.0e128;break;

case 8: t*=1.0e256;break;

}

e/=2;i++;

}

else t=1/0.0; /* return (INF ) */

return(t);

}

Pero es más eficiente emplear un arreglo estático:

double diezalai[9]={1.0e1,1.0e2,1.0e4,1.0e8,1.0e16,1.0e32,1.0e64,1.0e128,1.0e256};

/* pow(10,e) para double. Con arreglo. */

double ten2(unsigned int e)

{

double t=1.0;

int i=0;

if (e<309)

while (e!=0)

{ if ( e&1) t*= diezalai[i];

e/=2;i++;

}

else t=1/0.0; /* return (INF ) */

return(t);

}

4.5.2. Imprime exponente de flotante.

Rutina para imprimir, mediante putchar, el exponente de un número punto flotante.

Se emplean instrucciones con enteros.

/*Imprime exponente en flotante doble */

void prtexp(long int e)

{

static char buf[6];

char *p = buf; int e0,e1,e2;

*p++='e';

if (e<0) {*p++='-'; e=-e;} else *p++='+';

if (e<309)

Apéndice 2. Introducción al lenguaje C. 49

Profesor Leopoldo Silva Bijit 20-01-2010

{ e1=(int)(e*205)>>11; /* e/a = e*round(max/a)/max */

e2= (int)(e-(e1<<3)-(e1<<1)); /* e-10*e */

e0=(e1*205)>>11; /* e/10 = e1*round(2048/10)/2048 */

e1=e1-(e0<<3)-(e0<<1); /* e1=e1-e0*10 */

*p++=e0+'0';*p++=e1 +'0';*p++=e2 +'0';*p++='\0';}

else {*p++='I';*p++='N';*p++='F';*p++='\0';}

p=buf; while(*p) putchar(*p++);

}

/* con a de tipo float. round(2048/10) = 205. Funciona si e<1029 */

Para verificar la operación de redondeo empleando operaciones enteras, puede ejecutarse el

siguiente segmento:

long int i;

for(i=0; i<1030; i++)

/*realiza i/10 mediante multiplicaciones enteras. Para i<1029 */

if ( ((i*205)/2048)!=(i/10)) printf( " %d \n", i);

4.5.3. Redondeo de la mantisa.

double round(double t, unsigned int i)

{ /*no puede redondear a mas de 15 cifras */

if (i<16)

switch (i)

{ case 2: t+=0.5e-2;break;

case 3: t+=0.5e-3;break;

case 4: t+=0.5e-4;break;

case 5: t+=0.5e-5;break;

case 6: t+=0.5e-6;break;

case 7: t+=0.5e-7;break;

case 8: t+=0.5e-8;break;

case 9: t+=0.5e-9;break;

case 10: t+=0.5e-10;break;

case 11: t+=0.5e-11;break;

case 12: t+=0.5e-12;break;

case 13: t+=0.5e-13;break;

case 14: t+=0.5e-14;break;

case 15: t+=0.5e-15;break;

}

return(t);

}

La misma función anterior de Redondeo puede efectuarse mediante arreglos:

50 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

double roundalai[14]={0.5e-2,0.5e-3,0.5e-4,0.5e-5,0.5e-6,0.5e-7,0.5e-8,

0.5e-9,0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,0.5e-15};

double round2(double t, unsigned int i)

{ /*no puede redondear a mas de 15 cifras */

if ((i>1)||(i<16)) return(t+roundalai[i-2]);else return(t);

}

4.5.4. Convierta. Algoritmo dos.

/*Dado un flotante m=m1*pow(2,e1) se desea obtener m2*pow(10,e2) */

/* e2 = log(2)*e1 m2=m/pow(10,e2) */

/* log(2)=0.30103 es aprox 77/256 = 0,30078125 */

double convierta2(double m, long int e1, long int *e2)

{

m=m*pow(2,e1);

/* sin la funcion pow, se pasa el número y el exponente binario retornado por frexp */

if (e1>=0)

{ if(e1<1025)

{*e2=(e1*77/256)+1; m/=ten2((int)*e2);

while(m>=1.0) { m/=10.0; (*e2)++;}

while(m<0.1) {m*=10.0; (*e2)--;}

}

else printf("inf");

}

else

{*e2=(e1+1)*77/256; m*=ten2(-((int)*e2));

while(m<0.1) {m*=10.0; (*e2)--;}

while(m>=1.0) { m/=10.0; (*e2)++;}

}

return(m);

}

4.5.5. Imprime mantisa. Algoritmo dos.

Aplica redondeo a ndig

/* imprime mantisa normalizada u = .ddddd No chequea infinito*/

void prtmantisa(double u, int ndig, int base)

{

int i=0; int v;

putchar('0'); putchar('.');

if ((base<=0)||(base>36)) base=10; u=round2(u,ndig);

for(i=0; i<ndig; i++)

{ u*=base; v=(int)u;

if (v <=9) putchar(v+'0'); else putchar(v+'7');

u-=v;

}

}

Apéndice 2. Introducción al lenguaje C. 51

Profesor Leopoldo Silva Bijit 20-01-2010

5. Diseño de funciones con un número variable de argumentos.

Una función puede tener parámetros fijos y una lista de argumentos variables. El número y tipo

de argumentos no son conocidos en el momento que se diseña la función.

El siguiente prototipo de func indica que tiene un parámetro fijo, un puntero a entero, y los tres

puntos a continuación indican que se puede pasar a esta función un número variable de

argumentos.

int func( int *, …);

Por ejemplo se la puede invocar: x = func(p, a, b, c); o bien: x = func(p, a);

En la definición de la función debe existir alguna forma de conocer el número y tipo de los

parámetros variables. En la función printf el argumento fijo es el string de control que permite

determinar el número y tipo de argumentos.

Debe conocerse la estructura del frame de la función en el stack. Si suponemos que previo a la

invocación de una función se empujan los valores de los argumentos empezando por el último

argumento, se tendrá el siguiente esquema del frame, para la invocación a

func(p, a, b, c), después del llamado:

Figura A2.9 Estructura frame.

Entonces el primer argumento variable tiene una dirección mayor que el último argumento fijo.

Como además los tamaños de los argumentos pueden ser diferentes, deben guardarse los

parámetros alineados.

Si p es un puntero a entero, se tendrá que la dirección de inicio de p es &p. La dirección de

inicio de a, será la dirección &p más el tamaño de un puntero a entero. Si se conoce la dirección

de a, puede obtenerse su valor según:

* (tipo de a *) (dirección de a)

p

a

b

c

Las

direcciones

aumentan

52 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Se convierte la dirección de a, en un puntero al tipo de a, y luego se indirecciona.

Para el diseño de este tipo de funciones, se dispone de herramientas de biblioteca estándar.

Básicamente consisten en dotar a la función de un puntero de un tipo adecuado, para tratar

argumentos de diferente tipo; de un mecanismo para fijar el puntero al primer argumento

variable, y de una función que extraiga el valor del argumento, según su tipo y avance el

puntero al siguiente argumento; y un mecanismo para evitar resultados catastróficos por un uso

inadecuado del puntero.

5.1. Argumentos estándar.

En <stdarg.h> se definen un tipo y tres macros.

typedef void *va_list;

#define __size(x) ((sizeof(x)+sizeof(int)-1) & ~(sizeof(int)-1))

/* retorna el tamaño en bytes hasta el siguiente inicio de palabra alineada */

#define va_start(ap, parmN) ((void)((ap) = (va_list)((char *)(&parmN)+__size(parmN))))

#define va_arg(ap, type) (*(type *)(((*(char * *)&(ap))+=__size(type))-(__size(type))))

#define va_end(ap) ((void)((ap) = (va_list) 0) )

va_list es un tipo de puntero genérico; es decir puede apuntar a cualquier elemento de memoria,

no importando su tamaño. Más adelante se explicarán detalladamente los macros.

La inicialización del puntero, dentro de la función se logra con va_start, y debe usarse antes de

llamar a va_arg o va_end.

va_arg retorna el valor del argumento y mueve el puntero al inicio del siguiente argumento.

va_end desconecta el puntero, y debe emplearse una vez que va_arg haya leído todos los

argumentos.

El siguiente ejemplo ilustra el orden y uso de los macros.

#include <stdio.h>

#include <stdarg.h>

/* calcula la suma de una lista variables de enteros, terminada en 0 */

void sum(char *msg, ...)

/* Nótese que sum emplea un número variable de argumentos */

{

int total = 0; int arg;

va_list ap; /* 1. Se define el puntero */

va_start(ap, msg); /*2. Se inicia el puntero ap al primer argumento variable */

while ((arg = va_arg(ap,int)) != 0) { /* 3 Se buscan los argumentos, uno a la vez */

total += arg;

}

printf(msg, total);

Apéndice 2. Introducción al lenguaje C. 53

Profesor Leopoldo Silva Bijit 20-01-2010

va_end(ap); /* 4. Se aterriza el puntero ap */

}

int main(void) {

sum("El total de: 1+2+3+4 es igual a %d\n", 1, 2, 3, 4, 0);

return 0;

}

Para efectuar correctamente los movimientos del puntero, y para considerar el alineamiento en

el almacenamiento de los argumentos, se emplea el macro:

#define __size(x) ((sizeof(x) + sizeof(int) -1) & ~(sizeof(int) - 1))

/* retorna el tamaño en bytes hasta el siguiente inicio de palabra alineada */

Esto considera que un entero se guarda alineado, lo cual es una definición implícita en C. Lo

rebuscado, aparentemente, permite portar el código de estos macros a procesadores con

diferentes tamaños para el entero.

El operador sizeof retorna el número de bytes de la expresión o el tipo que es su argumento.

Para enteros de 16 bits, la máscara que se forma tomando el complemento a uno, resulta ser:

…1111110. Para enteros de 32 bits, la máscara es: ….1111100.

Para 64 bits: ….1111000.

Al tamaño de x en bytes se le suma el tamaño en bytes del entero menos uno, lo cual luego es

truncado por la máscara, obteniendo el tamaño de x en múltiplos del tamaño de un entero.

El siguiente segmento permite verificar en forma enumerativa, la función del macro que entrega

direcciones alineadas:

for(t=2;t<9;t*=2)

{ printf(" t=%d \n",t);

for( i=1; i<16;i++) printf(" i=%d largo=%d\n", i, (i+t-1)&~(t-1));

La macro que inicializa el puntero es:

#define va_start(ap, parmN) ((void)((ap) = (va_list)((char *)(&parmN) +__size(parmN))))

La dirección del último parámetro fijo se convierte a puntero a char(a un Byte) y se obtiene la

siguiente dirección (en Bytes) donde está almacenado el primer argumento variable, mediante:

( (char *)(&parmN) +__size(parmN) )

Luego esta dirección se convierte en la del tipo de ap, y se la escribe en ap. El primer void

indica que el macro no retorna valores.

Debe notarse que cuando se emplean macros, los argumentos deben colocarse entre paréntesis.

La macro que aterriza el puntero ap es:

#define va_end(ap) ((void)((ap) = (va_list) 0) )

54 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

El valor cero, que por definición es un puntero nulo, se lo convierte en puntero al tipo de ap,

antes de asignárselo. El primer void, indica que la función no retorna nada.

Finalmente la función que recorre la lista de argumentos:

#define va_arg(ap, type) (*(type *)(((*(char * *)&(ap))+=__size(type))-(__size(type))))

Es un tanto más compleja. Ya que efectúa dos cosas, retornar el valor del argumento y por otro

lado incrementar el puntero ap, de tal modo que apunte al próximo argumento.

Primero incrementa el puntero, luego obtiene el valor.

La dirección del puntero genérico se convierte en puntero a carácter mediante:

* (char * *) &(ap)

al cual se le suma el tamaño (en múltiplos del tamaño de un entero), de tal modo que apunte a la

dirección del próximo argumento. Esto se logra con:

( (* (char * *) &(ap)) +=__size(type) )

Luego a este valor se le resta su propio tamaño, para obtener la dirección del argumento actual,

la cual puede expresarse según:

( ((*(char * *)&(ap))+=__size(type)) - (__size(type) ) )

Se la convierte a un puntero al tipo del argumento, mediante:

(type *)( ((*(char * *)&(ap))+=__size(type)) - (__size(type) ) )

Y se indirecciona, para obtener el valor:

( * (type *)( ((*(char * *)&(ap))+=__size(type)) - (__size(type) ) ) )

Nótese que el retorno de este macro debe asignarse a una variable de tipo type.

La expresión *(char **)&ap podría haberse anotado más sencillamente: (char *) ap;

5.2. Estructura de printf.

Ver esquema de printf en KR 7.3, versión ANSI C.

El siguiente es un esquema para printf. Se traen los valores de los argumentos a variables

locales.

void printf(char * format,...)

{

va_list ap;

char *p, *sval ;

int ival ;

double dval;

va_start(ap, format);

for(p = format; *p ; p++) {

if( *p !='%') {putchar(*p); continue;}

switch( *++p) {

case 'd':

ival= va_arg(ap, int);

/* trae el argumento entero a la local ival */

Apéndice 2. Introducción al lenguaje C. 55

Profesor Leopoldo Silva Bijit 20-01-2010

/* acá debería formatearse el entero y desplegarlo */

break;

case 'f':

/* if ((int)ap&4) ap=(va_list)((int)ap +4); */

dval= va_arg(ap, double);

/* trae el argumento doble a la local dval */

/* acá debería formatearse el doble y desplegarlo */

break;

case 's':

sval= va_arg(ap, char *);

/* trae el puntero al string a la local sval */

/* acá debería desplegarse el string */

break;

}

}

va_end(ap);

}

Esta rutina es dependiente de la forma en que el compilador pasa los argumentos. Es decir si

pasa algunos argumentos en registros o los pasa en el stack. Además depende de la forma en

que el compilador almacena los diferentes tipos. Por ejemplo se ilustra una modificación a los

macros para corregir el valor de ap, en el caso que guarde alineados los dobles en direcciones

que son múltiplos de 8 Bytes. Si el tercer bit es 1, le suma 4 bytes a ap, de tal forma de alinear

correctamente al doble. Este es el caso del compilador lcc.

En la rutina se han empleado variables locales para depositar internamente los valores de los

argumentos, de acuerdo al tipo; y su objetivo es ilustrar el uso de va_arg.

Existen funciones como itoa que transforman un entero en una secuencia de caracteres.

Mediante estas funciones se puede traducir el despliegue de un entero, en determinada base, en

el despliegue de una secuencia de caracteres, lo cual se logra con putchar.

5.3. Estructura de scanf.

El siguiente esquema ilustra el diseño de scanf.

Se traen los valores de los argumentos a punteros locales a la rutina, con el fin de ilustrar el uso

de va_arg. Nótese que en este caso se traen punteros, y que puede cambiarse el valor de la

variable pasada por referencia, mediante indirección.

void scanf(char * format,...)

{

va_list ap;

char *p;

int * pi;

double *pd;

char *pc;

56 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

va_start(ap, format);

for(p = format; *p ; p++) {

if( *p !='%') continue;

switch( *++p) {

case 'd':

pi=(int *) va_arg(ap, int*);

/* trae a pi un puntero a entero */

/*acá debe ingresarse un entero y depositarlo en *pi */

break;

case 'l':

pd=(double *) va_arg(ap, double*);

/* trae a pd un puntero a doble */

/*acá debe ingresarse un doble y depositarlo en *pd */

break;

case 's':

pc=(char *) va_arg(ap, char *);

/* trae a pc un puntero a char */

/*acá debe ingresarse un string y copiarlo desde pc */

break;

}

}

va_end(ap);

}

Esta rutina debe cuidar que los caracteres ingresados correspondan a lo que se desea leer; que el

espacio asociado al string externo no sea excedido por el largo del string ingresado. También

dependerá de cómo se termine de ingresar los datos, o la acción que deberá tomarse si lo

ingresado no corresponde al tipo que se establece en el string de control.

Funciones como atoi y atof, pasan de secuencias de caracteres a enteros o flotantes; y mediante

éstas, puede implementarse scanf como una secuencia de llamados a getchar.

5.4. Salida formateada en base a llamados al sistema. SPIM.

Deseamos dotar a los programas compilados para MIPS, mediante el compilador lcc, de rutinas

de interfaz con los llamados al sistema que SPIM provee.

Para esto es preciso conocer la forma en que SPIM maneja la salida.

Se tiene cuatro llamados al sistema para implementar salidas.

Al ejecutar el siguiente código assembler, se imprime un entero en la consola de SPIM:

li $v0, 1 # código de llamado al sistema para print_int

li $a0, int # entero a imprimir se pasa en $a0

syscall # print it

Apéndice 2. Introducción al lenguaje C. 57

Profesor Leopoldo Silva Bijit 20-01-2010

Al ejecutar el siguiente código assembler, se imprime un float en la consola de SPIM:

li $v0, 2 # system call code for print_float

li $f12, float # float to print No se implementa, por el momento.

syscall # print it

Al ejecutar el siguiente código assembler, se imprime un double en la consola de SPIM:

li $v0, 3 # system call code for print_int

li $f12, double # double to print

syscall # print it

Al ejecutar el siguiente código assembler, se imprime un string en la consola de SPIM:

li $v0, 4 # system call code for print_str

la $a0, str # address of string to print

syscall # print the string

El siguiente código en C, muestra el diseño de la rutina printf, empleando los macros vistos

antes, ya que printf es una función con un número variable de argumentos:

void printf(char * format,...)

{

va_list ap;

char *p;

va_start(ap, format);

for(p = format; *p ; p++) {

if( *p !='%') {putchar(*p); continue;}

switch( *++p) {

case 'd':

syscall(va_arg(ap, int),1);

break;

case 'f':

if ((int)ap&4) ap=(va_list)((int)ap +4); /*align double */

syscall(va_arg(ap, double),3);

break;

case 's':

syscall(va_arg(ap, char *),4);

break;

}

}

va_end(ap);

}

La traducción de syscall se traduce en pasar en $a0 y $a1 los argumentos y luego efectuar un:

jal syscall.

58 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

La implementación de putchar, debe efectuarse en base al llamado al sistema que efectúa la

impresión de un string:

void putchar(char ch)

{char p[2];

p[0]=ch;

p[1]='\0';

syscall(p,4);

}

Debido a que el compilador lcc, introduce en el stack los dobles alineados en palabras dobles, se

requiere forzar el alineamiento en los casos que sea necesario.

Por ejemplo, la compilación de:

printf(" entero= %d doble= %f string=%s \n", 5,12.0000012,"hola");

se traduce en:

la $a0,formato

la $a1,5

l.d $f18,doble #carga en registro un valor doble

mfc1.d $a2, $f18 #mueve a $a2 y $a3 el valor

la $t8, stringhola

sw $t8,16($sp) #pasa el puntero en el stack.

jal printf

.data

.align 0

formato: .asciiz " entero= %d doble= %f string=%s \n"

.align 3

doble:

.word 0x2843ebe8

.word 0x40280000

.align 0

stringhola: .asciiz "hola"

Compilando las rutina de printf, vista antes, se logra:

.rdata

.align 2

_sss: .word 0 # espacio para almacenar un char terminado en \0.

.globl putchar

.text

.align 2

putchar: la $t8,_sss

sw $a0,0($t8)

Apéndice 2. Introducción al lenguaje C. 59

Profesor Leopoldo Silva Bijit 20-01-2010

move $a0,$t8

la $v0,4 # la $a1,4

syscall # jal syscall

j $ra

.globl printf

#void printf(char * format,...)

.text

.align 2

printf:

.frame $sp,32,$31

addu $sp,$sp,-32

.mask 0xc0800000,-8

sw $s7,16($sp)

sw $s8,20($sp)

sw $ra,24($sp)

sw $a0,32($sp)

sw $a1,36($sp)

sw $a2,40($sp)

sw $a3,44($sp)

#{

la $t8,4+32($sp)

sw $t8,-4+32($sp) # va_start(ap, format);

lw $s8,0+32($sp) # for(p = format; *p ; p++) {

b _tstcnd

_blkfor:lb $t8,($s8) # if( *p !='%') {putchar(*p); continue;}

la $t7,37 # '%'

beq $t8,$t7,_espc

lb $a0,($s8)

jal putchar

b _siga

_espc: la $t8,1($s8) # switch( *++p) {

move $s8,$t8

lb $s7,($t8)

la $t8,100 # 'd'

beq $s7,$t8,_esd

la $t7,102 # 'f'

beq $s7,$t7,_esf

blt $s7,$t8,_siga1

la $t8,115 # 's'

beq $s7,$t8,_ess

b _siga1

_esd: lw $t8,-4+32($sp) # syscall(va_arg(ap, int),1);

60 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

la $t8,4($t8)

sw $t8,-4+32($sp)

lw $a0,-4($t8)

la $v0,1 # la $a1,1

syscall # jal syscall

b _siga2 # break;

_esf:

lw $t8,-4+32($sp) # if ((int)ap&4) ap=(va_list)((int)ap +4);

and $t8,$t8,4

beq $t8,$0,_nosuma

lw $t8,-4+32($sp)

la $t8,4($t8)

sw $t8,-4+32($sp)

_nosuma:lw $t8,-4+32($sp) # syscall(va_arg(ap, double),3);

la $t8,8($t8)

sw $t8,-4+32($sp)

l.d $f12,-8($t8) #pasa eldoble en reg. doble $f12

la $v0,3 # la $a1,3

syscall # jal syscall

b _siga2 # break;

_ess: lw $t8,-4+32($sp) # syscall(va_arg(ap, char *),4);

la $t8,4($t8)

sw $t8,-4+32($sp)

lw $a0,-4($t8)

la $v0,4 # la $a1,4

syscall # jal syscall

_siga1: # break;

_siga2:

# }

_siga: la $s8,1($s8) # for(p = format; *p ; p++) {

_tstcnd:lb $t8,($s8)

bne $t8,$0,_blkfor

sw $0,-4+32($sp) # va_end(ap);

#}

lw $s7,16($sp)

lw $s8,20($sp)

lw $ra,24($sp)

addu $sp,$sp,32

j $ra

.end printf

Apéndice 2. Introducción al lenguaje C. 61

Profesor Leopoldo Silva Bijit 20-01-2010

Donde se han parchado los jal syscall por el llamado syscall, y se ha cuidado de pasar el

número del llamado en $v0. Las zonas de parches se destacan en rojo.

El siguiente código implementa, en C, la función scanf en base a los llamados al sistema de

SPIM. Se emplea el compilador lcc para pasar a assembler.

/* Macros for accessing variable arguments <stdarg.h>*/

typedef void *va_list;

#define __size(x) ((sizeof(x)+sizeof(int)-1) & ~(sizeof(int)-1))

/* retorna el tamaño en bytes hasta el siguiente inicio de palabra alineada */

#define va_start(ap, parmN) ((void)((ap) = (va_list)((char *)(&parmN)+__size(parmN))))

#define va_arg(ap, type) (*(type *)(((*(char **)&(ap))+=__size(type))-(__size(type))))

#define va_end(ap) ((void)((ap) = (va_list)0) )

/*

.text

li $v0, 5 # system call code for read_int

syscall # read it. Retorno en $v0

li $v0, 7 # system call code for read_double

syscall # retorno en $f0

li $a1,largo #

li $v0, 8 # system call code for read_str

la $a0, str # buffer of string to read

syscall # read the string

*/

void scanf(char * format,...)

{

va_list ap;

char *p;

int * pi;

double *pd;

char *pc;

const char * cp;

int largo;

va_start(ap, format);

for(p = format; *p ; p++) {

if( *p !='%') continue;

switch( *++p) {

case 'd':

pi=(int *) va_arg(ap, int*);

*pi=syscall(5);

/*printf(" *pi=%d \n", *pi);*/

62 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

break;

case 'l':

pd=(double *) va_arg(ap, double*);

*pd=syscall(7);

/*printf(" *pd=%f \n", *pd);*/

break;

case 's':

pc=(char *) va_arg(ap, char *);

cp=pc; while(*cp++) continue; /*calcula largo buffer*/

largo=cp - pc;

syscall(pc,largo,8);

/*printf(" pc=%s \n", pc);*/

break;

}

}

va_end(ap);

}

Los syscall generan jal syscall, que deben parcharse, y también el paso de los argumento a esos

llamados.

.rdata

.align 2

_sss: .word 0

.globl getchar

.text

.align 2

getchar:

la $a0,_sss # syscall(pc,2,8);

li $a1,2

la $v0,8

syscall

la $t8,sss

lw $v0,0($t8)

j $ra

.globl scanf

.text

#void scanf(char * format,...)

.text

.align 2

scanf:

.frame $sp,56,$31

addu $sp,$sp,-56

.mask 0xc0e00000,-24

Apéndice 2. Introducción al lenguaje C. 63

Profesor Leopoldo Silva Bijit 20-01-2010

sw $s5,16($sp)

sw $s6,20($sp)

sw $s7,24($sp)

sw $s8,28($sp)

sw $ra,32($sp)

sw $a0,56($sp)

sw $a1,60($sp)

sw $a2,64($sp)

sw $a3,68($sp)

#{

la $t8,4+56($sp) # va_start(ap, format);

sw $t8,-4+56($sp)

# for(p = format; *p ; p++) {

lw $s8,0+56($sp)

b _tstscn

# if( *p !='%') continue;

_blkscn:lb $t8,($30)

la $t7,37 # '%'

beq $t8,$t7,_swscn

b _cntscn

_swscn:la $t8,1($s8) # switch( *++p) {

move $s8,$t8

lb $s5,($t8)

la $t8,108 # 'l'

beq $s5,$t8,_lfscn

bgt $s5,$t8,_tsts

la $t8,100 # 'd'

beq $s5,$t8,_dscn

b _brkscn

_tsts: la $t8,115 # 's'

beq $s5,$t8,_sscn

b _brkscn

_dscn: lw $t8,-4+56($sp) # pi=(int *) va_arg(ap, int*);

la $t8,4($t8)

sw $t8,-4+56($sp)

lw $t8,-4($t8)

sw $t8,-8+56($sp)

la $v0,5 # *pi=syscall(5);

syscall

lw $t7,-8+56($sp)

sw $v0,($t7)

64 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

b _brkscn # break;

_lfscn: lw $t8,-4+56($sp) # pd=(double *) va_arg(ap, double*);

la $t8,4($t8)

sw $t8,-4+56($sp)

lw $t8,-4($t8)

sw $t8,-12+56($sp)

la $v0,7 # *pd=syscall(7);

syscall

lw $t7,-12+56($sp)

s.d $f0,($t7) # retorna el doble en $f0

b _brkscn # break;

_sscn: lw $t8,-4+56($sp) # pc=(char *) va_arg(ap, char *);

la $t8,4($t8)

sw $t8,-4+56($sp)

lw $s6,-4($t8)

move $s7,$s6 # cp=pc; while(*cp++) continue;

_tsteos:move $t8,$s7

la $s7,1($t8)

lb $t8,($t8)

bne $t8,$0,_tsteos

la $t8,0($23) # largo=(cp) - pc;

move $t7,$22

subu $t8,$t8,$t7

sw $t8,-16+56($sp)

move $a0,$22 # syscall(pc,largo,8);

lw $a1,-16+56($sp)

la $v0,8

syscall

# break;

_brkscn:

# }

_cntscn:la $30,1($30) # for(p = format; *p ; p++) {

_tstscn:lb $24,($30)

bne $24,$0,_blkscn

sw $0,-4+56($sp) # va_end(ap);

#}

lw $s5,16($sp)

lw $s6,20($sp)

lw $s7,24($sp)

lw $s8,28($sp)

Apéndice 2. Introducción al lenguaje C. 65

Profesor Leopoldo Silva Bijit 20-01-2010

lw $ra,32($sp)

addu $sp,$sp,56

j $ra

.end scanf

Si estas rutinas se agregan al código del trap.handler de SPIM, pueden emplearse llamados a

printf, scanf, getchar y putchar, sin tener que incluir dichos códigos junto a los programas

fuentes.

5.5. Desarrollo de printf en base a putchar.

Puede implementarse printf en términos de putchar. Es decir una función que saca un carácter

hacia el medio de salida. La implementación de putchar suele efectuarse programando la puerta

serial de un microcontrolador, para esto bastan muy pocas instrucciones (no más de 10).

Entonces el núcleo de printf puede describirse según:

Dejando en la función _doprnt la tarea de desplegar en la salida estándar los diferentes

argumentos. Esta función inspecciona el string de control format, y de acuerdo a la

especificación de la letra ubicada después del carácter %, ubica el tipo del argumento, y usa

va_arg con dicho tipo para tomar el valor y convertirlo a secuencia de caracteres.

#include <stdarg.h>

int printf(const char *format, ...)

{

va_list ap;

int retval;

va_start(ap, format);

retval = _doprnt(format, ap, stdout);

va_end(ap);

return retval;

}

Se ilustra una rutina bajada de la red (de la cual perdí la referencia), con pequeñas

modificaciones que implementa printf. Lo cual muestra que la función printf está formada por

numerosas instrucciones, lo cual debe ser tenido en cuenta al emplearla en microcontroladores.

En estos casos existen versiones recortadas, mediante la no implementación de algunos

formatos.

#include <stdio.h> /* usa prototipo putchar y definición de NULL*/

#include <ctype.h> /* usa prototipo de isdigit */

#include <math.h> /* usa frexp */

66 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

/* Macros for accessing variable arguments <stdarg.h>*/

typedef void *va_list;

#define __size(x) ((sizeof(x)+sizeof(int)-1) & ~(sizeof(int)-1))

/* retorna el tamaño en bytes hasta el siguiente inicio de palabra alineada */

#define va_start(ap, parmN) ((void)((ap) = (va_list)((char *)(&parmN)+__size(parmN))))

#define va_arg(ap, type) (*(type *)(((*(char **)&(ap))+=__size(type))-(__size(type))))

#define va_end(ap) ((void)((ap) = (va_list)0) )

/*

* This version only supports 32 bit floating point

*/

#define value long

#define NDIG 12 /* máximo número de dígitos ha ser impresos */

#define expon int

const static unsigned value

dpowers[] = {1, 10, 100, 1000, 10000, 100000L, 1000000L,

10000000L,10000000L,100000000L};

const static unsigned value

hexpowers[] = {1, 0x10, 0x100, 0x1000,0x10000L, 0x100000L,0x1000000L, 0x10000000L};

const static unsigned value

octpowers[] = {1, 010, 0100, 01000, 010000, 0100000, 01000000L,010000000L,

0100000000L, 01000000000L, 010000000000L};

static const double

powers[] ={1e0,1e1,1e2,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e20,1e30,};

static const double

npowers[] ={1e-0,1e-1,1e-2,1e-3,1e-4,1e-5,1e-6,1e-7,1e-8,1e-9,1e-10,1e-20,1e-30,};

/* this routine returns a value to round to the number of decimal places specified */

double fround(unsigned char prec)

{ /* prec is guaranteed to be less than NDIG */

if(prec > 10) return 0.5 * npowers[prec/10+9] * npowers[prec % 10];

return 0.5 * npowers[prec];

}

/* this routine returns a scaling factor equal to 1 to the decimal power supplied */

static double scale(expon scl)

{ if(scl < 0) { scl = -scl;

if(scl > 10) return npowers[scl/10+9] * npowers[scl%10];

return npowers[scl];

}

if(scl > 10) return powers[scl/10+9] * powers[scl%10];

return powers[scl];

}

Apéndice 2. Introducción al lenguaje C. 67

Profesor Leopoldo Silva Bijit 20-01-2010

#define OPTSIGN 0x00

#define SPCSIGN 0x01

#define MANSIGN 0x02

#define NEGSIGN 0x03

#define FILL 0x04

#define LEFT 0x08

#define LONG 0x10

#define UPCASE 0x20

#define TEN 0x00

#define EIGHT 0x40

#define SIXTEEN 0x80

#define UNSIGN 0xC0

#define BASEM 0xC0

#define EFMT 0x100

#define GFMT 0x200

#define FFMT 0x400

#define ALTERN 0x800

#define DEFPREC 0x1000

#define pputc(c) if(pb->ptr) *pb->ptr++ = (c); else pb->func(c)

struct __prbuf

{ char * ptr;

void (* func)(char);

} pb;

void _doprnt(struct __prbuf * pb, const register char * f, va_list ap )

{ int prec;

char c;

int width;

unsigned flag;

double fval;

int exp;

union {

unsigned value _val;

struct { char * _cp;

unsigned _len;

} _str;

double _integ;

} _val;

#define val _val._val /*definiciones para los campos de la unión val */

#define cp _val._str._cp

#define len _val._str._len

#define integ _val._integ

flag = 0;

while(NULL != (c = (char)*f++)) {

if(c != '%') { pputc(c); continue; }

68 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

width = 0;

flag = 0;

for(;;) {

switch(*f) {

case '-':

flag |= LEFT;

f++;

continue;

case ' ':

flag |= SPCSIGN;

f++;

continue;

case '+':

flag |= MANSIGN;

f++;

continue;

case '#':

flag |= ALTERN;

f++;

continue;

case '0':

flag |= FILL;

f++;

continue;

}

break;

}

if(flag & MANSIGN) flag &= ~SPCSIGN;

if(flag & LEFT) flag &= ~FILL;

if(isdigit((unsigned)*f)) {

width = 0;

do width = width*10 + *f++ - '0';

while(isdigit((unsigned)*f));

}

else if(*f == '*') {

width = va_arg(ap, int);

f++;

}

if(*f == '.')

if(*++f == '*') {prec = va_arg(ap, int);f++;}

else {prec = 0;

while(isdigit((unsigned)*f)) prec = prec*10 + *f++ - '0';}

else {prec = 0;flag |= DEFPREC;}

loop:

switch(c = *f++) {

case 0: return;

case 'l': flag |= LONG; goto loop;

Apéndice 2. Introducción al lenguaje C. 69

Profesor Leopoldo Silva Bijit 20-01-2010

case 'f': flag |= FFMT; break;

case 'E': flag |= UPCASE;

case 'e': flag |= EFMT;break;

case 'g': flag |= GFMT;break;

case 'o': flag |= EIGHT;break;

case 'd':

case 'i': break;

case 'X':

case 'p': flag |= UPCASE;

case 'x': flag |= SIXTEEN;break;

case 's': cp = va_arg(ap, char *);

if(!cp) cp = "(null)";

len = 0;

while(cp[len])len++;

dostring:

if(prec && prec < len) len = prec;

if(width > len) width -= len; else width = 0;

if(!(flag & LEFT)) while(width--) pputc(' ');

while(len--) pputc(*cp++);

if(flag & LEFT) while(width--) pputc(' ');

continue;

case 'c': c=va_arg(ap, int);

default: cp = &c;len = 1; goto dostring;

case 'u': flag |= UNSIGN;break;

}

if(flag & (EFMT|GFMT|FFMT)) {

if(flag & DEFPREC) prec = 6;

fval=va_arg(ap, double);

if(fval < 0.0) {fval = -fval;flag |= NEGSIGN;}

exp = 0;

frexp(fval, &exp); /* get binary exponent */

exp--; /* adjust 0.5 -> 1.0 */

exp *= 3;

exp /= 10; /* estimate decimal exponent */

if(exp <= 0) c = 1; else c = exp;

if(!(flag & ALTERN) && flag & FFMT && prec == 0) {

val = (long)(fval + 0.5);

flag |= LONG;

goto integer;

}

if(!(flag & ALTERN) && flag & GFMT && exp >= 0 && c <= prec) {

integ = fval + fround(prec - c);

if(exp > sizeof dpowers/sizeof dpowers[0] ||

integ - (float)(unsigned long)integ < fround(prec-c-1)) {

val = (long)integ;

flag |= LONG;

prec = 0;

70 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

goto integer;

}

}

/* use e format */

if(flag & EFMT || flag & GFMT && (exp < -4 || exp >= (int)prec)) {

if(exp > 0) {

fval *= scale(-exp);

if(fval >= 10.0) {fval *= 1e-1; exp++;}

}

else if(exp < 0) { fval *= scale(-exp);

if(fval < 1.0) { fval *= 10.0;exp--;}

}

if(flag & GFMT) prec--;

fval += fround(prec);

if(flag & GFMT && !(flag & ALTERN)) {

/* g format, precision means something different */

if(prec > (int)(sizeof dpowers/sizeof dpowers[0]))

prec = sizeof dpowers/sizeof dpowers[0];

val = (long)(fval * scale(prec));

if(val) { while(val % 10 == 0) { prec--; val /= 10; }}

else prec = 0;

}

if(fval != 0.0) {

while(fval >= 10.0) { fval *= 1e-1; exp++; if(flag & EFMT)prec++;}

while(fval < 1.0) {fval *= 10.0; exp--; if(flag & EFMT) prec--;}

}

width -= prec + 5;

if(prec || flag & ALTERN) width--;

if(flag & (MANSIGN|SPCSIGN))width--;

if(exp >= 100 || exp <= -100) /* 3 digit exponent */ width--;

if(flag & FILL) {

if(flag & MANSIGN) pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN) pputc(' ');

while(width > 0) {pputc('0');width--;}

} else {

if(!(flag & LEFT)) while(width > 0) { pputc(' '); width--;}

if(flag & MANSIGN) pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN)pputc(' ');

}

pputc((int)fval + '0');

if(prec || flag & ALTERN) {

if(prec > (int)(sizeof dpowers/sizeof dpowers[0]))

c = sizeof dpowers/sizeof dpowers[0];

else c = prec;

pputc('.');

prec -= c;

integ = (double)(unsigned long)fval;

Apéndice 2. Introducción al lenguaje C. 71

Profesor Leopoldo Silva Bijit 20-01-2010

val = (unsigned long)((fval - integ) * scale(c));

while(c) { pputc('0' + (int)((long)val/dpowers[--c]) % 10);}

while(prec) { pputc('0');prec--; }

}

if(flag & UPCASE) pputc('E');else pputc('e');

if(exp < 0) { exp = -exp;pputc('-');} else pputc('+');

if(exp >= 100) { pputc(exp / 100 + '0');exp %= 100;}

pputc(exp / 10 + '0');

pputc(exp % 10 + '0');

if((flag & LEFT) && width) do pputc(' ');while(--width);

continue;

}

/* here for f format */

frexp(fval, &exp); /* get binary exponent */

exp--; /* adjust 0.5 -> 1.0 */

exp *= 3;

exp /= 10; /* estimate decimal exponent */

if(flag & GFMT) {

if(exp < 0) prec -= exp-1;

val = (unsigned long)fval;

for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)

if(val < dpowers[c]) break;

prec -= c;

val = (unsigned long)((fval-(double)val) * scale(prec)+0.5);

while(prec && val % 10 == 0) {val /= 10; prec--;}

}

if(prec <= NDIG) fval += fround(prec);

if(exp > (int)(sizeof dpowers/sizeof dpowers[0])) {

exp -= sizeof dpowers/sizeof dpowers[0];

val = (unsigned long)(fval * scale(-exp));

fval = 0.0;

} else { val = (unsigned long)fval; fval -= (float)val;exp = 0;}

for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)

if(val < dpowers[c])break;

width -= prec + c + exp;

if(flag & ALTERN || prec)width--;

if(flag & (MANSIGN|SPCSIGN))width--;

if(flag & FILL) {

if(flag & MANSIGN)pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN) pputc(' ');

while(width > 0) { pputc('0'); width--;}

} else {

if(!(flag & LEFT))

while(width > 0) {pputc(' ');width--; }

if(flag & MANSIGN)pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN)pputc(' ');

}

72 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

while(c--) pputc('0' + (int)((long)val/dpowers[c]) % 10);

while(exp > 0) { pputc('0');exp--;}

if(prec > (int)(sizeof dpowers/sizeof dpowers[0]))

c = sizeof dpowers/sizeof dpowers[0];else

c = prec;

prec -= c;

if(c || flag & ALTERN) pputc('.');

val = (long)(fval * scale(c));

while(c) {pputc('0' + (int)((long)val/dpowers[--c]) % 10);}

while(prec) {pputc('0'); prec--;}

if((flag & LEFT) && width) do pputc(' ');

while(--width); continue;

}

if((flag & BASEM) == TEN) {

if(flag & LONG) val=va_arg(ap, long);

else val=va_arg(ap, int);

if((value)val < 0) {flag |= NEGSIGN;val = ~val+1;}

} else {

if(flag & LONG) val=va_arg(ap, unsigned long);

else val= va_arg(ap, unsigned);

}

integer:

if(prec == 0 && val == 0) prec++;

switch((unsigned char)(flag & BASEM)) {

case TEN:

case UNSIGN:

for(c = 1 ; c != sizeof dpowers/sizeof dpowers[0] ; c++)

if(val < dpowers[c])break;

break;

case SIXTEEN:

for(c = 1 ; c != sizeof hexpowers/sizeof hexpowers[0] ; c++)

if(val < hexpowers[c]) break;

break;

case EIGHT:

for(c = 1 ; c != sizeof octpowers/sizeof octpowers[0] ; c++)

if(val < octpowers[c]) break;

break;

}

if(c < prec) c = prec; else if(prec < c) prec = c;

if(width && flag & NEGSIGN) width--;

if(width > prec) width -= prec; else width = 0;

if((flag & (FILL|BASEM|ALTERN)) == (EIGHT|ALTERN)) {

if(width) width--;

} else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {

if(width > 2) width -= 2; else width = 0;

}

if(flag & FILL) {

Apéndice 2. Introducción al lenguaje C. 73

Profesor Leopoldo Silva Bijit 20-01-2010

if(flag & MANSIGN) pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN) pputc(' ');

else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {

pputc('0');

pputc(flag & UPCASE ? 'X' : 'x');

}

if(width) do pputc('0'); while(--width);

}

else { if(width && !(flag & LEFT)) do pputc(' '); while(--width);

if(flag & MANSIGN) pputc(flag & SPCSIGN ? '-' : '+');

else if(flag & SPCSIGN) pputc(' ');

if((flag & (BASEM|ALTERN)) == (EIGHT|ALTERN)) pputc('0');

else if((flag & (BASEM|ALTERN)) == (SIXTEEN|ALTERN)) {

pputc('0');

pputc(flag & UPCASE ? 'X' : 'x');

}

}

while(prec > c) pputc('0');

while(prec--) {

switch((unsigned char)(flag & BASEM)) {

case TEN:

case UNSIGN:

c = (int)((long)val / dpowers[prec]) % 10 + '0';break;

case SIXTEEN:

c = (flag & UPCASE ? "0123456789ABCDEF" :

"0123456789abcdef")[(int)(val / hexpowers[prec]) & 0xF];

break;

case EIGHT: c = ( (int)((long)val / octpowers[prec]) & 07) + '0';

break;

}

pputc(c);

}

if((flag & LEFT) && width) do pputc(' '); while(--width);

}

}

void miputc(char ch)

{ putchar(ch);}

void mvprintf(const char * f, va_list ap)

{

pb.ptr = 0;

pb.func = miputc; /*putchar */

va_start(ap, f);

_doprnt(&pb, f, ap);

va_end(ap);

74 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

}

char * mvsprintf(char * wh, const char * f, va_list ap)

{ pb.ptr = wh;

pb.func = (void (*)(char))NULL;

va_start(ap, f);

_doprnt(&pb, f, ap);

*pb.ptr++ = 0;

va_end(ap);

return ( char *)(pb.ptr - wh);

}

void mprintf(const char * f, ...)

{ va_list ap;

struct __prbuf pb;

pb.ptr = 0;

pb.func = miputc;

va_start(ap, f);

_doprnt(&pb, f, ap);

va_end(ap);

}

/* mini test */

int main(void)

{ int x=15, y=2678; float f=3.2e-5;

mprintf(" x = %X y = %d\n", x, y);

mprintf(" f = %g \n", f);

return(0);

}

Al disponer del código fuente, éste puede adaptarse a las necesidades del usuario. En caso de ser

empleado en un microcontrolador, con el objeto de disminuir la memoria ocupada por printf, se

pueden recortar algunos modos que no se requieran.

6. Algunas rutinas matemáticas.

Se muestran algunos diseños de funciones matemáticas de biblioteca.

6.1. Trigonométricas.

Para desarrollar el algoritmo, consideremos la relación:

Sen(-x) = -sen(x) lo cual permite mediante un cambio de variable y signo efectuar cálculos

sólo para x>=0.

Apéndice 2. Introducción al lenguaje C. 75

Profesor Leopoldo Silva Bijit 20-01-2010

La variable x se expresa en radianes, y es periódica. Se muestra la gráfica para un período.

Figura A2.10 Función seno.

plot(sin(x), x=0..2*Pi);

Si efectuamos el cambio de variable, w = x/2*Pi, tendremos:

plot(sin(2*Pi*w),w=0..1); cuya gráfica se ilustra a continuación:

Figura A2.11 Reducción a intervalo entre 0 y 1.

Reducción al primer período:

Para considerar la naturaleza periódica de la función, podemos considerar el cambio de

variables:

Z = w – floor(w), cuya gráfica se obtiene con plot(w - floor(w), w=0..5);

76 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Figura A2.12. floor(w).

Que mapea los diferentes intervalos de w entre i e i+1 en el intervalo de z entre 0 y 1. La

función floor(w) trunca el número real al entero menor; en el caso de reales positivos, equivale

al truncamiento del número. Por ejemplo: floor(1.5) = 1.0

Después de este cambio de variables, los valores del argumento estarán acotados. De esta forma

cuando se calcule con valores reales elevados, éstos se reducen a valores entre 0 y 4, y no se

producirán errores cuando se calculen las potencias del argumento al evaluar la serie.

Si efectuamos: m= 4*(w-floor(w)) las variaciones de m serán en el intervalo entre 0 y 4,

cuando w cambia entre cualquier inicio de un período hasta el final de ese período.

Entonces para todos los reales positivos (representables) de w, se puede calcular en el primer

período, para valores de m entre 0 y 4:

plot( sin(2*Pi*m/4 ), m=0..4);

Figura A2.13. Reducción al primer período.

Reducción al primer cuadrante:

Para 4 > m > 2 se tiene que f(m) = - f(m-2) y si se efectúa m=m-2, se tendrá que 0<m<2.

Apéndice 2. Introducción al lenguaje C. 77

Profesor Leopoldo Silva Bijit 20-01-2010

Ahora m está restringido a tomar valores entre 0 y 2.

Para 2> m > 1 se tiene f(m) = f(2-m) y si se efectúa m= 2-m, se tendrá que 0 < m < 1, lo cual

reduce los cálculos al primer cuadrante.

El intervalo donde se calculará el polinomio de aproximación se muestra en la siguiente gráfica:

plot( sin(2*Pi*m/4 ),m=0..1);

Figura A2.14. Reducción al primer cuadrante.

Entonces puede describirse el siguiente algoritmo:

signo = 1.0; /*describe signo positivo */

if(x < 0.0) { x = -x; signo = -signo; } /*Desde ahora sólo argumentos positivos */

x /= TWO_PI; /* 1 radian = 180/Pi Grados. Desde ahora: Inf > x > 0 */

x = 4.0 * (x - floor(x)); /* Reduce al primer período. Desde ahora 4 >= x >= 0 */

if(x > 2.0) { x -= 2.0; signo = -signo;} /* 2 >= x >=0 */

if( x > 1.0) x = 2.0 - x; /* Reduce al primer cuadrante. 1>= x >=0 */

Puede compararse la aproximación por series de potencia (de dos y tres términos) con el

polinomio de Pade, mediante:

plot([x-x^3/6,x-x^3/6+x^5/120, pade(sin(x),x=0,[9,6])], x=0.7..1.7,

y= 0.65..1,color=[red,blue,black], style=[point,line,point]);

78 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Figura A2.15. Series y polinomio de Pade.

Cuando m varía entre 0 y 1, el x de la gráfica anterior varía entre 0 y 2*Pi/4 = 1,571

Se muestra a partir de la ordenada 0,65 para ampliar la zona en que las aproximaciones difieren.

Es preciso calcular polinomios, puede emplearse la función estándar poly, descrita en math.h

Si por ejemplo se desea calcular:

p(x) = d[4]*(x**4)+d[3]*(x**3)+d[2]*(x**2)+d[1]*(x)+d[0]

puede describirse por:

(((d[4]*x + d[3] )*x + d[2] )*x + d[1] )*x +d[0]

Con algoritmo: poli=d[n]; i=n; while (i >0) {i--; poli = poli* x + d[ i-1]};

Debido a que los polinomios son de potencias pares en el denominador, se efectúa el reemplazo

x por x*x. Y para obtener potencias impares en el numerador se multiplica el polinomio del

numerador por x.

El algoritmo completo es:

#include <math.h>

/*Calcula para n=4 el polinomio: d[4]*(x**4)+d[3]*(x**3)+d[2]*(x**2)+d[1]*(x)+d[0] */

double eval_poly(register double x, const double *d, int n)

{ int i;

register double res;

res = d[i = n];

while ( i ) res = x * res + d[--i];

return res;

Apéndice 2. Introducción al lenguaje C. 79

Profesor Leopoldo Silva Bijit 20-01-2010

}

#define PI 3.14159265358979

#define TWO_PI 6.28318530717958

double seno(double x)

{ static const double coeff_a[] = { 207823.68416961012, -76586.415638846949,

7064.1360814006881, -237.85932457812158, 2.8078274176220686 };

static const double coeff_b[] = { 132304.66650864931, 5651.6867953169177,

108.99981103712905, 1.0 };

register double signo, x2;

signo = 1.0;

if(x < 0.0) { x = -x; signo = -signo; } /*Solo argumentos positivos */

x /= TWO_PI; x = 4.0 * (x - floor(x));

if(x > 2.0) { x -= 2.0; signo = -signo;}

if( x > 1.0) x = 2.0 - x;

x2 = x * x;

return signo * x * eval_poly(x2, coeff_a, 4) / eval_poly(x2, coeff_b, 3);

}

Empleando Mapple puede obtenerse el polinomio de Pade, que aproxima a la función seno.

with(numapprox):

pade(sin(x), x=0, [9,6]);

1768969 9 36317 7 80231 5 8234 3

(-------------------- x - -------------- x + ------------- x - -------- x + x ) /

4763930371200 472612140 14321580 55083

631 2 3799 4 911 6

(1 + --------- x + ------------- x + --------------- x )

36722 28643160 1890448560

El siguiente comando dibuja el polinomio:

plot(pade(sin(x),x=0,[9,6]),x=0..10);

Figura A2.16. Polinomio de Pade.

80 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Se aprecia que para x>6 la aproximación de la función seno no es buena.

Se requiere modificar el argumento de la función, de acuerdo al algoritmo:

a:=pade(sin(2*Pi*x/4), x=0, [9,6]);

evalf(denom(a)/10^11,17); Calcula el denominador, dividido por 10^11, con 17 cifras.

24391.323500544000+1034.1371819921864*x^2+19.695328959656098*x^4+

.17656643195797582*x^6

evalf(expand(numer(a)/10^11),17); Calcula el numerador, dividido por 10^11, con 17 cifras.

38313.801360320554*x-14131.500385136448*x^3+1306.7304862998132*x^5-

44.226197226558042*x^7+.52731372638787005*x^9

Los valores de los coeficientes son los que se emplean en la función.

La gráfica del polinomio es la zona donde será evaluado, se muestra a continuación:

plot(pade(sin(2*Pi*x/4),x=0,[9,6]), x=0..4);

Figura A2.17. Polinomio de Pade entre 0 y 4.

6.2. Manipulación de flotantes.

La función floor está basada en el truncamiento de la parte fraccionaria del número real.

Si se tiene: Double d, t ;

Entonces t = (double)(long)(d); es el número truncado, con parte fraccionaria igual a cero.

Primero el molde (long) transforma d a un entero, luego el molde o cast (double) transforma ese

entero a doble.

Si la cantidad de cifras enteras de un double, no pueden ser representadas en un entero largo, la

expresión será errónea. Por ejemplo si el entero largo tiene 32 bits, si las cifras enteras del doble

exceden a 231

-1 se tendrá error.

Apéndice 2. Introducción al lenguaje C. 81

Profesor Leopoldo Silva Bijit 20-01-2010

Un doble IEEE 754 ocupa 64 bits, con un long double de 80 bits, no hay problemas en el

truncamiento.

Un double de 64 bits tiene el rango: 1.7 * (10**-308) to 1.7 * (10**+308) .

Un long double de 80 bits tiene el rango: 3.4 * (10**-4932) to 1.1 * (10**+4932) .

Double floor( double x)

{ double i;

i = (double)(long double)(x);

if(i > x) return i - 1.0;

return i;

}

Luego pueden derivarse el resto de las funciones trigonométricas.

La función coseno, se calcula

#define PImedio 1.570796326794895

double coseno(double x)

{ return seno(x + PImedio); }

La función tangente, se deriva de su definición:

double tangente(double x)

{ return seno(x)/coseno(x); }

El valor absoluto de un doble, se calcula según:

double fabs(double d)

{ if(d < 0.0) return -d; else return d; }

6.3. Acceso a los bits de un número.

En ocasiones resulta conveniente tener acceso a las representaciones internas de los números.

Los programas de este tipo deben considerar el ordenamiento de los bytes dentro de la palabra

de memoria; es decir si son de orden big-endian o little endian.

Estudiaremos varias alternativas de tratamiento. Desde la más simple de interpretar los bytes

dentro de la palabra, pasando por interpretar los enteros largos que constituyen una palabra

mayor; a métodos más generales que emplean uniones y campos; esta última no se recomienda

ya que es dependiente de la implementación del compilador.

Analizaremos la función estándar frexp.

La función frexp extrae la mantisa y el exponente de un real:

double frexp(double x, int * exponente)

Dado un número real de doble precisión x, la función frexp calcula la mantisa m (como un real

de doble precisión) y un entero n (exponente) tal que:

82 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

x = m * (2n) con: 0.5 =< m < 1

Como las funciones, en el lenguaje C, sólo retornan un valor, y si éste es el de la mantisa, debe

pasarse un segundo argumento por referencia: la dirección de un entero; y la función devolverá

el exponente escrito en el entero.

Por esta razón el segundo argumento es un puntero a entero. El valor de la mantisa es el valor

retornado por la función.

Se tiene la siguiente representación externa para un número real:

x = (-1)S 1.M2 2

ee

Donde S es el bit del signo, M2 la mantisa binaria, y ee es la representación externa del

exponente, esto asumiendo representación de reales normalizados en formato IEEE 754.

Dividiendo y multiplicando por dos, obtenemos: x = (-1)S 1.M2 2

-1 2

ee + 1

Entonces el número real que debe retornar la función, como mantisa mayor que un medio y

menor que uno es:

mantisa = (-1)S 1.M2 2

-1

y el exponente, que retorna frexp, debe ser:

exponente = ee + 1.

La función debe extraer la representación interna del exponente, pasarla a representación

externa y sumarle uno para formar el exponente, que retorna la función. Por otra parte debe

convertir el exponente externo con valor menos uno a representación interna, y sobrescribirlo

en la parte binaria dedicada al exponente.

Se tiene que: exponente externo = exponente interno – polarización.

El exponente externo se representa como un número con signo en complemento a dos, y la

polarización es tal que el número más negativo (que tiene simétrico positivo) se represente

como una secuencia de puros ceros.

Para flotantes de simple precisión, que empleen 32 bits, se dedican 8 bits al exponente, el

mayor positivo en complemento a dos es, en decimal, 127; que equivale a 01111111 en binario.

El número más negativo, -127, se representa en complemento a dos como: 10000001,

cumpliéndose que, para este número, la representación interna es: 00000000. La polarización

para tipo float es 127, en decimal.

Para reales de precisión doble, se emplean 64 bits, y 11 para el exponente; en este caso la

polarización es 1023 en decimal, con representación binaria complemento a dos: 01111111111

(0x3FF en hexadecimal).

Apéndice 2. Introducción al lenguaje C. 83

Profesor Leopoldo Silva Bijit 20-01-2010

Entonces para doble precisión, para un exponente externo igual a menos uno, debe escribirse

en la parte que representa el exponente interno: -1 + 1023 = 1022 que equivale a

01111111110 (0x3FE).

Para el exponente retornado por la función se tiene: ee + 1 = ei – 1023 + 1 = ei –1022.

6.3.1. Acceso por caracteres (bytes).

Para extraer el exponente, supongamos que el puntero a carácter pc apunta al byte más

significativo del double; y que ps apunta al segundo.

unsigned char * pc;

unsigned char * ps;

unsigned int ei;

int exponente;

Los últimos 7 bits del primer byte (*pc & 0x7F) son los primeros siete del exponente (ya que el

primero se emplea para el signo del número).

Los primeros 4 bits del segundo, son los últimos 4 del exponente interno. Para esto es preciso

desplazar en forma lógica, en cuatro bits, esto se logra con: *ps>>4.

Para formar el exponente interno se requiere desplazar, en forma lógica, los primeros siete bits

en cuatro posiciones hacia la izquierda.

Entonces: ei = (*pc & 0x7F)<<4 | (*ps>>4) forma el exponente interno, como una secuencia

binaria de 11 bits. Al depositarlo en un entero sin signo, los primeros bits quedan en cero

(desde el doceavo hasta el largo del entero).

Finalmente, se logra:

exponente = ei –1022;

Para sobrescribir el número 0x3FE, en las posiciones en que va el exponente interno, se requiere

modificar los últimos siete bits del primer byte, para no alterar el signo del número. Esto se

logra haciendo un and con la máscara binaria 10000000(0x80) y luego un or con la máscara

binaria 00111111(0x3F)

Es decir:

*pc = (*pc & 0x80) | 0x3F;

Para el segundo byte, sólo se deben sobrescribir los primeros cuatro. Esto se logra haciendo un

and con la máscara binaria 00001111(0x0F) y luego un or con la máscara binaria

11100000(0xE0)

Es decir:

*ps = (*ps & 0x0F) | 0xE0;

El resto de los bits con la mantisa del número no deben modificarse.

Para apuntar a los primeros dos bytes, debe conocerse el orden de los bytes dentro de las

palabras de la memoria. Esto es dependiente del procesador. En algunos sistemas el primer byte

(el más significativo dentro del double) tiene la dirección menor, en otros es la más alta.

84 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Como casi todos los tipos de datos que maneja un procesador suelen ser múltiplos de bytes,

para obtener la dirección de una variable de cierto tipo (en este caso de un double) en unidades

de direcciones de bytes puede escribirse:

unsigned char * pc = (unsigned char *)&number;

unsigned char * ps;

El moldeo (cast) convierte la dirección de la variable number en un puntero a carácter.

Luego de esto, considerando que un double está formado por 8 bytes se tiene: pc += 7; ps=pc-1;

para sistemas en que el byte más significativo tiene la dirección de memoria más alta.

O bien: ps = pc +1; si el byte más significativo tiene la dirección menor; en este caso, no es

preciso modificar pc.

Entonces el código completo de la función frexp puede escribirse:

double frexp(double number, int *exponent)

{

unsigned char * pc = (unsigned char *)&number;

unsigned char * ps;

unsigned int ei;

pc += 7; ps=pc-1; /* Big endian. O bien: ps=pc +1, para little endian*/

ei = ((*pc & 0x7F)<<4) | (*ps>>4); /*extrae exponente interno */

*exponent = ei - 1022; /*escribe en el entero que se pasa por referencia*/

*pc = (*pc & 0x80) | 0x3F; /*deja exponente igual a -1 */

*ps = (*ps & 0x0F) | 0xE0;

return( number);

}

Sin embargo esta rutina tiene varias limitaciones. No trata número sub-normales y no detecta

representaciones de infinito y NaN.

6.3.2. Uso de dos enteros largos sin signo, para representar los bits de un double.

Obviamente esto sólo puede aplicarse si los enteros largos son de 32 bits.

Considerando ei como el exponente interno y ee como el exponente externo, se tienen:

ee = ei -1023; ei = ee + 1023

Entonces, de acuerdo a la interpretación IEEE 754, se tiene que:

Con ei = 0 y M2 != 0 se tienen números subnormales que se interpretan según:

N = (-1)S*0.M2*pow(2, -1022)

Con ei = 0 y M2 == 0 se tienen la representación para el 0.0 según:

N = (-1)S*0.0

Apéndice 2. Introducción al lenguaje C. 85

Profesor Leopoldo Silva Bijit 20-01-2010

Con 0< ei < 2047 se logran en forma externa: -1023 < ee < 1024, se tienen

representaciones para números normales, según:

N = (-1)S*1.M2*pow(2, ee)

Con ei = 2047 y M2 == 0 (ee = 1024) se tiene la representación para el según:

N = (-1)S*INF

Con ei = 2047 y M2 != 0 se tienen la representación para el según:

N = NaN

El exponente interno se está considerando de 11 bits, sin signo. En la norma IEEE 754 debe

considerarse números con signo. Para los dos últimos casos esto implica ei = -1.

Entonces con las definiciones:

unsigned long int *pm2=(unsigned long int *)&number;

unsigned long int *pm1=pm2+1;

Podemos apuntar con pm1 al entero largo más significativo, donde se almacena el signo, los 11

bits del exponente y 4 bits de la mantisa.

Podemos conocer el signo del número mediante:

int signo=( int)((*pm1)>>31);

Dejando en signo un uno si el número es negativo; y cero si es positivo.

La extracción del exponente interno, sin signo, se logra con:

unsigned int ei= (unsigned int)(((*pm1)<<1)>>21);

Primero se le quita el signo, y luego se desplaza a la derecha en 21 bits.

Si se deseara manipular el exponente interno como número con signo, habría que definir:

int ei=(int) ( ( ( long int)((*pm1)<<1)) >>21);

Se corre a la derecha el largo con signo, y luego se convierte a entero.

Las siguientes definiciones, nos permiten extraer la parte más significativa de la mantisa en m1

(20 bits), y la menos significativa en m2:

unsigned long m1=(*pm1)&0x000FFFFFL;

unsigned long m2=*pm2;

Para tratar números subnormales es preciso normalizar la mantisa, corrigiendo el exponente. En

el código se multiplica por dos el número y se resta uno al exponente, mientras primer dígito de

la mantisa sea diferente de cero. Este primer dígito se detecta con la condición: (

(*pm1)&0x00080000L)==0

Setear el exponente externo en -1, para tener mantisa decimal que cumpla:

0.5 =< m < 1

se logra, como se explico antes, dejando el exponente interno en: 01111111110 (0x3FE).

Lo cual se logra con: ((*pm1)&0x800FFFFFL)| 0x3FE00000L

86 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

Para probar la rutina se pueden usar los siguientes valores:

Para comprobar el cero: number = 0.0;

Para verificar los subnormales: number = 0.125*pow(2,-1023);

Debe resultar como respuesta: 0.5*pow(2,-1025);

Para probar número grandes: number = 1.0*pow(2, 1023);

Para probar el infinito: number = 1/0.0;

Para probar un Not a Number: number = 0.0/0.0;

El código completo para la función:

double frexp(double number, int *exponent)

{

unsigned long int *pm2=(unsigned long int *)&number;

unsigned long int *pm1=pm2+1;

unsigned long m1=(*pm1)&0x000FFFFFL;

unsigned long m2=*pm2;

unsigned int ei= (unsigned int)(((*pm1)<<1)>>21);

if (ei==0)

{ if((m2|m1)==0) {*exponent=0;} /* 0.0 */

else {*exponent=-1022;

while( ((*pm1)&0x00080000L)==0) {number*=2;(*exponent)--;}

*pm1=((*pm1)&0x800FFFFFL) | 0x3FF00000L; number--;

}

else

if (ei==2047) {if ((m2|m1)==0) printf("infinito \n"); /*ei==-1 con signo*/

else printf("NaN \n");

*exponent = 1025;

*pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L;}

else

{ *exponent = ei - 1022; /*escribe en el entero que se pasa por referencia*/

*pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L;

}

return( number);

}

Nótese que la misma rutina que no trata los casos subnormales y el cero, podría escribirse:

double frexp(double number, int *exponent)

{

unsigned long int *pm1=((unsigned long int *)&number) +1;

*exponent = ( (unsigned int)(((*pm1)<<1)>>21)) - 1022;

*pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L;

return( number);

}

Que equivale al comportamiento de la primera rutina que manipulaba los bytes del double.

Apéndice 2. Introducción al lenguaje C. 87

Profesor Leopoldo Silva Bijit 20-01-2010

En los ejemplos de uso de union y campos, se desarrollará la misma rutina anterior.

6.3.5. Uso de union.

Otra forma de accesar una zona de la memoria es a través de la estructura unión, que permite

definir variables que comparten una zona común del almacenamiento. La unión asigna a la

variable (de tipo union) un espacio de memoria suficiente para almacenar la variable de la union

de mayor tamaño.

En el ejemplo siguiente, la union denominada buffer, puede verse como un double o como una

estructura denominada pbs. Las variables anteriores tienen la misma dirección de memoria, y se

accesan de manera similar a una estructura. Si se escribe en una variable, se modifica la otra.

La estructura pbs, define 64 bits, el mismo tamaño que el double. Y permite identificar los dos

bytes más significativos del double, b0 y b1, en caso de que el byte más significativo esté

ubicado en la dirección menor. Y b6 y b7 si el más significativo del double está asociado a la

dirección mayor.

union buf

{ struct bts

{unsigned char b0;

unsigned char b1;

unsigned char b[4];

unsigned char b6;

unsigned char b7; /*el más significativo con dirección mayor*/

} pbs;

double d;

} buffer;

El valor +2.0 en doble precisión, equivale al valor 0x40000000 en formato IEEE 754. El signo

es cero, la mantisa normalizada es cero. Y el exponente externo es +1.

Para el exponente interno se cumple que: ei = ee + 1023

Empleando 11 bits en representación de números con signo polarizados, se tiene que 1023

decimal equivale a 0x3FF en hexadecimal.

Entonces ei = 00000000001 + 01111111111 = 10000000000 = 0x400 en hexadecimal. Y

resulta que el byte más significativo del double es 0x40, que equivale al binario: 01000000.

Con la siguiente asignación puede escribirse en el double de la unión:

buffer.d = 2.0;

Y leer los bytes de la unión, accesando por su nombre los bytes de la estructura pbs.

if (buffer.pbs.b7==0x40) printf("el byte más significativo del double tiene la dirección

mayor\n");

if (buffer.pbs.b0==0x40) printf("el byte más significativo del double tiene la dirección

menor\n");

88 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

El siguiente diseño genera una función frexp portable a plataformas que empleen big o little

endian para enumerar los bytes dentro de una palabra de memoria. La manipulación de los

bytes es similar al diseño basado en leer bytes de una variable de gran tamaño, en base a

punteros.

double frexp(double number, int *exponent)

{

union buf

{ struct bts

{unsigned char b0;

unsigned char b1;

unsigned char b[4];

unsigned char b6;

unsigned char b7; /*el más significativo con dirección mayor*/

} pbs;

double d;

} buffer;

unsigned int ei;

buffer.d=2.0;

if (buffer.pbs.b7==0x40)

{buffer.d = number;

ei=(unsigned int)(buffer.pbs.b7 & 0x7F)<<4|((unsigned int)(buffer.pbs.b6>>4));

*exponent=-1022+ei;

buffer.pbs.b7 = (buffer.pbs.b7 & 0x80)|0x3F;

buffer.pbs.b6 = (buffer.pbs.b6 & 0x0F)|0xE0;

return( buffer.d);

}

if (buffer.pbs.b0==0x40)

{buffer.d = number;

ei=(unsigned int)(buffer.pbs.b0 & 0x7F)<<4|((unsigned int)(buffer.pbs.b1>>4));

*exponent=-1022+ei;

buffer.pbs.b0 = (buffer.pbs.b0 & 0x80)|0x3F;

buffer.pbs.b1 = (buffer.pbs.b6 & 0x0F)|0xE0;

return( buffer.d);

}

*exponent = 0; /*no es little ni big endian */

return( number);

}

6.4.5. Uso de campos (fields)

El lenguaje C provee una estructura de campos de bits. Un campo de bits es un elemento de una

estructura que es definida en términos de bits. Es dependiente de la implementación del

lenguaje en un determinado procesador, pero asumiremos que está implementada con a lo

menos 16 bits de largo, en total.

Entonces en el ejemplo siguiente, dentro de la unión buffer, se tiene la estructura pbs, que a su

vez está formada por la estructura campos1, el arreglo de 4 caracteres b, y la estructura

Apéndice 2. Introducción al lenguaje C. 89

Profesor Leopoldo Silva Bijit 20-01-2010

campos2. Tanto la estructura pc1 como pc2 están formadas por campos de bits. Se han definido

de largo 11 los campos exp1 y exp2, que tratan como secuencias de bits a las posibles

ubicaciones del exponente de un double en formato IEEE 754.

union buf

{ struct bts

{ struct campos1

{ unsigned int signo1 :1;

unsigned int exp1 :11;

unsigned int man1 :4;

} pc1;

unsigned char b[4];

struct campos2

{ unsigned int man2 :4;

unsigned int exp2 :11;

unsigned int signo2 :1; /*el byte más significativo con dirección mayor*/

} pc2;

} pbs;

double d;

} buffer;

Con la siguiente asignación puede escribirse en el double de la unión:

buffer.d = 2.0;

Y leer los grupos de bits de la unión, accesando por su nombre los campos de la estructura pbs.

if (buffer.pbs.pc2.exp2==0x400) printf("el byte más significativo del double tiene la

dirección mayor\n");

if (buffer.pbs.pc1.exp1==0x400) printf("el byte más significativo del double tiene la

dirección menor\n");

El siguiente diseño implementa frexp usando estructuras de campos de bits (fields).

double pfrexp(double number,int *exponent)

{

union buf

{ struct bts

{ struct campos1

{ unsigned int signo1 :1;

unsigned int exp1 :11;

unsigned int man1 :4;

} pc1;

unsigned char b[4];

struct campos2

{ unsigned int man2 :4;

unsigned int exp2 :11;

unsigned int signo2 :1; /*el más significativo con dirección mayor*/

} pc2;

90 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

} pbs;

double d;

} buffer;

unsigned int ei;

buffer.d=2.0;

if (buffer.pbs.pc2.exp2==0x400)

{buffer.d = number;

ei=(unsigned int)(buffer.pbs.pc2.exp2);

*exponent=-1022+ei;

buffer.pbs.pc2.exp2 = 0x3FE;

return( buffer.d);

}

if (buffer.pbs.pc1.exp1==0x400)

{buffer.d = number;

ei=(unsigned int)(buffer.pbs.pc1.exp1);

*exponent=-1022+ei;

buffer.pbs.pc1.exp1 = 0x3FE;

return( buffer.d);

}

*exponent=0;

return( number);

}

double mfloor( double x)

{ double i;

int expon;

i= frexp(x, &expon);

if(expon < 0) return x < 0.0 ? -1.0 : 0.0;

/* pow(2,52) = 4503599627370496*/

if((unsigned) expon > 52) return x; /*se asume entero */

/* pow(2,31) = 2147483648 */

if (expon < 32 )

{i = (double)(long)(x); /* cabe en long x */

if(i > x) return i - 1.0;}

/*debe truncarse el double cuya parte entera no cabe en un long */

return i;

}

Referencias.

Niklaus Wirth, “Algorithms + Data Structures = Programs”, Prentice-Hall 1975.

Apéndice 2. Introducción al lenguaje C. 91

Profesor Leopoldo Silva Bijit 20-01-2010

Índice general.

APÉNDICE 2 .............................................................................................................................................. 1

INTRODUCCIÓN AL LENGUAJE C. ................................................................................................... 1

1. FUNCIONES. .......................................................................................................................................... 1 1.1. Abstracción de acciones y expresiones. ....................................................................................... 1 1.2. Prototipo, definición, invocación. ................................................................................................ 2 1.3. Alcances del lenguaje C. .............................................................................................................. 3 1.4. Paso de argumentos por valor. .................................................................................................... 4 1.5. Paso por referencia. ..................................................................................................................... 4 1.6. Frame. .......................................................................................................................................... 5 1.7. Algunos conceptos básicos ........................................................................................................... 6

1.7.1. Datos. .................................................................................................................................................... 6 Enteros con signo. ....................................................................................................................................... 6 Enteros sin signo. ....................................................................................................................................... 8 Enteros Largos. ........................................................................................................................................... 8 Largos sin signo. ......................................................................................................................................... 8 Números Reales. ( float ) ............................................................................................................................ 8 Carácter. ...................................................................................................................................................... 9 Strings. ........................................................................................................................................................ 9

1.7.2. Acciones. ............................................................................................................................................... 9 Secuencia. ................................................................................................................................................... 9 Alternativa. ............................................................................................................................................... 10 Repetición. ................................................................................................................................................ 10 For. ............................................................................................................................................................ 10 Abstracción. .............................................................................................................................................. 10

1.7.3. Entrada. Salida. .................................................................................................................................... 10 Ejemplos. .................................................................................................................................................. 11

2. TIPO CHAR. ......................................................................................................................................... 12 2.1. Valores. ...................................................................................................................................... 12 2.1. Definición de variables y constantes de tipo char. ..................................................................... 12 2.2. Caracteres ASCII. ...................................................................................................................... 12 2.3. Secuencias de escape. ................................................................................................................. 14 2.4. Archivos de texto y binarios. ...................................................................................................... 14 2.5. Expresiones. ............................................................................................................................... 15 2.6. Entrada-Salida ........................................................................................................................... 16

Entrada y salida con formato. ........................................................................................................................ 17 2.7. Funciones. .................................................................................................................................. 18 2.8. Macros. ....................................................................................................................................... 19 2.9. Macros con argumentos. ............................................................................................................ 21 2.10. Biblioteca. ctype.c .................................................................................................................. 22

3. STRINGS. ............................................................................................................................................. 24 3.1. Definición de string. ................................................................................................................... 24

3.1.1. Arreglo de caracteres. .......................................................................................................................... 24 3.1.2. Puntero a carácter. ................................................................................................................................ 25

3.2. Strcpy. ......................................................................................................................................... 25 3.3. Strncpy. ....................................................................................................................................... 27

92 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 20-01-2010

3.4. Strcat. .......................................................................................................................................... 28 3.5. Strncat. ........................................................................................................................................ 28 3.6. Strlen. .......................................................................................................................................... 29 3.7. Strcmp. ........................................................................................................................................ 29 3.8. Strncmp. ...................................................................................................................................... 30 3.9. Strstr. ........................................................................................................................................... 30 3.10. Strchr. ....................................................................................................................................... 31 3.11. Strrchr. ...................................................................................................................................... 32 3.12. Strpbrk....................................................................................................................................... 32 3.13. Strcspn....................................................................................................................................... 32 3.14. Strspn. ....................................................................................................................................... 33 3.15. Strtok. ........................................................................................................................................ 33 3.16. Strdup. ....................................................................................................................................... 34 3.17. Memcpy. .................................................................................................................................... 35 3.18. Memccpy. .................................................................................................................................. 36 3.19. Memmove. ................................................................................................................................. 36 3.20. Memcmp. ................................................................................................................................... 37 3.21. Memset. ..................................................................................................................................... 38 3.22. Movimientos de bloques, dependientes del procesador. .......................................................... 38

4. RUTINAS DE CONVERSIÓN. .................................................................................................................. 40 4.1. De enteros a caracteres. Ltoa. Long to Ascii. ........................................................................... 40 4.2. De secuencias de caracteres a enteros. ...................................................................................... 42 4.3. De dobles a caracteres. ............................................................................................................... 45 4.4. Imprime mantisa. ........................................................................................................................ 46 4.5. Rutinas más eficientes para convertir un número punto flotante binario a punto flotante

decimal. .............................................................................................................................................. 47 4.5.1. Potencias de 10. .................................................................................................................................... 47 4.5.2. Imprime exponente de flotante. ............................................................................................................ 48 4.5.3. Redondeo de la mantisa........................................................................................................................ 49 4.5.4. Convierta. Algoritmo dos. .................................................................................................................... 50 4.5.5. Imprime mantisa. Algoritmo dos. ......................................................................................................... 50

5. DISEÑO DE FUNCIONES CON UN NÚMERO VARIABLE DE ARGUMENTOS. ............................................... 51 5.1. Argumentos estándar. ................................................................................................................. 52 5.2. Estructura de printf. .................................................................................................................... 54 5.3. Estructura de scanf. .................................................................................................................... 55 5.4. Salida formateada en base a llamados al sistema. SPIM. .......................................................... 56

5.5. DESARROLLO DE PRINTF EN BASE A PUTCHAR. ................................................................................ 65 6. ALGUNAS RUTINAS MATEMÁTICAS...................................................................................................... 74

6.1. Trigonométricas. ......................................................................................................................... 74 6.2. Manipulación de flotantes. .......................................................................................................... 80 6.3. Acceso a los bits de un número. .................................................................................................. 81

6.3.1. Acceso por caracteres (bytes). .............................................................................................................. 83 6.3.2. Uso de dos enteros largos sin signo, para representar los bits de un double. ........................................ 84 6.3.5. Uso de union. ....................................................................................................................................... 87 6.4.5. Uso de campos (fields) ......................................................................................................................... 88

REFERENCIAS. ......................................................................................................................................... 90 ÍNDICE GENERAL. .................................................................................................................................... 91 ÍNDICE DE FIGURAS. ................................................................................................................................ 93

Apéndice 2. Introducción al lenguaje C. 93

Profesor Leopoldo Silva Bijit 20-01-2010

Índice de figuras.

FIGURA A2.1. TABLA ASCCI. .................................................................................................................... 13 FIGURA A2.2. ARREGLO DE CARACTERES. .................................................................................................. 25 FIGURA A2.3. COPIA DE STRINGS. ............................................................................................................... 26 FIGURA A2.4. CONCATENA STRINGS. .......................................................................................................... 28 FIGURA A2.5. LARGO STRING. .................................................................................................................... 29 FIGURA A2.6. PUNTEROS DESPUÉS DE PRIMER WHILE. ................................................................................ 32 FIGURA A2.7. STRTOK ................................................................................................................................ 33 FIGURA A2.8 MEMMOVE. ........................................................................................................................... 37 FIGURA A2.9 ESTRUCTURA FRAME. ............................................................................................................ 51 FIGURA A2.10 FUNCIÓN SENO. ................................................................................................................... 75 FIGURA A2.11 REDUCCIÓN A INTERVALO ENTRE 0 Y 1. .............................................................................. 75 FIGURA A2.12. FLOOR(W). .......................................................................................................................... 76 FIGURA A2.13. REDUCCIÓN AL PRIMER PERÍODO........................................................................................ 76 FIGURA A2.14. REDUCCIÓN AL PRIMER CUADRANTE. ................................................................................. 77 FIGURA A2.15. SERIES Y POLINOMIO DE PADE. .......................................................................................... 78 FIGURA A2.16. POLINOMIO DE PADE. ......................................................................................................... 79 FIGURA A2.17. POLINOMIO DE PADE ENTRE 0 Y 4. ..................................................................................... 80