Programación en C

Anuncio
1
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.
Profesor Leopoldo Silva Bijit
20-01-2010
2
Estructuras de Datos y Algoritmos
#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)
{
return ( 3*x*x + 5);
}
/* definición */
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
3
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.
Profesor Leopoldo Silva Bijit
20-01-2010
4
Estructuras de Datos y Algoritmos
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);
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
5
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
Profesor Leopoldo Silva Bijit
20-01-2010
6
Estructuras de Datos y Algoritmos
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)
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
7
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 Equivalente Decimal Equivalente Decimal Entero sin
interna
Complemento a dos. Complemento a uno. 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.
Profesor Leopoldo Silva Bijit
20-01-2010
8
Estructuras de Datos y Algoritmos
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:
( en C se puede anotar: i++)
i = i +1;
quedará almacenado 6.
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;
También puede definirse y autoiniciarse con un valor:
int i = 5 ; /* define e inicia i */
c)
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;
double y;
/* define flotante */
/*Define un real en doble precisión. (10 cifras decimales de precisión).*/
long double z; /* real de precisión extendida. */
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
9
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.
Profesor Leopoldo Silva Bijit
20-01-2010
10
Estructuras de Datos y Algoritmos
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*/
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
11
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);
}
Profesor Leopoldo Silva Bijit
20-01-2010
12
Estructuras de Datos y Algoritmos
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;
ch = „c‟;
declara una variable de tipo char.
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
H
D
H
D
H
D
13
H
D
H
D
H
00 NULL 00 10 DEL 16 20
32 30 0 48 40 @ 64 50
01 SOH 01 11 DC1 17 21 ! 33 31 1 49 41 A 65 51
02 STX 02 12 DC2 18 22 " 34 32 2 50 42 B 66 52
03 EXT 03 13 DC3 19 23 # 35 33 3 51 43 C 67 53
04 EOT 04 14 DC4 20 24 $ 36 34 4 52 44 D 68 54
05 ENQ 05 15 NAK 21 25 % 37 35 5 53 45 E 69 55
06 ACK 06 16 SYN 22 26 & 38 36 6 54 46 F 70 56
07 BEL 07 17 ETB 23 27 ' 39 37 7 55 47 G 71 57
08
BS
08 18 CAN 24 28 ( 40 38 8 56 48 H 72 58
09 TAB 09 19 EM 25 29 ) 41 39 9 57 49 I 73 59
0a
LF
10 1a SUB 26 2a * 42 3a : 58 4a J 74 5a
0b VT 11 1b ESC 27 2b + 43 3b ; 59 4b K 75 5b
0c
FF
12 1c FS 28 2c , 44 3c < 60 4c L 76 5c
0d CR 13 1d GS 29 2d - 45 3d = 61 4d M 77 5d
0e
SO
14 1e RS 30 2e . 46 3e > 62 4e N 78 5e
0f
SI
15 1f US 31 2f / 47 3f ? 63 4f O 79 5f
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
D
H
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
60
61
62
63
64
65
66
67
68
69
6a
6b
6c
6d
6e
6f
D
`
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
H
70 p
71 q
72 r
73 s
74 t
75 u
76 v
77 w
78 x
79 y
7a z
7b {
7c |
7d }
7e ~
7f del
D
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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.
Profesor Leopoldo Silva Bijit
20-01-2010
14
Estructuras de Datos y Algoritmos
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
15
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.
Profesor Leopoldo Silva Bijit
20-01-2010
16
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
17
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'
Profesor Leopoldo Silva Bijit
20-01-2010
18
Estructuras de Datos y Algoritmos
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')
return (char)c;
}
c &= ~('a' - 'A');
char tolower(int c)
{
if((char)c <= 'Z' && (char)c >= 'A')
return (char)c;
}
c |= ('a' - 'A');
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:
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
19
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>
Profesor Leopoldo Silva Bijit
20-01-2010
20
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
21
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).
Profesor Leopoldo Silva Bijit
20-01-2010
22
Estructuras de Datos y Algoritmos
#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 */
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
_N,
_P,
_U,
_U,
_U,
_P,
_L,
_L,
_L,
};
23
_N, _P, _P, _P, _P, _P, _P,
/* 8, 9, :, ;, <, =, >, ? */
_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,
/* H, I, J, K, L, M, N, O */
_U, _U, _U, _U, _U, _U, _U,
/* P, Q, R, S, T, U, V, W */
_U, _U, _P, _P, _P, _P, _P,
/* X, Y, Z, [, \, ], [ , ^, _ */
_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, /* h, i, j, k, l, m, n, o */
_L, _L, _L, _L, _L, _L, _L, /* p, q, r, s, t, u, v, w */
_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
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
isascii(c)
iscntrl(c)
isupper(c)
islower(c)
isalpha(c)
isdigit(c)
isxdigit(c)
isalnum(c)
isspace(c)
ispunct(c)
isprint(c)
isgraph(c)
( (unsigned)(c) < 128)
(_ctype_[(unsigned char) (c) + 1]&_C)
(_ctype_[(unsigned char) (c) + 1]&_U)
(_ctype_[(unsigned char) (c) + 1]&_L)
(_ctype_[(unsigned char) (c) + 1]&(_U | _L))
(_ctype_[(unsigned char) (c) + 1]&_N)
(_ctype_[(unsigned char) (c) + 1]&(_N | _X))
(_ctype_[(unsigned char) (c) + 1]&(_U | _L | _N))
(_ctype_[(unsigned char) (c) + 1]&_S)
(_ctype_[(unsigned char) (c) + 1]&_P)
(_ctype_[(unsigned char) (c) + 1]&(_P | _U | _L | _N | _S))
(_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
*/
Profesor Leopoldo Silva Bijit
20-01-2010
24
#define _C
#define _X
Estructuras de Datos y Algoritmos
_ISbit(5)
_ISbit(6)
/* Control
/* 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 NULL 0
/* define tipo requerido por sizeof */
/* 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;
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
25
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;
}
Profesor Leopoldo Silva Bijit
20-01-2010
26
Estructuras de Datos y Algoritmos
destino
cp
fuente
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
27
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.
Profesor Leopoldo Silva Bijit
20-01-2010
28
Estructuras de Datos y Algoritmos
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;
destino
}
cp
fuente
Figura A2.4. Concatena strings.
3.5. Strncat.
strncat concatena al final del string destino a lo más n caracteres del string fuente.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
29
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.
s
\0
cp
Figura A2.5. Largo string.
3.7. Strcmp.
Compara dos strings.
Profesor Leopoldo Silva Bijit
20-01-2010
30
Estructuras de Datos y Algoritmos
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)
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
31
{ 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.
Profesor Leopoldo Silva Bijit
20-01-2010
32
Estructuras de Datos y Algoritmos
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;
}
cp
\0
s
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++;}
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
33
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.
s1
t1
s2
t2
s2
t2
sp
Figura A2.7. Strtok
Profesor Leopoldo Silva Bijit
20-01-2010
s2
34
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
35
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.
Profesor Leopoldo Silva Bijit
20-01-2010
36
Estructuras de Datos y Algoritmos
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 )
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
37
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).
d
s < d && s+n >= d
s + n <d
s>=d
s
s
n
s
d
n
d
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
Profesor Leopoldo Silva Bijit
20-01-2010
38
Estructuras de Datos y Algoritmos
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
BIG ENDIAN
PDP ENDIAN
"1234"
"4321"
"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;
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
39
/* 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
Profesor Leopoldo Silva Bijit
20-01-2010
40
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
41
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>
Profesor Leopoldo Silva Bijit
20-01-2010
42
Estructuras de Datos y Algoritmos
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].
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
43
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;
Profesor Leopoldo Silva Bijit
20-01-2010
44
Estructuras de Datos y Algoritmos
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)
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
45
{ 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.
Profesor Leopoldo Silva Bijit
20-01-2010
46
Estructuras de Datos y Algoritmos
/*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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
47
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)
Profesor Leopoldo Silva Bijit
20-01-2010
48
Estructuras de Datos y Algoritmos
{ 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)
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
49
{
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:
Profesor Leopoldo Silva Bijit
20-01-2010
50
Estructuras de Datos y Algoritmos
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;
}
}
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
51
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:
p
a
b
c
Las
direcciones
aumentan
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)
Profesor Leopoldo Silva Bijit
20-01-2010
52
Estructuras de Datos y Algoritmos
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);
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
va_end(ap);
53
/* 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) )
Profesor Leopoldo Silva Bijit
20-01-2010
54
Estructuras de Datos y Algoritmos
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 */
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
55
/* 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;
Profesor Leopoldo Silva Bijit
20-01-2010
56
Estructuras de Datos y Algoritmos
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
57
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.
Profesor Leopoldo Silva Bijit
20-01-2010
58
Estructuras de Datos y Algoritmos
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:
_sss:
putchar:
.rdata
.align 2
.word 0 # espacio para almacenar un char terminado en \0.
.globl putchar
.text
.align 2
la
$t8,_sss
sw
$a0,0($t8)
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
move $a0,$t8
la
$v0,4
syscall
j
$ra
59
# la $a1,4
# jal syscall
.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
sw
lw
b
$t8,4+32($sp)
$t8,-4+32($sp) #
$s8,0+32($sp) #
_tstcnd
va_start(ap, format);
for(p = format; *p ; p++) {
_blkfor:lb
la
beq
lb
jal
b
$t8,($s8)
$t7,37
$t8,$t7,_espc
$a0,($s8)
putchar
_siga
#
# '%'
if( *p !='%') {putchar(*p); continue;}
_espc: la
move
lb
la
beq
la
beq
blt
la
beq
b
$t8,1($s8)
$s8,$t8
$s7,($t8)
$t8,100
$s7,$t8,_esd
$t7,102
$s7,$t7,_esf
$s7,$t8,_siga1
$t8,115
$s7,$t8,_ess
_siga1
#
switch( *++p) {
_esd:
$t8,-4+32($sp) #
lw
Profesor Leopoldo Silva Bijit
# 'd'
# 'f'
# 's'
syscall(va_arg(ap, int),1);
20-01-2010
60
Estructuras de Datos y Algoritmos
la
sw
lw
la
syscall
b
$t8,4($t8)
$t8,-4+32($sp)
$a0,-4($t8)
$v0,1
# la $a1,1
# jal syscall
_siga2
#
break;
_esf:
lw
and
beq
lw
la
sw
_nosuma:lw
la
sw
l.d
la
syscall
b
$t8,-4+32($sp) #
if ((int)ap&4) ap=(va_list)((int)ap +4);
$t8,$t8,4
$t8,$0,_nosuma
$t8,-4+32($sp)
$t8,4($t8)
$t8,-4+32($sp)
$t8,-4+32($sp) #
syscall(va_arg(ap, double),3);
$t8,8($t8)
$t8,-4+32($sp)
$f12,-8($t8)
#pasa eldoble en reg. doble $f12
$v0,3
# la $a1,3
# jal syscall
_siga2
#
break;
_ess:
$t8,-4+32($sp) #
syscall(va_arg(ap, char *),4);
$t8,4($t8)
$t8,-4+32($sp)
$a0,-4($t8)
$v0,4
# la $a1,4
# jal syscall
lw
la
sw
lw
la
syscall
_siga1:
_siga2:
#
}
_siga: la
_tstcnd:lb
bne
sw
#
break;
$s8,1($s8)
#
$t8,($s8)
$t8,$0,_blkfor
for(p = format; *p ; p++) {
$0,-4+32($sp) #
va_end(ap);
#}
lw
lw
lw
addu
j
.end printf
$s7,16($sp)
$s8,20($sp)
$ra,24($sp)
$sp,$sp,32
$ra
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
61
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
syscall
li $a1,largo
li $v0, 8
la $a0, str
syscall
*/
# system call code for read_double
# retorno en $f0
#
# system call code for read_str
# buffer of string to read
# 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);*/
Profesor Leopoldo Silva Bijit
20-01-2010
62
Estructuras de Datos y Algoritmos
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
.word 0
_sss:
.globl getchar
.text
.align 2
getchar:
la
li
la
syscall
la
lw
j
$a0,_sss
$a1,2
$v0,8
#
syscall(pc,2,8);
$t8,sss
$v0,0($t8)
$ra
.globl scanf
.text
#void scanf(char * format,...)
.text
.align 2
scanf:
.frame $sp,56,$31
addu $sp,$sp,-56
.mask 0xc0e00000,-24
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
sw
sw
sw
sw
sw
sw
sw
sw
sw
63
$s5,16($sp)
$s6,20($sp)
$s7,24($sp)
$s8,28($sp)
$ra,32($sp)
$a0,56($sp)
$a1,60($sp)
$a2,64($sp)
$a3,68($sp)
#{
#
la
$t8,4+56($sp) #
sw
$t8,-4+56($sp)
for(p = format; *p ; p++) {
lw
b
va_start(ap, format);
$s8,0+56($sp)
_tstscn
#
if( *p !='%') continue;
_blkscn:lb
la
beq
b
$t8,($30)
$t7,37
# '%'
$t8,$t7,_swscn
_cntscn
_swscn:la
move
lb
la
beq
bgt
la
beq
b
_tsts: la
beq
b
$t8,1($s8)
$s8,$t8
$s5,($t8)
$t8,108
$s5,$t8,_lfscn
$s5,$t8,_tsts
$t8,100
$s5,$t8,_dscn
_brkscn
$t8,115
$s5,$t8,_sscn
_brkscn
_dscn: lw
la
sw
lw
sw
la
syscall
lw
sw
$t8,-4+56($sp) #
$t8,4($t8)
$t8,-4+56($sp)
$t8,-4($t8)
$t8,-8+56($sp)
$v0,5
#
#
switch( *++p) {
# 'l'
# 'd'
# 's'
pi=(int *) va_arg(ap, int*);
*pi=syscall(5);
$t7,-8+56($sp)
$v0,($t7)
Profesor Leopoldo Silva Bijit
20-01-2010
64
Estructuras de Datos y Algoritmos
b
_brkscn
#
break;
_lfscn: lw
la
sw
lw
sw
la
syscall
lw
s.d
b
$t8,-4+56($sp) #
$t8,4($t8)
$t8,-4+56($sp)
$t8,-4($t8)
$t8,-12+56($sp)
$v0,7
#
_sscn: lw
la
sw
lw
$t8,-4+56($sp) #
$t8,4($t8)
$t8,-4+56($sp)
$s6,-4($t8)
pc=(char *) va_arg(ap, char *);
move
_tsteos:move
la
lb
bne
la
move
subu
sw
move
lw
la
syscall
$s7,$s6
#
$t8,$s7
$s7,1($t8)
$t8,($t8)
$t8,$0,_tsteos
$t8,0($23)
#
$t7,$22
$t8,$t8,$t7
$t8,-16+56($sp)
$a0,$22
#
$a1,-16+56($sp)
$v0,8
cp=pc; while(*cp++) continue;
$t7,-12+56($sp)
$f0,($t7)
#
_brkscn
#
#
pd=(double *) va_arg(ap, double*);
*pd=syscall(7);
retorna el doble en $f0
break;
largo=(cp) - pc;
syscall(pc,largo,8);
break;
_brkscn:
#
}
_cntscn:la
_tstscn:lb
bne
$30,1($30)
#
$24,($30)
$24,$0,_blkscn
for(p = format; *p ; p++) {
sw
$0,-4+56($sp) #
va_end(ap);
lw
lw
lw
lw
$s5,16($sp)
$s6,20($sp)
$s7,24($sp)
$s8,28($sp)
#}
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
lw
addu
j
.end scanf
65
$ra,32($sp)
$sp,$sp,56
$ra
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 */
Profesor Leopoldo Silva Bijit
20-01-2010
66
Estructuras de Datos y Algoritmos
/* 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];
}
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
67
#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; }
Profesor Leopoldo Silva Bijit
20-01-2010
68
Estructuras de Datos y Algoritmos
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;
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
69
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;
Profesor Leopoldo Silva Bijit
20-01-2010
70
Estructuras de Datos y Algoritmos
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;
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
71
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(' ');
}
Profesor Leopoldo Silva Bijit
20-01-2010
72
Estructuras de Datos y Algoritmos
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) {
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
73
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);
Profesor Leopoldo Silva Bijit
20-01-2010
74
Estructuras de Datos y Algoritmos
}
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
75
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);
Profesor Leopoldo Silva Bijit
20-01-2010
76
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
77
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]);
Profesor Leopoldo Silva Bijit
20-01-2010
78
Estructuras de Datos y Algoritmos
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;
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
79
}
#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.
Profesor Leopoldo Silva Bijit
20-01-2010
80
Estructuras de Datos y Algoritmos
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^544.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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
81
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:
Profesor Leopoldo Silva Bijit
20-01-2010
82
x = m * (2n)
Estructuras de Datos y Algoritmos
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 2ee
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).
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
83
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.
Profesor Leopoldo Silva Bijit
20-01-2010
84
Estructuras de Datos y Algoritmos
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
85
Con
0< ei < 2047 se logran en forma externa:
representaciones para números normales, según:
N = (-1)S*1.M2*pow(2, ee)
-1023 < ee < 1024, se tienen
Con ei = 2047 y M2 == 0 (ee = 1024) se tiene la representación para el
N = (-1)S*INF
Con ei = 2047 y M2 != 0 se tienen la representación para el
N = NaN
según:
según:
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
Profesor Leopoldo Silva Bijit
20-01-2010
86
Estructuras de Datos y Algoritmos
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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
87
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");
Profesor Leopoldo Silva Bijit
20-01-2010
88
Estructuras de Datos y Algoritmos
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
89
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
unsigned int
unsigned int
} pc1;
unsigned char b[4];
struct campos2
{ unsigned int
unsigned int
unsigned int
} pc2;
} pbs;
double d;
} buffer;
signo1 :1;
exp1 :11;
man1 :4;
man2 :4;
exp2 :11;
signo2 :1; /*el byte más significativo con dirección mayor*/
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;
Profesor Leopoldo Silva Bijit
20-01-2010
90
Estructuras de Datos y Algoritmos
} 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.
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
91
Í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
Profesor Leopoldo Silva Bijit
20-01-2010
92
Estructuras de Datos y Algoritmos
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
Profesor Leopoldo Silva Bijit
20-01-2010
Apéndice 2. Introducción al lenguaje C.
93
Í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
Profesor Leopoldo Silva Bijit
20-01-2010
Descargar