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