Autor Tema: Proyecto de Curso (Dictado - Notas): Programación en C (2013, por Argentinator)

0 Usuarios y 1 Visitante están viendo este tema.

20 Febrero, 2013, 06:14 pm
Respuesta #30

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
30. ¿Cómo lograr que todo funcione? (Parte I)

Este post tiene un contenido netamente espiritual.  ;D

(Lectura opcional)
Primero que nada, ¿para qué diablos aprendemos un lenguaje de programación?
Obviamente, es que nos gusta resolver cositas con la computadora, y disfrutamos ver que una máquina hace automáticamente algo que a nosotros nos llevaría mucho esfuerzo, sudor y lágrimas.

En el fondo, las computadoras son máquinas que nos alivian una buena parte del trabajo intelectual.
No sólo eso, sino que lo hacen con rapidez, a una velocidad de cálculo que los seres humanos no podemos alcanzar.
Esto se vuelve importante porque no sólo ahorramos esfuerzo, sino también tiempo.

En cualquier caso, se ahorra dinero.
Pensemos que el esfuerzo físico o mental nos obliga a ingerir nutrientes, que nos insumen un cierto costo.
Si además podemos producir más resultados en menos tiempo, esto se traduce en un aumento en las ganancias que provienen de nuestro trabajo.

Una ventaja adicional que se gana con las máquinas es que ellas pueden, además, intercambiar información entre sí, de un modo eficiente, y también de bajo costo. Y también es cómodo.
No podemos pasarle a un amigo una fotografía por teléfono, sólo podemos describírsela, y más o menos.
Un conjunto complejo de datos tampoco es fácil de pasar por teléfono o telégrafo.
Hay millones de transacciones y actos burocráticos que tienen la oportunidad, hoy en día, de automatizarse y terminarse rápidamente gracias a las computadoras.

Luego de toda esta propaganda pro-computadoras, es hora de que hagamos algo de publicidad en contra.
Las computadoras no son mágicas. Son máquinas pasivas que requieren que un ser con voluntad le dé órdenes para realizar una tarea. También insumen un costo energético: electricidad (aunque el consumo es bajo comparado con otros electrodomésticos).

Para lograr que la máquina haga algo, tenemos que aceptar el diseño dado por el fabricante, entender cómo trabaja la máquina internamente, y "hacerle tragar" por algún lado una lista de instrucciones a realizar, a fin de que dé el resultado de un cálculo o una búsqueda de información, o imprima un gráfico, etc.

En la prehistoria, esto se hacía con "tarjetas perforadas", que eran unos cartones rectangulares en los que se practicaban agujeros, donde se ponían instrucciones de máquina en código binario. Había un dispositivo de lectura de tarjetas que le pasaba los datos a la máquina, la máquina hacía sus pases mágicos, y después el resultado se obtenía, quién sabe dónde (lamparitas iluminadas, otra tarjeta, datos impresos, una cinta de audio  ??? ).

A medida que las cosas fueron evolucionando, se logró que una persona tenga en su escritorio un dispositivo con una pantalla cómoda de visualizar, un teclado fácil de usar al estilo de una máquina de escribir, y toda una circuitería miniaturizada, de última tecnología, escondida dentro de una carcaza de la que nadie tiene que preocuparse.

Lo único que hay que hacer ahora es escribir un programa en un lenguaje (elijan el C), traducirlo a lenguaje de máquina con un compilador (como el GCC), generando un programa ejecutable (un archivo de extensión .EXE bajo Windows), que hace lo que queremos que haga: resolver un problemita computacional de algún tipo.

Ahora, para lograr que la máquina haga algo, tenemos que "estudiar".  :'(
Estudiamos las reglas de un lenguaje de programación (el C), para ver cómo se escribe un programa (de alto nivel),
y de paso nos damos cuenta de las herramientas que ese programa nos provee (tipos de datos, manejo de archivos, acceso a memoria, control de flujo de la ejecución, soporte multihilo, librerías gráficas, etc.).
En la medida que ellenguaje nos ofrece más herramientas de control sobre nuestra máquina, más y mejores cosas podemos hacer.

Entonces para lograr que nuestros programas hagan lo que deseamos, tenemos que estudiar bien el lenguaje y sus librerías.
Si no existe una librería que haga lo que necesitamos, no queda más remedio que "inventarla".
Somos nosotros los que tendremos que diseñar una librería que resuelva una serie de tareas comunes.

Pero eso no siempre es necesario.
Es muy posible que si queremos desarrollar tareas "lindas y divertidas", alguien más ya ha diseñado librerías para resolver ese tipo de cosas.
Así que hay que averiguar quién hizo esas librerías.

Uno podría buscar un catálogo de programadores de todo el mundo, e ir preguntando de a uno quién de ellos hizo una librería que, por ejemplo, permita hacer figuras geométricas en la pantalla.
Más ágil sería la búsqueda si a la gente del mundillo de la programación le preguntamos si alguno de ellos "conoce a alguien" que haya hecho o tenga a mano la librería que buscamos.
Si hallamos una persona que tenga esa librería, puede que nos regale una copia, o puede que nos la venda.
A veces quien la vende es el mismo autor (y está en su derecho hacerlo), o alguien autorizado por el autor, pero a veces no, y quien nos la vende es un tipo que no tiene relación comercial con el autor. Eso viola los derechos de propiedad intelectual.

También viola los derechos de autor el conseguir una copia gratuita, de una librería que no tiene autorización de uso comercial, y luego utilizarla para generar un programa con fines comerciales.

O sea que, a pesar de que exista una librería que nos soluciona el problema, resulta que nuestro problema no se puede solucionar, porque la librería no puede usarse, por cuestiones legales.

Estos impedimentos no son ni de hardware ni de software, obviamente.
Ya se trata de cuestiones de tipo "social", y así vemos como el factor "social" juega un papel, en este caso negativo o represor, en el desarrollo de una aplicación para la computadora.

Por otra parte, si conseguirmos una copia pirata de una librería, y la usamos en forma privada sólo con fines de aprendizaje o experimentación, entonces es muy probable que no tengamos problemas de tipo legal.
Así, en ese caso, estaríamos resolviendo nuestro problema computacional, aunque no podríamos hacer público nuestro programa así realizado.  :(
Es como resolver la conjetura de Fermat, y no poder decírselo a nadie.
Sirve para decir que la solución existe, pero no se puede compartir dicha solución.

___________________________

Todo esto nos hace plantear de nuevo la dimensión "social" de la informática.
¿Para quién realizamos el programa?
Por lo general, si sólo lo realizamos para nosotros mismos, tenemos más libertades, porque en nuestra computadora mandamos nosotros, y podemos hacer y deshacer a nuestro antojo.

Cuando uno hace un programa para "alguien más", ya interesa el factor "social" del asunto, y entonces hay que "lidiar con las estupideces del género humano", como por ejemplo, las leyes de derecho de autor.

Así que no es fácil decir si un problema informático está o no resuelto, según cuál haya sido la motivación social que generó el problema.

Ahora bien. Con las mismas restricciones sociales que antes, podemos entrar en ellas al menos en forma más eficiente.
No vamos a preguntarle al amigo del amigo de un amigo, a ver si nos facilita una librería que nos falta.

Mejor, ya que estamos en la era de Internet, la buscamos en la red.
Sin embargo, esta opción no reemplaza a la opción de preguntarle al amigo del amigo de un amigo.
Son posibilidades complementarias.

Así, ponemos la dirección de un buscador (Google) en nuestro navegador (Firefox), y allí ponemos "librería para hacer figuras geométricas en C", clic en "Buscar".

Nos saldrá una lista enorme de enlaces con ese tema.
Lo que hay que hacer luego es entrar en varios de esos enlaces, y ver si realmente tienen lo que buscamos.
Si no, habrá que volver a la página de búsqueda, y probar con otro enlace, y así hasta hallar lo que queremos.

Ahora vemos que, si bien no tenemos que perder precioso tiempo diseñando nosotros mismos programando una "librería para hacer figuras geométricas en C",
no obstante igual perdemos tiempo realizando una búsqueda, que nos puede llevar horas si lo que buscamos está medio "escondido" en Internet.

incluso si encontramos una librería con los requisitos pedidos, tenemos que renegar con la página de descarga, que a veces tiene virus, troyanos, o bien opciones de descarga inentendibles o confusas, esquivar la publicidad, y prestar atención a que hay muchos botones que dicen "Descargar", bien grandotes, ¡pero que no descargan el software que queremos! No, esos son botones engañosos que esconden publicidad o directamente virus.

Hasta que por fin damos con el "verdadero" botón de descarga, que probablemente sea "pequeño" y "escondido" quién sabe en qué parte de la página, y la descarga comienza.

Tras descargar la librería, tenemos que investigar un poco cómo se instala, y cómo se hace para que encaje con nuestras librerías (del lenguaje C) que tenemos previamente instaladas.
Esto puede dar un terrible dolor de cabeza porque, digamos la verdad, nunca se entiende qué diablos hay que hacer ni para qué.  :banghead:

Hasta que finalmente uno termina adivinando, si tiene suerte, cómo se hace para que la descarga encaje con nuestro sistema en forma armoniosa.  :aplauso:

Luego hay que aprender a usar la librería, y también hay que hacer pruebas, hasta que finalmente todo funcione más o menos bien.
Pero puede ocurrir que los resultados obtenidos con esa librería que tanto nos costó bajar, nos sean decepcionantes.

Entonces tenemos que desinstalarla, con mucho cuidado para no hacer daño a la configuración del sistema que teníamos antes.
Si hacemos esto mal, que es lo más probable  ;) , no habrá más remedio que desinstalar todo, y volver a instalar todo desde cero.  :banghead:

Todo esto es una pérdida de tiempo, obviamente.
Pero a veces también es inevitable.

Y luego se repite el ciclo de búsqueda de una "librería para hacer figuras geométricas en C", y así probamos, instalamos y desinstalamos cosas extrañas, hasta que hallamos algo que nos conforme.

Puede pasar que no encontremos ninguna librería que nos satisfaga del todo, o que nos guste pero que ande mal, o que ande bien, pero que no sabemos configurarla, etc.

Todas estas molestias son el costado negativo del desarrollo informático de estos tiempos, y a los que a nadie parece importarle.  >:(

Algunas razones para no estar satisfechos con una librería pueden ser que necesiten características que consideremos indeseables.
Por ejemplo, hay programas o librerías que tienen una licencia de "prueba", o sea que son gratuitas por un tiempo, y luego hay que pagar para usarlas.
Si no tenemos intención de gastar dinero en estas cosas, es una pérdida de tiempo descargarlas para experimentar con ellas.

Puede darse el caso de programas o librerías que sean específicos de una plataforma, por ejemplo Windows.
Esto puede importarnos o no, pero si esto supone además que tenemos que aprendernos toda una filosofía de trabajo "bajo Windows" (las famosas API), eso ya es otra historia. Debemos tomar la decisión de invertir o no tiempo en eso.

Significaría aprender algo nuevo, para un solo sistema operativo, y es algo que cuesta tiempo.

Si la librería que nos descargamos es gratuita, hermosa, satisface nuestros deseos, es genérica (sirve para casi cualquier sistema operativo: Windows, Linux, etc.), y es más o menos fácil de aprender, estaríamos dispuestos a descargarla y utilizarla.

Aquí surgen otros inconvenientes.
Puede que la librería esté hecha con tanto amor, que se volvió demasiado completa y sofisticada.
Entonces nos será costoso aprenderla, e innecesario, ya que si sólo queremos hacer cosas sencillas, no sirve de nada aprender toda una filosofía de trabajo asociada a esa librería.

Además, puede ser toda una odisea encontrar manuales adecuados o claros, o completos, de una librería, incluso de una famosa.

_________________________________________

Otro obstáculo es el idioma.
Puede que en castellano encontremos muy poco material asociado a nuestra búsqueda.
Lo recomendable es poner frases en inglés en el buscador (Google), tales como "geometrical figures library in C".
A veces una tal búsqueda puede ser infructuosa si nuestra traducción no es afortunada.
Cambiando la palabra "figures" por "shapes", puede que tengamos muchas más posibilidades de éxito.

A continuación, habrá que renegar con las instrucciones en inglés, un idioma que cada vez entiendo menos, porque me gusta más el idioma que me enseñó mi mamá.

Por razones absolutamente misteriosas de "frikilandia", las librerías que uno necesita bajar no son tan simples de instalar. No basta con apretar un botón que diga "instalar" y listo.
A veces hay "muchos paquetitos" repartidos por ahí, distintos, independientes, pero todos necesarios y relacionados entre sí.
Es muy posible que uno no entienda para qué diablos sirve cada bendito paquetito, ni si le va a hacer falta alguna vez en la vida o no.
Las instrucciones de instalación no son naturales, intuitivas, sino que hay que navegar por la página web, leyendo y releyendo instrucciones, para ver cómo se instala algo, y que, tras instalarlo, lo más probable es que esté todo mal, no funcione nada, y hay que formatear el disco rígido debido al desastre que hicimos instalando y probando cosas nuevas.  :banghead:

Es por esa razón que yo, en este curso de C, hago lo posible para que no haya que instalar prácticamente nada, salvo al principio del curso, y nada más. Y en lo posible también, no cambiar las opciones originales de configuración. Porque cambiar algo en un sistema que usamos para compilar programas en C, es siempre candidato al desastre.

La consecuencia de todo esto, no es ya que nuestros programas no compilan, no funcionan, o que no podemos hacer exactamente lo que deseamos, sino que ni siquiera nos funciona el entorno de programación, y a veces el descalabro llega hasta las entrañas del sistema operativo (según qué cosa hayamos instalado, o bien si se nos metieros virus, gusanos, troyanos, etc.).

El resultado es siempre el mismo: NO FUNCIONA.

Y el objetivo es que las cosas: FUNCIONEN. Todo tiene que funcionar, y bien.

En la prehistoria la dificultad era lo primitivo de la tecnología de las computadoras,
luego la dificultad estaba en nuestros conocimientos adquiridos de programación,
después estaban en la disponibilidad o no de ciertas librerías o programas,
y ahora la dificultad está en lo intrincado que resulta instalar armoniosamente un componente a nuestro entorno de programación.

En cualquier caso, se invierte tiempo y esfuerzo.
LAS DIFICULTADES NO DESAPARECEN, SÓLO SE TRANSFORMAN.

________________________________

Sin embargo, no todo está perdido, y es ahora cuando recuperamos el factor "social" de la informática.
Si vemos que nosotros solos no podemos con un problema, para eso está el resto del mundo para que nos ayude.

Aunque la estructura del mundo es capitalista y mercantilista, es muy común encontrar personas que ofrecen ayuda gratuitamente, o que regalan soluciones.
Esto se puede hallar en Organizaciones Sin Fines de Lucro (ONGs), Institutos de Enseñanza (gratuitos), países donde el ingreso a la Universidad es gratuito, y comunidades virtuales en Internet.

En el caso de Internet, existieron en un principio las "listas de correo", que aún hoy están muy activas, pero que casi nadie sabe que existen. Son famosas en el mundillo de los informáticos, o de alguna especialidad técnica en particular.
En las listas de correo la gente escribe usando un conjunto reducido de caracteres: ASCII, y hasta es posible que ni siquiera se permitan subrayados ni resaltados en negrita o cursiva, como tanto me gusta usar a mí.
Esto es lógico ya que los sistemas que gestionan correos electrónicos puede que den resultados anómalos o desagradables, si es que hay algún error o incompatibilidad. Para evitar toda clase de posibles problemas, directamente te prohíben usar características especiales (HTML) en el correo, y sólo te dejan enviar texto plano (sin formato).

A veces puede pasar que enviaste un correo sin formato, pero te lo rebotan igual.
¿Por qué ocurre esto? Ocurre porque en realidad los servicios de email modernos ponen de todos modos un "formato básico". No basta conque nosotros no usemos ninguna característica HTML, sino que hay elegir específicamente una opción de correo que diga algo como "texto plano". De esa manera, se envía sólo texto (como un archivo TXT).

En eso hay un contratiempo, porque la tecnología de la información avanza para hacernos la vida más agradable, nos dicen que el formato HTML es maravilloso y podemos enviar correos electrónicos bellamente formateados, con fotos y videos, etc., pero resulta que hay gente que no nos deja utilizarlos: dicen que no, que hay que volver a la prehistoria.  ???

Entonces tenemos que "sintonizar" con esa mentalidad extraña, y tratar de expresarnos en forma escueta y clara, usando sólo texto plano, incluso en el caso que necesitamos explicar un asunto complejo.

Existen listas de correo que discuten temas de matemática, por ejemplo, escribiendo exclusivamente en LaTeX, tal como hacemos nosotros, pero con la diferencia de que ellos nunca pueden ver la formulita en forma agradable como hacemos en este foro.

Y discuten temas complejos de matemática así. Es bastante feo, por no decir otras cosas. Pero es algo que existe.

La necesidad de un formato que sea "universal", que no esté sujeto a interpretaciones erróneas de los navegadores, o que sea compatible con "todos" los navegadores y sistemas operativos del mundo, lleva a que la gente tome decisiones extremas como el de comunicarse con correos en "texto plano".
Me cuesta entender que al día de la fecha haya gente que no tenga un navegador de última generación.
Después de todo, son gratuitos.

En realidad, los correos en texto plano tienen la ventaja de que no saturan los servidores de ciertas páginas web que reciben mucha correspondencia, porque casi no ocupan memoria, como sí lo harían en caso de que aceptaran elementos multimedia avanzados.

__________________________

Una evolución a las listas de correo lo son, por supuesto, los foros de internet, como éste en el que estamos.
La manera de debatir temas en un foro es más cómoda y más organizada.
Si además el foro admite capacidades gráficas o multimedia de diversa índole, la comunicación de ideas e información se hace más agradable, fluida y natural.

Si no nos animamos a meternos en el mundillo de las listas de correo, podemos hacer el intento en los foros de internet.
Aquí podemos tener distintos tipos de experiencias.
Habrá foros donde se contestan las preguntas de un modo amable, pero escueto y seco.
Hay otros que contestan puras estupideces e ironías, y otros donde se responde con información incompleta, y encima con soberbia, como si el culpable fuera uno.

Si yo pregunto por qué no puedo factorizar el polinomio \( x^2+1 \), puede que me expliquen con cordura diciéndome que "eso depende del dominio en que considere definido el polinomio, siendo no factorizable en los reales, pero que sí lo es en los complejos".
Cuando me contestan así, me ponen un poco a prueba para ver qué es realmente lo que yo quiero hacer, o cuál es el grado de profundidad que quiero darle a la pregunta.

Entonces ya no es lo mismo que poner una consulta en Google y esperar la respuesta.
Ahora no queda más remedio que interactuar con las personas que están del otro lado de la pantalla, iniciar una conversación con seres humanos de verdad, y entonces volver a preguntar, hasta que la duda queda resuelta completamente.

Puede haber foros o personas que contesten así: "Acá, el problema es que vos nunca estudiaste números complejos, y por eso no sabés cómo se hace esto".
Con tonos parecidos se puede encontrar uno en algunos foros, sobretodo si son de una temática más general o más mundana.

Una respuesta así puede desanimarnos a usar los foros de internet.
Incluso el maltrato o las discusiones con otras personas son algo que es probable que ocurra alguna vez.
Los malentendidos están a la orden del día, por diversas razones, y la gente y sus defectos también.

El costado social del asunto tiene este aspecto negativo.
Entonces, lo que era un original problema de computación, se convierte ahora en un drama psicosocial, que nos hace perder tiempo, discutiendo con personas algo idiotas por un lado, o tratando de entender a personas demasiado técnicas por el otro lado. Esta interacción también lleva tiempo.

Uno tiene que tener paciencia, y seguir en la búsqueda de foros y personas que sean amables.
No tiene sentido desilusionarse o deprimirse por las malas experiencias con la gente en Internet.
Así como en el mundo hay unas 7000 millones de personas, constituyendo una "fauna" de lo más prolífica y exótica, lo mismo pasa con Internet. El mundo entero está ahí adentro, y hay mucha gente desagradable con la que tendremos que lidiar.

La ventaja de Internet es que la pantalla de la computadora nos protege.
Si tenemos que lidiar con un idiota, podemos insultarlo a nuestro antojo, o bien simplemente podemos elegir ignorarlo.

En general ignorar a la gente problemática o que piensa distinto que nosotros, en Internet, es la alternativa más "eficiente".
No puede uno perder demasiado tiempo en discusiones personales o filosóficas, porque tenemos que recordar que tenemos un problema que resolver, y que todo consume tiempo.

O sea que no sólo tenemos que saber "navegar" en la dimensión "social" de Internet, sino que también tenemos que resistir tentaciones propias de nuestra propia psicología.

Entre esas tentaciones están los videos graciosos de Youtube, las películas gratuitas de SeriesYonkis, las noticias escandalosas de la farándula y el deporte, y así por el estilo.

El entretenimiento es una pérdida de tiempo cuando estamos tratando de que nuestro programa "compile" y dé una solución efectiva a un problema.
Sin embargo, los seres humanos no somos máquinas, y necesitamos unas pequeñas dosis de entretenimiento.

Si no, nos ofuscamos y empezamos a hacer todo mal, y pasaremos horas trabados en algún paso estúpido, en algo que no vemos por mero cansancio.
O sea que ahora hay que recobrar la dimensión del entretimiento, darle su justo valor, y dedicarse un poco a los deportes, los juegos, la farándula, etc. Y esto como parte de todo el proceso de ahorrar tiempo, aunque parezca que no.

Es preferible tener 8 horas de trabajo intercaladas con 2 horas de entretenimiento, que no 12 horas seguidas de trabajo, porque se obtendrán mejores resultados de la 1era forma, y más rápido, a pesar de que los números aparenten lo contrario.

O sea que ahora, para que las cosas FUNCIONEN estamos considerando los factores "biológicos" y "psicológicos" de nosotros mismos.

____________________________________

Si ni en los foros de internet ni en las listas de correo hallamos soluciones, son posibles otros caminos.

Podemos formar grupos de intereses comunes en las redes sociales de Internet, como Facebook o Google+.
Se pueden realizar preguntas en páginas como Yahoo respuestas, aunque es muy probable que allí la gente conteste estupideces. Aún así se puede probar suerte.

Páginas como Facebook o Yahoo respuestas no ofrecen una plataforma adecuada para comunicar ideas técnicas complejas. No se pueden escribir fórmulas en LaTeX, no se puede formatear texto (que yo sepa).
En cambio en Facebook al menos se pueden incrustar imágenes y videos muy fácilmente.

Todo tiene sus pros y sus contras.

La creación o ingreso a una comunidad virtual en Facebook requiere otra predisposición que para un foro.
Es común que en Facebook la gente nos haga perder tiempo con impresiones personales, yéndose por las ramas fuera de tema, haciendo chistes, y demás "cositas del face".  :D

Pero si tenemos suerte de dar con gente bien enfocada, es posible generar una experiencia de intercambio interesante.
Allí puede que haya expertos que contesten preguntas (los Administradores del grupo), pero es muy posible también que eso no sea lo que prevalezca, y que tengamos que mentalizarnos en una "ayuda recíproca" como modo de acción, es decir, compartiendo información entre todos.

Una herramienta muy poderosa de comunicación es el chat, aunque casi siempre sólo se usa para conseguir parejas esporádicas por Internet.
Antiguos servicios de chat como el ÏRC, eran (y son todavía) algo así como 'listas de correo' en tiempo real, en que la gente puede intercambiar opiniones de temas técnicos, como por ejemplo, el diseño de una librería en C que permita hacer figuras geométricas.

Los sistemas de chat más modernos como MSN Messenger o el de Yahoo, disponen de opciones para introducir "emoticones", "fotografías" y "videos".
Aunque esta manera de comunicarse instantánea es algo muy bueno, la fama de Facebook ha diluido un poco el uso del chat.
No obstante, es claro que siempre se seguirán utilizando, y ahora MSN evolucionará a Skype definitivamente, que es chat con video.
Esta es una comunicación directa ideal, el poder hablar y ver a la persona con la que uno conversa, es superior y más barato que el uso de llamadas telefónicas a larga distancia.

Por más moderno que esto sea, a veces puede que nos sintamos tímidos de comunicarnos así con desconocidos, y prefiramos conversar usando meros "caracteres", a la antigua.
Hay, pues diversas opciones, pero lo que quiero remarcar con esto es que pueden haber limitaciones psicológicas en donde la tecnología no muestra impedimento alguno.
Y todo es parte de la misma realidad.

Lo importante de sistemas como Skype es que, si superamos la timidez, podemos conversar en directo con varias personas. Son las "llamadas en conferencia". El ahorro de dinero en esto es muy grande.
También se ahorra tiempo, porque es más fácil explicar lo que queremos si usamos el tono de voz y los gestos adecuados. La conversación con voz y audio es siempre más eficiente.

Entonces puede que tengamos suerte de que consigamos más rápidamente lo que queremos: la dichosa librería que hace figuras goemétricas en C.

En toda conversación entran los egos y temperamentos en juego, y esto producirá retrasos, ya que hay gente que querrá ser el centro de atención, o querrá hablar primero, etc.

En cualquier caso, esto se aleja de nuestro objetivo de realizar un simple programita en C, al punto que dan ganas de ponerse a llorar.  :'( :'(
¿Todo esto para dibujar un triangulito en mi computadora?  ::)

____________________________________________

Hay otros obstáculos a sortear, y tienen que ver la documentación y el conocimiento.
A veces no nos alcanza una mera respuesta, sino que necesitamos que "alguien" nos dé "algo".
Para eso hay que encontrar las personas que dan "ese algo", y hallar la manera más adecuada de "pedirlo".
Supongamos que queremos un libro en formato PDF que no se consigue en ninguna parte, pero que el autor eligió regalarlo a quienes se suscriban a su blog, y se lo pidan amablemente.

Entonces hay que registrar una cuenta en el servidor que aloja el blog,
después afiliarse al blog del tipo, esperar a que te acepta la afiliación,
finalmente dirigirle un mensaje privado, o postear algo en una de sus entradas, haciendo el pedido del bendito PDF.
Después esperar a que el tipo tenga ganas de responder, etc.

En otras ocasiones hay gente que sube información a Wikipedia, en forma voluntaria, pero que se demora un cierto tiempo.
Uno puede hacer el intento de entrar a la zona de debate de Wikipedia, y hacer una pregunta ahí, a ver si alguien responde, y de paso lo agregan a la entrada de Wikipedia del tema que nos interesa.

Les dejo como ejercicio pensar otras situaciones.
__________________

Pero hay una dimensión más de la interacción con gente en Internet.

Supongamos que tenemos todo un entorno de programación perfectamente instalado en nuestra computadora, pero que detectamos que no funciona como esperamos, o nos da resultados "erróneos".

¿Qué hacemos?

Lo primero es revisar nuestro programa, a ver si no hemos metido la pata.
Una vez corregidos los posibles errores de programación, hay que estudiar las especificaciones técnicas del lenguaje, que por lo general estarán escritas en un documento, que contiene "el estándar".

Si este estándar no existe, hay que buscar un sustituto, averiguar por Internet a ver qué es lo más parecido a un "estándar" que se acepta para un cierto lenguaje, o una librería externa asociada a él.

Si usamos una versión específica de un lenguaje, diseñada por un grupo empresarial específico, tendremos que conseguir un manual, tutorial o información específica de esa versión.

Si las especificaciones técnicas del estándar no nos aclaran la fuente del error, y parece estar todo correcto, entonces hay que fijarse en la documentación de nuestro compilador particular (GCC), y ver qué opciones de compilación existen. Puede que haya alguna que nos dé el resultado que deseamos, respecto al comportamiento general esperado para nuestro programa ejecutable.

Lo que hacemos en este curso es configurar el compilador para que funcione según la normativa estándar C99, por ejemplo, y que además descarte aquellos aspectos de GCC que no son estrictamente soportados por el estándar.

Si todavía las cosas no funcionan bien, quiere decir que, muy posiblemente, exista un error de diseño en el compilador.

En este caso, podríamos cruzarnos de brazos y decir "esto no tiene más remedio".  
Si el compilador funciona mal, pareciera que nada podemos hacer. :'(

Una opción improbable sería ponernos nosotros mismos a reprogramar el compilador, o hacer otro nuevo directamente, y que funcione bien... Pero descartemos este enfoque impráctico.
Otra opción es reclamarle a los diseñadores del compilador que arreglen el error.

Aquí de nuevo tenemos que interactuar con gente.
Si los diseñadores son competentes, tendremos suerte y escucharán nuestro pedido.
Si no, puede que obtengamos respuestas soberbias y equivocadas.

En el caso del compilador GCC las personas involucradas en su desarrollo son bastante amables, aunque están enfocados estrictamente a su tarea, y no conversan demasiado.
Uno va a la página de desarrollo de GCC, reporta un error, y ellos verifican si de verdad es culpa de ellos o no.
Si no, rechazan el pedido, y te dicen básicamente que "te las arregles por tu cuenta" con tu error.

Esto puede ocurrir cuando tenemos un problema en nuestra computadora (el hardware anda mal), o en nuestro sistema operativo, o bien el problema está relacionado con el compilador, pero no es culpa de GCC.

Recordemos que tenemos más o menos unas 3 capas de herramientas de compilación:

* El compilador (GCC).
* La biblioteca o librerías (MinGW).
* Un IDE o entorno integrado de desarrollo (wxDev-C++).

Estas 3 cosas son bien distintas, las desarrollan grupos de personas diferentes, incluso de páginas webs separadas, y además se pretende que las 3 funcionen juntas, armoniosamente.

Si todo eso funciona en nuestra computadora sin problemas, es casi como un milagro.
Pero cuando algo no funciona, se nos complica la vida, pues tenemos que discernir a quién echarle la culpa, y a quién reclamar.

Una vez que descartamos que los idiotas no somos nosotros mismos, ni Bill Gates,
entonces podemos intentar ver si el error está en otra parte.

Los diseñadores del compilador se ocupan sólo de aspectos sintácticos, traducción del código fuente a ejecutable, y de los mensajes de error o advertencias que arroja el compilador.
Consultas o reclamos de eso, se hacen a la gente de GCC.

Cuando lo que no funciona tiene que ver funciones o datos definidos en las librerías, u otras cuestiones del manejo de los archivos proveídos por la implementación, esto es culpa del equipo de MinGW.

Cuando en línea de comandos el compilador funciona correctamente, pero en el IDE tenemos problemas, las cosas no funcionan, se nos cierran ventanas sin explicación, las opciones de configuración que elegimos no tienen efecto, etc., esto empieza a ser culpa de la gente de wxDev-C++.

Y la única manera de que nuestro programa compile correctamente es que estos tres grupos de personas se dediquen, cada uno de ellos, a arreglar lo que ellos mismos hicieron mal.  :-\

No podemos esperar que mágicamente lo arreglen, porque a pesar de que hay mucha gente en el mundo, que se encuentra con problemas similares que los nuestros, es muy probable que seamos nosotros los primeros en tener un determinado problema con el compilador, las librerías o el IDE.
No habrá más remedio que perder la timidez, registrarse en las páginas webs de esos extraños seres de la informática, prestar atención al modo en que se deben escribir los formularios de informe de errores (llamados bugs), y decir lo que tenemos que decir: "tal o cual cosa NO FUNCIONA".

En estos casos el modo adecuado de hacerlo es en idioma inglés, y además hay que ser lo más conciso y claro posible. Si vamos a reportar un error que obtuvimos en un programa, el ejemplo que pongamos tiene que ser bien sencillo, de ser posible, para que sea bien clara la fuente del error.

Si nos ponemos muy verborrágicos, lo más probable es que no se entienda, y luego les saltará a algunos el "gusanito del orgullo informático", e intentarán hacernos creer que la culpa es nuestra, con una condescendencia un poco disimulada.

En estos casos hay que buscar la manera de insistir, pero al mismo tiempo sin molestar demasiado, porque se enojan enseguida con la gente que se vuelve repetitiva e insistente.
Hay que tener un tacto especial para opinar en estos sitios webs, sobretodo recordando el contexto: la mayoría de los que están allí son expertos, y no les sobra al tiempo. Hay que ir al grano, sobretodo porque los problemas de diseño con los que ellos tienen que lidiar son muy complejos.

Si nosotros vemos que nuestro compilador nos arroja un mensaje equivocado y de apariencia tonta, puede que para los ingenieros que deban arreglarlo sea un dolor de cabeza.
El motivo es que un compilador genérico como GCC, está orientado a funcionar en cientos de máquinas de arquitectura diferente, y también en la mayoría de los sistemas operativos conocidos.
Arreglar un "bug" implica arreglarlo para todas esas configuraciones posibles.
Requiere un diseño cuidadoso.

Además, es un trabajo comunitario, con lo cual varias personas contribuyen a un proyecto global.

[cerrar]



Organización
Comentarios y Consultas

20 Febrero, 2013, 06:16 pm
Respuesta #31

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
31. ¿Cómo lograr que todo funcione? (Parte II)

Voy a terminar la exposición anterior contándoles una breve experiencia personal.

(Lectura opcional)

Como ustedes sabrán, estuve luchando varias semanas con los números de punto flotante, a fin de poder explicarlos correcta y completamente en este curso.

(1) Lo primero que tuve que hacer fue estudiar todos los detalles de los datos en "punto flotante" que aparecen en el documento del estándar C99.

(2) Además, tuve que tener en cuenta la enorme experiencia que Derek Jones demuestra en su texto "The New C Standard", accesible en el primer post de este curso.

(3) Tras conjugar esos dos factores, advertí que tenía que estudiar otro estándar aparte: el ISO/IEC/IEEE 60559, relativo a la representación de números de punto flotante.
Hay allí varias cosas que surgen de la experiencia de los ingenieros e informáticos a lo largo de las décadas, de las cuales yo no estoy del todo enterado.

(4) Tuve que elegir qué cosas explicar, y qué cosas no, con sumo cuidado, para mantenerme en un esquema concreto, y todavía entendible.

(5) Entre las cosas que tuve que descartar está un documento importante, un artículo que contiene la teoría de "todo lo que un informático debe saber sobre los números de punto flotante", que contiene resultados matemáticos de mucho interés. Este tema lo estudiaremos más adelante.

(6) A fin de hacer experimentos con los bits de un dato de punto flotante, ingresé a un par de páginas webs, que permitían jugar con las representaciones binarias de dichos números.

(7) Tuve que tener en cuenta las especificaciones de GCC en lo concerniente a si adhieren o no a la normativa IEEE para los números de punto flotante. También, ver con cuidado qué es lo que supuestamente hace el compilador con esta cuestión, y qué es lo que aún no está resuelto o está incompleto.

(8) Al final pude diseñar finalmente los programas de testeo, y obtuve algunos comportamientos que no eran los que yo esperaba, según lo que interpreto de las normativas del estándar.

(9) Habiéndome asegurado de que yo no cometí errores de programación, y tras vacilar un poco, hice el intento de comunicarme con el señor Schmidt, creador de la página http://www.h-schmidt.net/FloatConverter/IEEE754.html, que tiene el programita para jugar online con números punto flotante en binario.  
Si bien este señor pone su email a disposición para que todo el mundo le pueda preguntar cualquier cosa,
y si bien estoy tratando de convencerlos a ustedes de que no sean tímidos y dialoguen con la gente en Intenet, aún así vacilé un poco, porque se trata de una persona que no me conoce, es alguien serio y que se ve que tiene cierto grado de experto en lo suyo.
Si yo le pregunto una estupidez tras otra, no me va a responder nunca más cosa alguna.

No es lo mismo enviar un email, que postear una pregunta en un foro, pues el email es algo más personal.
En ese caso, tomé todas las precauciones: primero me aseguré de que no podía resolver la duda por mí mismo, ni con libros o información en Internet; luego busqué en Google estilos de redacción de correspondencias formales en inglés, hice una breve presentación de mi persona, intenté explicar el problema brevemente pero con claridad, y en un lenguaje simple, pero correcto (siempre en inglés).
Además, me convencí a mí mismo de que la pregunta que le estaba haciendo tenía un nivel técnico que realmente valía la pena hacérsela a un experto, en vez de, por ejemplo, a mi vecina.

Este señor me respondió en forma amable y escueta, y dijo que él no podía ayudarme con eso, porque no sabía sobre el tema.

O sea que la comunicación salió bien, pero no obtuve soluciones.

(10) Intenté luego informar en la lista de correos de GCC, destinada a informar errores del compilador (bugs). En principio no entendía muy bien cómo venía la mano. Hasta que entendí que el método consistía en enviar un correo a una cierta dirección de email.
El correo que envié lo rebotaron un par de veces hasta que logré encontrar la opción de la página de outlook que envía emails sólo "sólo texto plano".
Al poco tiempo tuve una respuesta, pero mi pregunta fue derivada a la sección de consultas sobre GCC, y no fue considerada como el "reporte de un bug del compilador".  :(
Básicamente me culparon a mí de que tenía mal configurado el compilador.  :-\

El señor que me respondió fue muy amable en indicarme todos los detalles de lo que tenía que hacer, y qué es lo que estaba pasando.
Esto me demostró que yo, por ejemplo, no estaba a la altura de los desarrolladores de GCC. Ellos saben muy al detalle las especificaciones del estándar, y tienen mucha experiencia con compiladores y máquinas.
Yo, en cambio, tengo dudas del alcance de la normativa en casos específicos, y entonces no sabía si estaba frente a un error de concepto mío, o ante un bug del compilador.  :-[

En el primer caso, el error es mio, y en el segundo caso, es de ellos.
Pero el caso es que nunca lo hubiera comprendido del todo si no hubiera intentado comunicarme, hacer el experimento de "meterme por ahí" y ver qué pasa.

A pesar de que esa lista de correo está abierta a todo el público,
igual tomé muchos recaudos para tratar de no preguntar estupideces.
En la parte de "ayuda de GCC" quizá no sea necesario ser tan tímido,
pero en cambio en la parte de "reportar bugs" creo que hay que estar más a tono con el nivel técnico de la gente que está ahí.

Me refiero a que es muy posible que si creemos encontrar un "bug" del compilador, en realidad no lo sea,
ya que el problema puede ser sencillamente que no conocemos el lenguaje y/o nuestro compilador a fondo.
Y es en ese sentido que hay que ser precavido, y estudiar el caso con cuidado por cuenta propia, hasta que se vuelva imperioso plantear el tema directamente a los expertos.

(11) Otra cosa que hice fue meterme en un foro de informática, que es parecido a éste, en el sentido de que uno puede preguntar cosas sin temor de quedar como ignorante, porque hay distintos niveles de conocimiento en los usuarios, y siempre hay otros que saben más que uno. Tuve respuesta amable de un informático. Se notaba que él sabía más que yo sobre el funcionamiento interno de las computadoras y otras cuestiones, pero sobre el tema específico que yo estaba discutiendo, no estaba tan informado como yo.
Así que no obtuve una solución satisfactoria por este camino.

(12) Finalmente hice el intento de ingresar al sitio de GCC, para reportar "bugs", pero ahora de un modo más eficaz que el de la lista de correo.
Creé una cuenta en GCC para esto (tuve que hacerlo), y reporté un bug muy específico del compilador, tal que era posible verlo escribiendo una sola línea de código, sin librerías ni nada.

Expuse el caso, y la primer respuesta que obtuve fue la de un tipo al que "todo le funcionaba maravillosamente bien" en todos sus sistemas y computadoras (que se ve que tiene más de una).  >:(
Me dio a entender que yo tenía una versión anticuada del compilador...

Sin perder tiempo, busqué inmediatamente en la página de MinGW una versión actualizada del compilador.
¿Por qué en MinGW y no directamente en GCC?
Porque a los paquetes de GCC no los entiendo.
En cambio MinGW tiene todo integrado: compilador + librerías, y es lo que necesitamos para que todo funcione correctamente.

Descargué la última versión de GCC, la 4.7.2, que viene con la última distribución actualizada de MinGW.

Para eso tuve que invertir un cierto tiempo en aprender qué y cómo tenía que descargar, dónde ponerlo en mi computadora, y luego cómo reconfigurar el IDE de wxDev-C++ para que todo encaje armoniosamente, hacer algunas pruebas, y ver que FUNCIONA. Uufff, menos mal.

Entonces volví a compilar mi programita, y obtuve el mismo mensaje equivocado del compilador.
Yo estaba seguro que iba a obtener ese mismo comportamiento, pero necesitaba opinar en la web de GCC con "las pruebas en la mano" para que me hagan caso.
Además, advertí que el usuario que me respondió, había pasado por alto las opciones de compilación que yo indiqué.
Tras presentar estos datos, y diciendo con más claridad las opciones que yo usé, este usuario se rectificó y comprobó que él tenía el mismo problema.
Otros usuarios intervinieron, y estuvieron de acuerdo que es un bug del compilador.

Una vez que esto se ha consensuado, el "bug" queda en una lista de espera hasta que aparezca algún voluntario en la comunidad para resolverlo.

Ese alguien podría ser yo mismo, pero al día de hoy no me siento capacitado para inmiscuirme en el desarrollo del compilador GCC.

Entonces lo que debo hacer ahora es esperar a que en una próxima versión alguien haya corregido ese "bug", y así el problema no se presente.

(13) Dado que había tenido respuesta favorable a este reporte del "bug", subí la apuesta ;)
y en ese mismo lugar aproveché para relacionarlo con otro problema que yo consideraba de la misma índole u origen.
Pero entonces me retaron y me dijeron que ahí no iba esa pregunta.  :-[
Me sentí desconcertado, porque no sabía en qué sección, o en qué página de internet, o en qué edificio de la ONU, es que tenía que ir a hacer el reclamo o pregunta.  ??? ??? ???

(14) Sólo por persistencia, ingresé en otra sección de GCC, que parecía más acorde a la pregunta que yo había hecho, a ver cómo me iba. Me disculpé de antemano por si la pregunta no iba en ese lugar (normas de etiqueta), y expliqué el problema lo más clara y escuetamente posible. Un usuario muy amablemente me respondió que a la gente de GCC eso no le concierne, y que tenía que dirigirme al grupo de desarrollo de las librerías de MinGW.
En tal caso, por cortesía, me disculpé y le agradecí.

La cortesía y la diplomacia son importantes para no ser expulsados de esos lugares, sólo por un tonto malentendido, o por estar medio desubicado.
Tras estos intentos, medio acertados y medio equivocados, fui aprendiendo a qué lugar tenía que dirigirme para resolver tal o cual cuestión.

En ocasiones, no sólo hay que consolarse con eso de que "del error se aprende", sino más aún "a veces el error es el único camino posible para avanzar".

(15) Ahora ya tuve más claro adónde tenía que ir. Busqué el equipo de trabajo de MinGW, ya que mi reporte era ahora sobre una cuestión de las librerías de punto flotante de GCC.
El proyecto MinGW está alojado en SourceForge.net, y tuve que registrar una cuenta en ese sito web, lo cual es necesario para reportar "bugs".

A continuación hay que fijarse con cuidado cuál es la metodología adecuada para reportar un "bug". Hay que hacerlo de una manera apropiada, sobretodo breve, y con los formularios específicos.
Tras estos cuidados, volví a hacer la misma pregunta que nadie me respondió en GCC, a ver si ahora tenía más suerte.

Se originaron problemas en el mensaje que escribí, los cuales yo no entiendo porque es la primera vez que escribía en esa web.
Un usuario, quizá un administrador, muy amablemente corrigió mi mensaje.
Luego tuve que esperar a que alguien me responda, a ver si se trata realmente de un "bug" de las librerías de GCC que implementa MinGW, o si yo tengo un error de concepto o interpretación del estándar C99, y tengo la suerte de que alguien me lo explique  ;D con amabilidad.

Una forma astuta de hacer una pregunta teórica es pues, reportar un "bug" del compilador, jejeje.  >:D

Pero para hacer una "jugada" como esa, tuve que estar muy seguro antes de que realmente el problema estaba en el límite de ambigüedad entre lo conceptual y los bugs de implementación.
Se trata de un detalle muy quisquilloso de los números de punto flotante, a saber, las macros de clasificación.
De hecho, casi que me convencí de que era un "bug" cuando investigué internamente cómo estaba hecha la macro (con las famosas DEB que les enseñe a hacer), y parecía que intentaba lograr algo que en el programa compilado no aparece. Lo mismo parecen sugerir los textos técnicos.

Seguramente la respuesta obtenida será breve, pensé.
Existía la posibilidad de que me manden al diablo, diciendo que eso no va ahí, y entonces tendría yo que seguir buceando y preguntando en Internet, a ver quién me da la solución...

Por suerte eso no pasó, y tuve una respuesta favorable.
En este mundo de cuestiones técnicas, "respuesta favorable" sólo significa que alguien puso una lista de especificaciones técnicas que en resumen significa esto: "Hay que investigar qué pasa con tal o cual función, queda pendiente de revisión, la tarea se asigna a tal o cual experto, se intentará solucionar para tal o cual número de versión".

No hubo mucha más discusión ni comentarios que eso, casi como un idioma de robots.


(16) En un momento dado pensé en escribirle un email a Derek Jones, que seguramente es capaz de aclararme las dudas respecto al lenguaje C y los tipos de punto flotante. Pero no lo hice, pues quise resolver las cosas de otro modo, y quizá hacerle una consulta más interesante sobre el tema, un poco más adelante.

______________________________

Al final de todo este proceso, no sólo fui aprendiendo a interactuar con la gente en internet,
sino que además ahora existe la posibilidad de que el problema del compilador se solucione,
gracias a que me animé a reportar estos problemas, incluso haciéndolo de forma algo equivocada al principio.  ;)

Aprovechando que existen estas comunidades de programadores que invierten mucho esfuerzo en dejar las cosas a punto, puede ser que en unos pocos meses el problema esté solucionado.

A mí sólo me queda "esperar".  :D

Visto así, pareciera que actúo en forma egoísta, usando a la gente, pero creo yo que tenemos que acostumbrarnos al modo moderno de hacer las cosas.
Siempre hay gente que sabe más que uno, y hay que delegarles los problemas o las responsabilidades.

En algún momento, cuando uno ya se ha vuelto experto, será quien le solucione la vida a otros.

O bien, en lo inmediato, que ellos vengan acá a preguntarme algo de matemática, y les retribuyo de esa manera el esfuerzo que ellos hacen por mí en el costado informático del mundo.

_________________________________

Lo que quiero mostrar es que no hay que tener prejuicios a la hora de lograr que un programa funcione.
No siempre la solución está "en la máquina" o "en el lenguaje C".
Hay que aprender otras habilidades, buscar información de diversas fuentes, insertarse en círculos sociales que nos resultan extraños, intentar comunicarse, aprender inglés, etc.

La única condición para salir a abordar terrenos y personas desconocidas es tomar antes algunas precauciones, informarse bien de las normas de etiqueta esperadas, a fin de que los demás tomen en serio nuestra búsqueda o reclamo. Una vez superado ese escollo, hay que meter el dedito en la tecla, y animarse.

Siendo así, para dentro de unos meses seguramente tendremos al menos una leve mejora en el manejo de números de punto flotante, y les informaré debidamente de cómo instalar la nueva versión, tanto del compilador GCC como con el entorno MinGW, en armonía con nuestro IDE wxDev-C++.

 :)
[cerrar]



Organización
Comentarios y Consultas

28 Agosto, 2013, 09:43 pm
Respuesta #32

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)

30 Agosto, 2013, 07:03 am
Respuesta #33

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
33. Caracteres en el lenguaje C (I)

   He tardado mucho desde el último post. No crean que es porque he olvidado este proyecto, sino todo lo contrario:
   El tema que nos ocupa ahora, el de los caracteres en C, es tan vasto y complejo, que no he tenido más remedio que invertir un largo tiempo investigando hasta al menos llegar a la orilla de la real comprensión del asunto.

   El lenguaje C en su versión estándar C99 tiene la obligación de considerarse en medio de una etapa histórica de transición en el mundo informático, en la cual los sistemas de codificación de caracteres tienen muchas variantes y estándares distintos, todos conviviendo juntos, y con la esperanza de poder migrar todos ellos a un único y uniforme estándar: el Unicode.
   Si bien todos los lenguajes de programación y todos los sistemas informáticos están orientados en esa dirección migratoria, el cambio definitivo a Unicode tardará todavía varios años más, debido a que está involucrado un gran costo económico, ya sea en el cambio de los equipos, la reescritura de antiguas bibliotecas de software, documentos antiguos guardados en una codificación previa a Unicode, páginas web y protocolos anticuados que todavía son útiles por diversas razones, etc.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El estándar C99 tiene en cuenta todas las variantes posibles, y más aún, tiene presente la distinción entre los caracteres de un programa fuente y los caracteres que perviven en el entorno donde el programa compilado habrá de ejecutarse.
   Es fácil imaginar situaciones en que una biblioteca de funciones se escriben bajo un determinado entorno, con caracteres codificados de cierto modo, y que luego el programa compilado tenga que llevarse o correrse en un entorno de computación distinto.
   Ya podemos vislumbrar en esto una primer fuente de sufrimiento, pues en general los programadores tienden a creer que los mismos caracteres a los que pueden acceder mientras están escribiendo un programa en C, son los mismos caracteres que podrán utilizarse luego mientras el programa esté compilado y ejecutándose.

Malas noticias: eso no es cierto.    :-\

   Las especificaciones del estándar C99 son tan amplias y abiertas a tantas posibilidades que sólo pueden producir un enorme desconcierto.
   En particular, es muy difícil que una implementación específica (compilador + librerías) sea capaz de seguir al pie de la letra todas las consideraciones del estándar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para llegar a comprender las consideraciones y peculiaridades del estándar en torno a los caracteres, es necesario tener un cierto conocimiento de la historia y detalles de las diversas codificaciones de caracteres que ha habido en el mundillo informático.
   Daré aquí una rapidísima reseña de esa historia:

(1844) En los albores de la telegrafía surgió el código Morse.
(1870) Luego la telegrafía utilizó el código de Baudot, estandarizado como ITA1: codifica caracteres mediante 5 bits.
(1930) El código de Baudot evolucionó gracias a la empresa Western Union a una nueva versión, estandarizada como ITA2.
(1956) La milicia estadounidense inventó el código FIELDATA, codificando caracteres en un sistema de 7 bits.

(1960) IBM lanza EBCDIC, un código de 8 bits basado en su antiguo sistema de tarjetas perforadas.
(1963) Apareció la primera versión de ASCII: caracteres para escritura en inglés de Estados Unidos, en formato de 7 bits.
(1967) Nueva versión de ASCII, ahora conteniendo letras minúsculas, y cambios en caracteres de control.
(1968) Estandarización internacional de ASCII mediante norma ANSI.
(1972) Creación del estándar ISO 646, que es como ASCII excepto por unos pocos signos de puntuación, que se dejan sin especificar, para ser aprovechados por las necesidades locales de cada nación.
(1973) Aparición de la norma ISO/IEC 2022, un sistema de codificación multibyte que permite usar ASCII junto con caracteres de control que dan acceso a subtablas de caracteres, permitiendo expresar símbolos de idiomas complejos como el Chino o el Japonés. Cada caracter puede estar expresado como 1 o más bytes (de 8 bits).
(1987) Aparición de las normas ISO/IEC 8859-X, de caracteres ASCII extendidos, cuyo formato es de 8 bits.
(1991) Nacimiento del consorcio Unicode, con el fin de representar todo caracter posible de todo alfabeto del mundo. Inicialmente se codificaba en 2 bytes, o 16 bits. Actualmente esto ha sido sobrepasado, por no ser suficiente.
(1993) Caracteres UCS, bajo la norma ISO/IEC 10646. En la actualidad equivale a Unicode.

   En la actualidad tenemos las codificaciones Unicode multibyte UTF8 (8 bits), UTF16 (16 bits) y la de ancho fijo UTF32 (32 bits).
   Se presume que esta última será suficiente para toda la historia futura de la humanidad.

   Conviven aún todos los sistemas desde EBCDIC en adelante, junto con multitud de variantes introducidas por fabricantes de equipos, sistemas operativos, etc.

   Para más detalles sobre esta historia, y sobre cómo funcionan los distintos métodos de cofidificación,
   los enlazo a mi artículo en la revista del foro sobre el respectivo tema:

Codificación de Caracteres

   En esencia, ASCII es la base de los sistemas de codificación más utilizados, y eso incluye a Unicode que es una extensión de ASCII.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Qué son los caracteres multibyte?

   Por un lado tenemos los códigos, que son números binarios de 8 bits, y que ocupan por lo tanto 1 byte.
   Cada byte representa un cierto caracter de cierto conjunto de caracteres.
   Este conjunto puede ir variando a medida que vamos leyendo datos de algún archivo de texto.
   Se presume que el primer caracter leído está en un estado inicial, que corresponde al estándar ASCII.
   Luego, mediante secuencias de ESCAPE se indican cambios de estado (SHIFT), que permiten especificar grupos de caracteres para la parte baja de la tabla (GL) y para la parte alta (GR).
   En tal sentido, el "estado" es una propiedad del sistema que permite saber a qué caracter nos estamos refiriendo en determinado momento.

   En cierto modo, la computadora tiene que "llevar la cuenta" o "memorizar" cuál es el último estado seleccionado, para saber dónde estamos ubicados.
   Hay sin embargo algunos caracteres que no son afectados por el "estado": son los caracteres de control, y muy especialmente el caracter nulo (el que tiene todos los bits igual a 0).

   A veces, un estado determinado indica que el próximo caracter ha de especificarse ya sea con 1 byte o con 2 o más bytes consecutivos.
   En resumen, los sistemas multibyte trabajan mediante cambios de estado indicados mediante secuencias de ESCAPE, y puede que haya caracteres representados con 1 byte o 2 bytes. En la práctica no suelen aparecer caracteres de 3 bytes o más, aunque no hay restricciones teóricas en este sentido.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Mientras las codificaciones multibyte aprovechan un sistema de 1 byte para representar muchos más caracteres que los 256 de máximo que estarían normalmente disponibles, esto no deja de ser un mecanismo, un artificio que funciona con determinadas reglas.
   Por otra parte, una codificación de caracteres anchos es aquella que tiene una cantidad fija de bytes para cada caracter, y cuyo tamaño en memoria es normalmente mayor que 1 byte por caracter. Lo normal son 2 bytes, pero no hay restricciones a anchos mayores. De hecho, para el juego completo de caracteres Unicode, son necesarios 4 bytes.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

07 Septiembre, 2013, 11:10 pm
Respuesta #34

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
34. Caracteres en el lenguaje C (II)

Entorno de traducción y entorno de ejecución.

   A partir de ahora tenemos que abrir nuestro cerebro partiéndolo en dos mitades.
   Cuando trabajamos con el lenguaje C tenemos que estar pensando siempre en dos sistemas de computación distintos: el 1er sistema es aquel en que escribimos nuestro programa y lo compilamos para crear un archivo ejecutable, mientras que el 2do sistema es aquel en donde dicho programa efectivamente correrá y hará de las suyas.
   Se llama entorno de traducción a aquel sistema en donde la edición y compilación del programa se realizan, y se llama entorno de ejecución a aquel sistema en donde el programa ejecutará realmente.
   Un ejemplo claro y sencillo: un amigo nuestro tiene una modernísima computadora Intel Core Duo, con sistema operativo Linux, y nos ha prometido hacernos un programa en C con un salido por el día del amigo que estará compilado de manera que no funcionará en su sistema, pero que sí funcionará en el nuestro: un Intel Celeron antiguo, con un desactualizado sistema Windows XP.
   Esto separa las herramientas de diseño de un programa, de las capacidades del sistema en que se ejecuta. Otros ejemplos más modernos serían estos: diseñar un jueguito que ejecutará en un teléfono con sistema operativo Android, pero que ha sido diseñado en una PC corriente con procesador Intel y sistema Windows 7.
   Las combinaciones son muchas, y cada vez más a medida que la informática moderna se diversifica.

   Se denomina archivo fuente al archivo de texto en el que escribimos nuestro programa en C.
   La versión compilada y lista para andar se llama archivo ejecutable.
   Los archivos fuente se diseñan o escriben en un determinado entorno de traducción, mientras que el ejecutable corre y realiza sus tareas en un determinado entorno de ejecución.

   Esta distinción nos puede parecer extraña, ya que toda la vida hemos escrito, compilado y corrido nuestros programas en una sola computadora: la que tenemos en casa, y punto.
   Pero a los fines teóricos, debemos tener siempre en cuenta este doble contexto para un programa en C.

   Cuando ambos entornos son diferentes en la práctica, se le suele llamar compilación cruzada (cross compiling).  :-* :-*

   ¿Por qué insistimos con todo este asunto? Bueno, es necesario ya que para definir exactamente cómo se entienden los caracteres en C, tenemos que distinguir en qué entorno estamos pensando: si el de traducción o si el de ejecución.
   Los conjuntos de caracteres tienen, en cada uno de esos casos, un nombre distinto, así como elementos potencialmente distintos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Un caracter (abstracto) es, según el estándar C un miembro de un conjunto de elementos usado para la organización, control o representación de datos.

(Todos estos términos nos llevaría meses definirlos, así que nos conformamos con la versión intuitiva del asunto.)

Un caracter (byte solitario) es una representación en bits que cabe exactamente en un byte.

Una collating sequence (¿sucesión de clasificación?) es una relación definida entre objetos de un mismo conjunto con propósitos de ordenación. Es decir, se dice qué elementos preceden a cuáles otros.

\( \bullet \)   Se definen dos conjuntos de caracteres y sus sucesiones de clasificación/ordenación (collating sequences): el conjunto de los caracteres fuente (del entorno donde los archivos fuente se escriben), y el conjunto de los caracteres de ejecución (del entorno donde el programa se ejecuta).

   Nunca se termina de aclarar en el estándar cuáles son todos los caracteres que hay en cada uno de esos conjuntos. Pero sí se aclara: cuáles son los caracteres que sí o sí tienen que estar en cada uno de ellos, y cuáles son los caracteres que ambos conjuntos sí o sí tienen que compartir.

   Cada uno de ambos conjuntos viene con una relación de orden previamente definida, que sirve para saber cuál va primero y cuál después. Esto se usa, claro está, cuando hace falta ordenar datos.

   Los caracteres usados en la definición del lenguaje C existirán al mismo tiempo en ambos conjuntos de caracteres.
(Estos son unos pocos caracteres, que luego listaremos, y forman apenas un subconjunto de los caracteres ASCII. ¡Y no se requiere que haya más caracteres que estos en ninguno de los dos tipos de entornos (fuente y ejecución)!).

   Cada conjunto (de caracteres) se divide en:

\( \bullet \)    Conjunto de caracteres básicos
\( \bullet \)    Conjunto de caracteres extendidos.

   A la unión de ambos conjuntos se le llama conjunto extendido de caracteres. Esta terminología es totalmente confusa, por supuesto.  :banghead: :banghead:

   Los caracteres básicos siempre han de estar disponibles, tanto en el conjunto fuente (la traduccón exacta sería: conjunto de caracteres para los archivos fuente) como en el conjunto de ejecución.
   Mientras que caracteres extendidos, que son siempre distintos y adicionales respecto los básicos, puede haber varios o ninguno. (Se sobreentiende que son finitos, aunque el estándar no aclara este punto, seguramente porque sería pedante una tal aclaración).

   El estándar no especifica los valores binarios a los cuales corresponde cada caracter, ya sea del entorno fuente o del entorno de ejecución. Aunque se especifican algunas otras propiedades.
   Estos valores son dependientes del compilador utilizado.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Los caracteres (abstractos) que existen siempre tanto en los caracteres fuente como en los caracteres de ejecución, son los siguientes:

\( \bullet \)   Alfabéticos (mayúsculas): A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
\( \bullet \)   Alfabéticos (minúsculas): a b c d e f g h i j k l m n o p q r s t u v w x y z
\( \bullet \)   Dígitos decimales:     0 1 2 3 4 5 6 7 8 9
\( \bullet \)   Caracteres gráficos: ! " # % & ’ ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
\( \bullet \)   Blancos y de control: Espacio, Tab Horizontal, Tab Vertical, Avance de Formulario.

   En total estos son los 95 caracteres básicos en el entorno de traducción.
   En total son 96, pues se agrega el siguiente:

   Se supone que en el entorno de traducción (archivos fuente) hay alguna manera de indicar el final de una línea de texto. El estándar considera a "esto" como si fuese un único caracter fin de línea.

(Se podría decir que es un caracter básico más en el entorno de traducción ??? ).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El entorno de ejecución contiene a todos esos caracteres, y además los siguientes caracteres de control:

\( \bullet \)   Alerta, Retroceso, Retorno de Carro, Nueva Línea.
\( \bullet \)   También contiene el Caracter Nulo (ver abajo).

   En total hay 100 caracteres fijos en el entorno de ejecución.

   Estos nuevos caracteres no están, necesariamente, en el entorno de traducción.
   Por lo tanto, tienen que indicarse con secuencias de escape. (Ver luego).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Trigrafos.

Los trigrafos son secuencias de exactamente 3 caracteres que han de reemplazarse por exactamente 1 caracter. Son los siguientes:

??=   #
??(   [
??/   \
??)   ]
??’   ^
??<   {
??!   |
??<   }
??-   ~


   Los trigrafos aparecen exclusivamente en el entorno de traducción, y así:

   El compilador analiza el archivo fuente (o sea, nuestro programa en C), y busca todas los trigrafos. Los reemplaza automáticamente por el caracter que se indica a la derecha.
   Después de esta operación comienzan las etapas de compilación propiamente dichas.
   Se observa que:

\( \bullet \)   Los trigrafos son caracteres del conjunto básico del entorno de traducción.
\( \bullet \)   Los trigrafos se reemplazan luego por otros caracteres que también son del básicos del entorno de traducción.
\( \bullet \)   El reemplazo se realiza sólo en el entorno de traducción, y nunca en el entorno de ejecución.
\( \bullet \)   El reemplazo no es opcional. Se realiza SIEMPRE. (En realidad hay sistemas donde los trigrafos se han desactivado por conveniencia, tal como en los compiladores para Apple Macintosh).
\( \bullet \)   Para evitar confusiones, debemos saber que el compilador primero reemplaza los trigrafos, y después analiza el código fuente resultante.
\( \bullet \)   Los trigrafos se utilizan para representar algunos caracteres de la norma ASCII (de 7 bits) que no están contemplados en la norma ISO/IEC 646.

   Así, si escribimos int float w??(20??); se transforma en int float w[20];.

   Los trigrafos se han inventado por la siguiente razón: El código ASCII no fue adoptado por todos los países con total exactitud, sino que hay posiciones de la tabla que han sido utilizados para otras necesidades locales (acentos, tildes, letras especiales, ligaduras, etc.).
   Eso hace que algunos caracteres necesarios en el lenguaje C no estén disponibles.

   Las posiciones en gris de la siguiente tabla (extraída de Wikipedia) muestra en gris las posiciones usadas para variantes locales (sin embargo los trigrafos no cubren todos los casos, sino sólo los que se necesitan en C):



Importante: A algún diseñador de compiladores se le podría ocurrir la idea de seguir jugando con trigrafos o cosas similares. En realidad esto no está permitido por el estándar: los únicos trigrafos admitidos son los 9 ya listados.

   Este tema está relacionado específicamente a la norma ISO/IEC 646, cuyos detalles hemos discutido aquÍ.

ISO/IEC 646 (ECMA 6)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Existencia de caracteres multibyte

   La norma C99 establece que:

\( \bullet \)   El conjunto de caracteres fuente puede (léase: no es obligatorio, pero tiene permitido) contener caracteres multibyte usados para representar miembros del conjunto extendido de caracteres.

Importante: la norma C99 no especifica de qué modo son considerados los caracteres multibyte: puede que se decodifiquen transformándolos de alguna manera a los miembros del conjunto extendido de caracteres, o puede que queden sin transformar, etc. Esto es algo que concierne solamente a la implementación local utilizada.

   El resultado de esta norma tan incierta es que no podemos estar seguros de qué caracteres tenemos a mano o no para escribir un programa en C. ¿Esto es grave o trae algún problema? Respuesta: NO. En el entorno de traducción sólo tenemos los programas fuente, y allí no es estrictamente necesario tener disponibilidad o certeza alguna acerca de los caracteres multibyte.
   Estos caracteres podrían aparecer como parte de un identificador, o como parte de un comentario.
   Pero cuando compilamos el programa, la versión ejecutable no se preocupa en lo más mínimo de los caracteres que usamos para indicar identificadores (no quedan rastros de esto en el ejecutable), ni mucho menos los comentarios.

Los programas fuente son archivos de texto que residen en el entorno de traducción.

   Lo importante entonces es que el compilador sea claro acerca de qué caracteres nos permite usar, o mejor, que explícitamente podamos saber qué codificación de caracteres estamos usando para archivos de texto.

Pero esta información tenemos que buscarla externamente: en la documentación del compilador.

   Concretamente: Puede que en el entorno de traducción sólo estén los caracteres básicos, o puede que haya extendidos. Entre los extendidos, puede que algunos se generan mediante alguna codificación del tipo "multibyte", o puede que simplemente se tenga una codificación sencilla de 1 solo byte (como los formatos de ASCII extendidos), o puede que se use alguna codificación sencilla de 2 o más bytes, aprovechando Unicode.

   En el caso de caracteres multibyte, puede que no se haga traducción alguna a elementos concretos de un conjunto dado de caracteres, o puede que sí.
   Si queremos estos detalles, tenemos que indagar en la documentación del compilador que estemos utilizando.

\( \bullet \)   El conjunto de caracteres de ejecución también puede contener caracteres multibyte para representar miembros de su propio conjunto extendido de caracteres.
\( \bullet \)   En caso de haber caracteres multibyte tanto en el entorno de traducción como el de ejecución, no tienen por qué coincidir sus decodificaciones.

   Más aún: el conjunto de caracteres del entorno de traducción está siempre fijo para una implementación dada, pero en el entorno de ejecución, la decodificación de los caracteres multibyte podría cambiar veces en un mismo programa, dependiendo de si se cambian o no la configuración "local" (o sea, los detalles de un programa que dependen de cambios de idioma o nacionalidad).

   Nota adicional: Aquí existe una discusión de fondo acerca de los caracteres que pueden visualizarse exitosamente o no mientras el programador está escribiendo o leyendo un programa en C. Si un mismo programa se lee en entornos distintos, puede que haya caracteres que no se visualicen correctamente o no, según el entorno circundante.
   El estándar da certezas mínimas para que al menos los caracteres básicos sean visibles en cualquier caso.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Codificación: Se refiere al valor numérico que se asigna a un caracter.

En C99 podemos asegurar lo siguiente (tanto para el entorno de traducción como para el entorno de ejecución):

\( \bullet \)   La codificación de los caracteres básicos, caben siempre en 1 byte.
(Esto evita que haya implementaciones que sean, por ejemplo, totalmente multibyte).

\( \bullet \)   El tamaño (cantidad de bits) que ocupa 1 byte está dado por la macro CHAR_BIT. Se asegura que este valor será siempre \( \geq{8} \).

   La observación sutil que se desprende de esto es que hay caracteres que pueden tener una codificación mayor que \( 255 = 2^8-1 \)... siempre y cuando el sistema subyacente admita bytes con más de 8 bits.
   ¿Existen de verdad sistemas de computación en los que los bytes tienen más de 8 bits?
   . No voy a dar ejemplos por ahora.

   Aunque no se dice explícitamente, un hecho que podemos afirmar es que CHAR_BIT permanece constante a lo largo de todo el programa: un programa dado no ejecutará en una máquina donde los bytes tengan... ¡número de bits variable!
   Esto no lo dice el estándar... pero uno lo deduce a partir de las misteriosas leyes del sentido común.
   No existen en la actualidad sistemas de cómputo en los que la cantidad de bits vaya a variar mientras un programa ejecuta.

Sin embargo, tengo mis dudas. ¿Qué pasa si una aplicación se diseña para funcionar en una red de computadoras cuya arquitectura varía en cada terminal?  ::)
Pero esto no nos ha de preocupar, olviden que lo he mencionado.

* La presencia, significado y representación de los miembros adicionales concierne a la "configuración local" específica que hay en el programa ejecutable.

(Aunque no lo hayamos mencionado, en todo momento en un programa ejecutable hecho en C se supone que está activa una cierta "configuración local". Hay una por defecto, llamada "C", pero puede cambiar a lo largo del programa. Según la configuración existente es que cambia el modo en que se interpretan o manejan los caracteres adicionales.)

Los valores (numéricos binarios) de las codificaciones asociadas a los caracteres básicos (tanto del entorno de traducción como de ejecución) caben siempre en 1 byte, pero sus valores específicos son elegidos por el compilador.
Sin embargo, se puede asegurar que:

\( \bullet \)   Los valores de los caracteres básicos que representan dígitos son siempre consecutivos.
\( \bullet \)   Los valores de los caracteres básicos son enteros no negativos.
\( \bullet \)   Los valores de los caracteres básicos caen dentro del rango de valores del tipo char.

   En el conjunto de caracteres de ejecución existe un caracter cuyo valor es el entero 0, es tal que todos sus bits son 0, y se llama el caracter nulo.
   Como se puede apreciar, el caracter nulo no tiene la obligación de existir en el entorno de traducción (si lo pensamos un poco, tiene perfecto sentido...).

\( \bullet \)   Los caracteres multibyte funcionan acorde lo que se conoce como una máquina de estados finitos??? ??? :o :o :o

   Es demasiada terminología para decir que, simplemente, algunos bytes se usan para codificar "estados" (se llaman SHIFTS), que permiten acceder, temporalmente, a otros caracteres que comparten una misma posición en una tabla dada.

   Una analogía en nuestro teclado sería esta: El estado de las teclas "normal" genera letras minúsculas, pero cuando activamos la tecla CAPS LOCK, las mismas teclas generan ahora otros caracteres: letras mayúsculas, hasta que volvemos a presionar CAPS LOCK, y el "estado" cambia de nuevo a "letras minúsculas".

   El CAPS LOCK es un ejemplo de un "estado" que dura hasta que se indique lo contrario.
   Es un "SHIFT permanente".
   Es posible que haya algún "SHIFT temporal", tal que el cambio de estado se aplica sólo al siguiente caracter. (Esto ocurre más o menos cuando presionamos la tecla de la tilde ' seguida de una vocal, que termina generando una volcal acentuada: á).

   Lenguajes más complicados como el japonés o el chino requieren variantes más intrincadas de "cambios de estado". Ahora bien, supongamos que el caracter de escape usado para indicar CAPS LOCK hubiese sido por ejemplo "@" (todo esto es un ejemplo inventado por razones pedagógicas...):

   La cadena "xyz @abc @huv ~nn" resulta en lo siguiente: "xyz ABC huv ñn".

   Algo similar ocurre con los caracteres multibyte "de verdad", salvo que los SHIFTS se especifican mediante un conjunto de reglas especificado localmente, en cada sistema, y no podemos aquí enumerar y ejemplificar todos los casos posibles.

   Es complicado programar caracteres multibyte, porque dada una secuencia de caracteres, el significado de un caracter depende, en un momento dado, del "estado" en que se encuentre la secuencia, o sea, hay que tener la información concreta de toda la historia de SHIFTs que se han utilizado desde el principio de la secuencia.

   En nuestro ejemplo inventado del CAPS LOCK, si en un momento dado tenemos una secuencia de caracteres tal que aparece una b, ¿qué significa? b ó B. Eso depende de que el estado CAPS LOCK esté activado o no, y dicha información no está en el caracter mismo, que siempre es b, sino que proviene de la historia de todos los caracteres leídos antes.
   Muchas veces esto obliga a leer textos de principio a fin para realizar correctamente operaciones de cómputo que, de otro modo, serían mucho más sencillas.

   Lo que estamos diciendo es que un caracter multibyte se codifica usando a su vez otros caracteres (de 1 solo byte) y SHIFTs. Puede que haga falta usar más de 1 byte para generar un caracter multibyte deseado.
   ¿Cuántos? No se puede saber de entrada.
   Pero el estándar C99 establece que ha de haber un máximo de bytes en el entorno de ejecución que será suficiente para albergar uno de estos caracteres multibyte.
   Podemos obtener la información de dicho máximo consultando el valor de la macro MB_LEN_MAX.
   Más todavía: no importa si se cambia la configuración local varias veces en el programa. El valor máximo alojado en esa macro es el máximo que el compilador soporta para toda configuración local posible.
   Este límite superior ayuda también a poner una cota a los posibles SHIFTs redundantes que pudieran existir o aparecer.

   Para el lector interesado, un caso de estudio importante puede ser la norma ISO/IEC 2022, que estandariza métodos de codificación para caracteres multibyte (aunque las codificaciones propiamente dichas se especifican en otras normas).

http://en.wikipedia.org/wiki/ISO/IEC_2022

   Lo más probable es que nunca tengamos que lidiar con estos problemas de los caracteres multibyte.
   De hecho, con el advenimiento de Unicode (un sistema simple, uniforme, universal), las complicadas codificaciones multibyte han de caer en desuso.
   Sin embargo, tenemos que hablar de esto porque aún existe este tema en la programación de hoy en día, y además hay reglas importantes asociadas a los caracteres multibyte, por ejemplo esta:

\( \bullet \)   En una secuencia de caracteres multibyte, aquellos bytes cuyos bits son todos nulos corresponden siempre al caracter nulo, y no interesa aquí en qué estado (SHIFT) se encuentre
\( \bullet \)   En el entorno de ejecución, el caracter nulo no puede formar parte en forma parcial de un caracter multibyte, ni estar afectado a SHIFT alguno. Sólo puede haber caracteres nulos al final de una secuencia completa que conforme un caracter multibyte.

   En un programa en C, podemos contar conque una secuencia multibyte no terminada, al final de una cadena de caracteres, no tendrá efecto alguno. (Revisar).

En realidad, siempre se supone que hay un SHIFT activado: es el denomiando "estado inicial".

   Se supone que un archivo fuente siempre empieza y termina en su estado inicial.
   Y que una secuencia de caracteres de un programa ejecutable siempre comienza en dicho "estado inicial".
   El caracter nulo vive, pues, en este "estado inicial".

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Soporte para caracteres multibyte en distintos compiladores:

   No puede asegurarse que todos los compiladores manejen por igual los caracteres no básicos.
   En particular puede que algunos de ellos indiquen errores en donde otros compiladores consideren que se trata de código válido.

\( \bullet \)   Se exige que todo identificador, comentario, constante literal, en un programa fuente, comience y termine en "estado inicial" como una secuencia de caracteres multibyte válida.  ::)

   Lo triste aquí es que cada compilador puede elegir lo que considera una secuencia válida de caracteres multibyte.

   Como consecuencia de esto, un mismo programa puede que no compile en distintos entornos de traducción.
   Esto obviamente trae problemas de compatibilidad/portabilidad.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

08 Septiembre, 2013, 07:58 am
Respuesta #35

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
35. Caracteres en el lenguaje C (III): Constantes de caracter

(To foreign readers: if you experiment difficulties in reading this post, change the character encoding of your browser to Unicode)

   Una constante (o un literal) de caracter es un modo de indicar en un programa fuente el valor asociado a un caracter dado en el entorno de ejecución. Por ejemplo, la constante de caracter 'x' representa el valor numérico asociado al caracter x, que es 120.
   De esta manera dejamos en claro desde un principio que las constantes de caracter representan números. Esos números tienen relación con caracteres, pero un "caracter" no es un valor ni un tipo de datos en C.
   El valor numérico depende de la codificación que utilice el entorno de ejecución, y esto no puede predecirse (o sea, el estándar C99 no dice nada al respecto).

   Hay dos tipos de constantes de caracter:

\( \bullet \) Las que van encerradas entre comillas simples: 'x', '1', '@',
\( \bullet \) las que anteponen además una L mayúscula: L'x', L'1', L'@'.

   Una constante de caracter (de alguno de los dos tipos) es válida si entre las dos comillas simples se coloca una de las siguientes:

\( \bullet \) Un caracter cualquiera del conjunto fuente, siempre y cuando no sea alguno de estos: '  \  {nueva-línea}
\( \bullet \) Una secuencia de escape simple, que es un par de caracteres formado por una contrabarra \ y una letra, así:

\'   \"   \?   \\   \a   \b   \f   \n   \r   \t   \v

\( \bullet \) Una secuencia de escape octal, lo cual consiste de una contrabarra \ seguida de uno, dos o tres caracteres octales. Por ejemplo: '\05', '\34', '\177'.
\( \bullet \) Una secuencia de escape hexadecimal, lo cual consiste en el par de caracteres \x seguida de 1 o más dígitos hexadecimales (los dígitos mayores que 9 pueden indicarse con letras tanto minúsculas como mayúsculas). Ejemplos:
'\x890C4', '\x074bb218faab', '\xff'.

   Se observa que no hay límites en el número de dígitos hexadecimales que se aceptan como una secuencia hexadecimal válida. Pero esto no significa que se acepten números enteros arbitrariamente grandes.
   Cada constante de caracter representa un número entero, concretamente, es una constante de tipo int.

   Si una contrabarra \ está seguida por algún otro caracter que los especificados, el comportamiento es indeterminado (o depende de la implementación local).
   Importante: Si bien las constantes de caracter se especifican mediante el conjunto fuente, sus valores efectivos serán los que correspondan al entorno de ejecución.
   Las secuencias de escape se usan para indicar caracteres del conjunto de ejecución que, de otro modo, no se podrían indicar con el conjunto de caracteres fuente.
   Aquí, pues, tenemos que tener en cuenta los dos conjuntos de caracteres, y las relaciones entre ellos.

   Hay dos maneras de representar comillas dobles: '\"' o simlemente '"'.
   Hay dos maneras de representar signos de pregunta: '\?' o simlemente '?'.
   Hay una sola manera de representar una comilla simple: '\''.
   Hay una sola manera de representar una contrabarra: '\\'.
   Una barra solitaria, como en '\', da un error de sintexis, porque \' se interpreta como el código de escape de la comilla simple.

   ¿Para qué sirve la secuencia de escape del signo de pregunta '\?' ?
   Su misión es defendernos de los trigrafos. Si quisiéramos tener literalmente una cadena de caracteres como "??[", podríamos indicarla con "\?\?[". (Ver abajo: constantes de cadena).



   Cada secuencia de escape antes listada representa exactamente un único caracter del entorno de ejecución.
   Los valores octales y hexadecimales representan un número entero no negativo, correspondiente, supuestamente, al caracter que uno desea. Sin embargo, dado que lo que se especifica del caracter es sólo su valor numérico asociado, es lo mismo '\xA3' que la constante hexadecimal numérica 0xA3, por ejemplo. Representan un mismo valor numérico.
   Los valores octales que pueden representarse con una constante de caracter varían entre 0 y 511. En particular, es común ver en los programas el uso de '\0', una constante de caracter octal que se usa para denotar el caracter nulo (cuyo valor siempre es 0).
   En general tenemos caracteres representables en bytes de 8 bits, lo cual da un valor máximo posible de 255. Esto nos dice que las constantes de caracter octales en general son suficientes para cubrir todas las necesidades al respecto. ¿Qué ocurre en este caso con valores, digamos, más allá de 255 en este caso? El compilador reconoce un tal valor, pero se considera un número entero fuera de rango (ver debajo).

   Una constante de caracter es un número entero. Si se escriban como "caracteres" es más bien por conveniencia intuitiva para el programador, y el significado que tendrá en el programa en ejecución. Por otra parte, el valor numérico concreto de una tal constante no está previamente especificado: 'A' no significa necesariamente el número 65 (código ASCII de la letra A). Si bien eso será cierto en casi todos los casos, no podemos darlo por garantizado, y no conviene programar bajo la creencia de que 'A' "es" el número 65.



Las secuencias de escape simples \a \b \f \n \r \t \v
denotan caracteres de control, no (necesariamente) imprimibles en el entorno de ejecución:

\a Caracter BELL (pitido).
\b Caracter BACKSPACE (retroceso).
\f Caracter FORMFEED (avance de formulario).
\n Caracter NEWLINE (nueva línea).
\r Caracter CARRIAGE RETURN (retorno de carro).
\t Caracter TAB (tabulación).
\v Caracter VERTICAL TAB (tabulación vertical).


Daremos más detalles de estos caracteres en otro post...



Rangos de valores numéricos para las constantes de caracter:

   Las constantes de caracter entrecomilladas se denominan constantes de caracter enteras.
   Las constantes de caracter entrecomilladas precedidas por L se denominan constantes de caracter ancho.
   (Nota: sólo se admite la L mayúscula para caracteres anchos, y nunca la l minúscula).

\( \bullet \) Una constante de caracter tiene tipo int.

\( \bullet \) El rango de valores de una constante de caracter octal o hexadecimal siempre "DEBE SER REPRESENTABLE" en el del tipo unsigned char.
\( \bullet \) El tipo de datos char permite (es suficiente para) especificar el valor de la codificación de cualquier caracter del entorno de ejecución.
\( \bullet \) Además, un miembro del conjunto básico de caracteres (del entorno de ejecución) tiene asociado siempre un valor no negativo.
\( \bullet \) Los valores de los caracteres que representan dígitos: '0' a '9' siempre son contiguos.
\( \bullet \) El valor numérico de una constante caracter de 1 byte, es el que corresponde a dicho caracter en el entorno de ejecución. (Aclaración: si la letra A tiene un código en el entorno de traducción, y tiene otro código numérico en el de ejecución, entonces la constante 'A' se refiere al valor numérico del entorno de ejecución).
\( \bullet \) Es posible indicar constantes de caracter con más de 1 caracter fuente (como 'ab') o secuencias de escape. Pero el significado de esto no está definido en el estándar C99, y depende de la implementación.
(Nosotros intentaremos, pues, no usar este tipo de cosas).
\( \bullet \) El valor de una constante de caracter que contiene un único caracter o una secuencia de escape, es el que tendría si se lo considera como un dato de tipo char que promociona (o convierte) a tipo int.

   ¿Qué quiere decir todo esto?

   El tipo de datos char es, recordemos, uno más de los tipos de datos aritméticos enteros.
   Asociados a él están los tipos signed char y unsigned char.
   Recordemos también que en algunas implementaciones puede darse el caso de que char equivale a signed char, y en otros casos char equivale a unsigned char.

   Los rangos de valores de estos tipos de datos son los siguientes intervalos de números enteros:

         char: [CHAR_MIN, CHAR_MAX]
unsigned char: [0,          UCHAR_MAX]
  signed char: [SCHAR_MIN, SCHAR_MAX]


   Por otro lado, tenemos el tipo int, cuyo rango de valores es:

int: [INT_MIN, INT_MAX]

   En cualquier caso, el rango de valores de signed char "cabe" en el rango de valores de un int.
   Además, todos los valores no negativos que están en el rango de valores admitido para signed char, también están en el rango de valores admitido para unsigned char.

   ¿Podría ocurrir que signed char e int tengan el mismo rango de valores? Respuesta: en teoría .
   Esto quiere decir que no podemos programar suponiendo que un int siempre tiene un rango de valores mayor, por más que esto es lo que sucede en la mayoría de los casos.
   (Las excepciones suelen darse en máquinas donde 1 byte ocupa más de 8 bits).

Volviendo a los caracteres:

   Una constante de caracter de tipo octal o hexadecimal determina un valor numérico entero y no negativo. En el caso octal, hasta 511, en el caso hexadecimal, no hay límites.
   Pero este valor DEBE poder representarse como un unsigned char.
   Si esto no ocurre, el estándar C99 indica que se trata de una violación de restricción (constraing violation), o sea, una situación anómala en el lenguaje, que requiere un aviso del compilador.

   En este caso podría ocurrir que el programa no compile, o que compile pero con un mensaje de advertencia (warning) informando que la constante está fuera del rango (de un unsigned char).
   Si el compilador acepta de todos modos la constante, no podemos asegurar qué camino va a tomar.
   Sólo podemos estar seguros de que elegirá un valor en el rango de unsigned char para esta constante.

   (El compilador GCC 4.8, con la opción -std=c99, lleva a cabo el procedimiento típico que el estándar exigiría a cualquier constante no negativa fuera de rango: trunca el número, quedándose con la porción menos significativa. En concreto, si la constante que hemos definido tiene valor X, el valor se cambia a R, donde R es el resto de la división entre X y UCHAR_MAX.)

   Luego de que el tamaño de la constante ha sido "resuelto" de alguna manera, el compilador intenta convertir el valor de la constante a tipo char.
   Mientras el valor de la constante se mantenga no negativo y no sobrepase a CHAR_MAX, no habrá cambios en el numerito que veníamos arrastrando.
   Pero si el valor fuese mayor que CHAR_MAX, el número resultante dependerá de los caprichos del compilador utilizado. Ya el estándar no asegura nada.

   Esta situación incómoda puede darse si char coincide con signed char,
   Supongamos el caso típico en que CHAR_BIT es 8. Tendríamos que '\xA0' es una constante de caracter válida (pues sería representable en un unsigned char, ya que 0xA0 es igual a 160, menor que UCHAR_MAX, que es 255).
   Al convertir eso a signed char, obtendríamos un número negativo (no voy a decir cuál, pues esto depende de la implementación).
   En un último "paso" (que virtualmente lleva a cabo el compilador), ese valor char es promocionado a int.

   En nuestro ejemplo de char igual signed char, con 8 bits en 1 byte, la situación es que toda constante de caracter (válida) tiene un valor que oscila entre -128 y +127, y se considera de tipo int.

   En el caso de que tuviésemos que char igual a unsigned char, y tal que el rango de valores de unsigned char cabe en int, tendríamos que toda constante de caracter (válida) tiene un valor que oscila entre 0 y 255.

   ¿Y qué pasa si hay valores en unsigned char que no están en int?
   Supongamos el caso en que int coincidiese con signed char.
   Siendo así, el rango de valores de unsigned char iría de 0 a 255, mientras que el de int iría de -128 a +127.
   El resultado sería que las constantes se convierten de nuevo a int, dando un comportamiento no claramente definido en el caso de valores mayores a +127.

   En el caso de constantes de caracter válidas o admitidas por el compilador, tenemos (internamente) esta cadena de conversiones:

constante original --> (unsigned char) --> (char) --> (int)

   Puede resultar sorprendente que para algunas implementaciones la constante '\xFF', que a propósito se refiere a un valor hexadecimal igual al decimal 255, termine teniendo un valor ¡negativo! (típicamente -1).

Moraleja: Una constante de caracter, si bien representa un número entero, no está destinada a representar información con significado numérico, sino que es una herramienta para manejar caracteres en un programa dado.

Luego: No podemos confiar en que siempre '\x(hexa)' será lo mismo que 0x(hexa).
   Pero: Sí que podemos estar seguros de que son el mismo número, cuando el valor hexadecimal sea inferior a SCHAR_MAX.



Valores para caracteres anchos:

   Repitamos la misma experiencia de antes, ahora para caracteres anchos.

   Primero aclaremos que en el archivo de cabecera <stddef.h> está definido el tipo de datos entero wchar_t. (También aparece definido en <stdlib.h> y en <wchar.h>)

\( \bullet \) Una constante de caracter ancho tiene tipo wchar_t.
\( \bullet \) El rango de valores de una constante de caracter octal o hexadecimal siempre "DEBE SER REPRESENTABLE" en el del tipo sin-signo (unsigned) que corresponda a wchar_t.
\( \bullet \) El tipo de datos wchar_t permite (es suficiente para) especificar el valor de la codificación de cualquier caracter del entorno de ejecución.
\( \bullet \) Todo caracter ancho especificado por medio de 1 solo caracter multibyte (que tiene correspondencia con algún caracter del entorno de ejecución) se asigna en una forma bien definida al valor wchar_t (de un caracter ancho) que le corresponde, mediante las reglas especificadas para la función de biblioteca estándar llamada mbtowc() (la cual no analizaremos aquí).
\( \bullet \) Si se indica más de 1 caracter multibyte en la constante, o el caracter multibyte dado no se corresponde con caracter alguno del entorno de ejecución, el estándar C99 no es capaz de definir o decidir qué comportamiento adoptará el compilador en ese caso.

   El tratamiento dado a los caracteres anchos dependerá en todo momento, en la ejecución del programa, de la "configuración local" que esté activa en un instante dado.



¿Es cierto que los caracteres anchos son más anchos que los caracteres "normales"?

   Muchas veces encontraremos compiladores donde no hay soporte adecuado para los caracteres anchos. La idea es que los caracteres anchos sirvan para representar todos los caracteres del conjunto especificado en ISO/IEC 10646 (que equivale a Unicode).
   Pero esto no necesariamente tiene que ser así, y bien podríamos tener wchar_t siendo de 1 byte.



¿Es cierto que para los caracteres del conjunto básico, las constantes de caracter y las constantes de caracter ancho tienen el mismo valor numérico asociado?

   Por ejemplo, ¿es cierto que 'x' da un número entero igual que L'x'?

Respuesta: ¡En principio no se puede asegurar!  :o :o

   ¡Oh, por Dios!  :banghead: :banghead: :banghead:
   Lo lógico es pensar que, en la mayoría de los casos, sería saludable que el caracter de la letra x tuviera el mismo código numérico ya sea que se considere como un caracter normal que cuando se le considere un caracter ancho.
   El programador puede verificar si éste es el caso, consultando la macro:

__STDC_MB_MIGHT_NEQ_WC__

   Si el valor de esa macro es 1, significa que los valores numéricos de las versiones "normal" y "ancha" de una misma constante de caracter podrían diferir.
   Si dicha macro tiene otro valor, o no está definida, entonces podemos asegurar que los caracteres básicos tienen constantes asociadas con el mismo valor numérico en sus versiones "normal" y "ancha". Así, por ejemplo: 'x' tiene el mismo valor numérico que L'x'.
   El caracter ancho nulo corresponde al valor 0 (de tipo wchar_t).



   Sobre wchar_t:

\( \bullet \) Los valores mínimo y máximo tolerados por wchar_t son: WCHAR_MIN y WCHAR_MAX.
\( \bullet \) Si wchar_t se define como un tipo entero con signo (signed), entonces contiene como mínimo al rango de valores [-127, +127]. Si se define como entero sin signo (unsigned), entonces contiene como mínimo al rango de valores [0, 255].
\( \bullet \) El tipo de datos wint_t se relaciona "de algún modo" con wchar_t (del mismo modo que int se relaciona con char en funciones de biblioteca). Sus valores mínimo y máximo son WINT_MIN y WINT_MAX.
\( \bullet \) El rango de valores de wint_t contiene en cualquier caso, como mínimo, al rango de valores de wchar_t.
\( \bullet \) Si wint_t se define como un tipo entero con signo (signed), entonces contiene como mínimo al rango de valores [-32767, +32767]. Si se define como entero sin signo (unsigned), entonces contiene como mínimo al rango de valores [0, 65535].
\( \bullet \) wchar_t y wint_t pueden ser el mismo tipo entero.  ::)

   Lo importante respecto este último punto es que:
\( \bullet \) Todo valor asociado a un caracter ancho puede representarse tanto en el rango de valores de wchar_t como en el de wint_t. (Si sobran valores numéricos, no asignados a caracter alguno, no importa  ;) ).
\( \bullet \) Sin embargo, el tipo wint_t es capaz de albergar al menos un valor numérico más que no corresponde a los valores enteros de los caracteres anchos.

   ¿Pero y entonces? ¿Si wint_t tiene espacio para al menos 1 valor más que wchar_t, no amerita tener un tipo de datos mayor? De hecho no, porque si a wchar_t le quedaron valores sin asignar a caracteres anchos, cualquiera de esos valores puede usarse como el adicional requerido por wint_t, y así ambos tipos enteros pueden tener el mismo tamaño en memoria.



Un poco de Unicode.

   La norma ISO/IEC 10646 equivale a Unicode. Contiene un conjunto de caracteres mucho mayor que ASCII, cuya virtud es contener juegos completos de caracteres para todos los idiomas de la humanidad, hasta la fecha. También contiene símbolos matemáticos, científicos, técnicos, etc.
   El conjunto resultante necesita 32 bits para tolerar todas las posibilidades. Su codificación se denota UTF-32. Sin embargo, de los \( 2^{32}(\approx 4000\textsf{\ millones}) \) códigos posibles, se usan apenas 110000 (año 2013). Para la mayoría de los casos es suficiente la versión original de Unicode que "encajaba todo" en 16 bits, dando un máximo de 65535 caracteres. (Esto es lo que hacen por ejemplo las fuentes típicas de Windows).

   A fin de ahorrar espacio de almacenamiento, dado que no siempre se requieren todos los caracteres de Unicode, se suelen usar otras dos codificaciones: UTF-16 y UTF-8. Son codificaciones multibyte de longitud variable, es decir: dan reglas que permiten especificar todos los caracteres Unicode mediante SHITFs adecuados, que indican qué parte de la tabla Unicode ha de utilizarse. Los caracteres se representan en UTF-16 con 1 o 2 bytes, mientras que en UTF-8 se utilizan de 1 a 4 bytes.
   No vamos a entrar en detalles de cómo es la codificación multibyte utilizada, pero diremos que está diseñada de forma ventajosa respecto a otras estrategias multibyte. Por ejemplo, no es necesario llevar la cuenta de todos los SHIFTs que hubieren desde el principio del Universo, y además, si un programa observa 1 byte individual, es capaz de darse cuenta en qué estado de la codificación multibyte está.

   En UTF-8, los caracteres ASCII funcionan normalmente con los mismos valores codificados de siempre (del 32 hasta el 127), ocupando 1 byte.

¿Hay soporte en C para caracteres Unicode? ¿Cómo?

   En C99 se declara que:

\( \bullet \) El conjunto requerido Unicode consiste en todos los caracteres definidos por la norma ISO/IEC 10646, junto con todos las enmiendas y correcciones que correspondan a los sucesivos años o versiones.

\( \bullet \) Para saber la fecha de la versión de Unicode que se está usando, se debe consultar el valor de la macro: __STDC_ISO_10646__. Su valor tiene el formato yyyymmL, es decir, un número entero de tipo long int de 6 dígitos decimales, tal que los primeros 4 corresponden al año, y los 2 que siguen al mes.

Caracteres Unicode en el Entorno de Traducción

   En el código fuente (o sea, en el entorno de traducción) pueden usarse los nombres de caracter universales (abreviado: UCN):

\( \bullet \) Un UCN tiene alguno de los formatos

\uhhhh: La letra u siempre es minúscula, y hhhh denota exactamente 4 dígitos hexadecimales (ni más ni menos).

\UHHHHhhhh: La letra U siempre es minúscula, y HHHHhhhh denota exactamente 8 dígitos hexadecimales (ni más ni menos).

   Es una manera de escribir (en el entorno de traducción) un caracter Unicode a través de su código en hexadecimal.
   La forma "larga" \UHHHHhhhh permite especificar cualquier caracter Unicode.
   La forma "corta" \uhhhh[/i][/color] se usa para indicar los caracteres Unicode del primer plano de 65535 elementos de la tabla Unicode, y así el caracter \uhhhh[/i][/color] equivale a la versión "larga" con 0's delante: \U0000hhhh.

   Esta notación puede usarse en el entorno de traducción para escribir un programa en C.
   Cada UCN representa un caracter Unicode a través del código hexadecimal correspondiente.
   (Así como los trigrafos se reemplazan luego por caracteres específicos, también los nombres de caracter universales hacen algo similar).

   Esto otorga un soporte portable para caracteres Unicode, en el costado del entorno de traducción.

\( \bullet \) Los nombres de caracter universales no pueden representan tal que su identificador corto (o sea, sus últimos 4 dígitos hexadecimales) tiene valores por debajo del hexadecimal 00A0, exceptuando los que corresponden a 0x0024 ($), 0x0040 (@), 0x0060 (').
   (Los caracteres por debajo de 0x00A0 contienen los típicos caracteres de control de ASCII, así como los caracteres básicos del estándar C).

\( \bullet \) Tampoco están permitidos los valores cuya parte "corta" está en el rango que va de hexadecimal D800 a hexadecimal DFFF. (Es un rango de valores reservados por Unicode).

   Por otro lado, tenemos que:

\( \bullet \) Los $UCN$s pueden aparecer en identificadores, constantes de caracter y constantes de cadena, para especificar caracteres que no están en el conjunto básico de caracteres.

¿Qué diferencia hay entre \x00A0 y \u00A0?  ???

   La diferencia es que \u00A0 es una forma de introducir caracteres en cualquier parte de nuestro programa, con código Unicode hexadecimal 00A0, mientras que \x00A0 sólo puede usarse como código de escape dentro de una constante caracter o de cadena: '\x00A0'  "Hello: \x00A0 !!!".

   En particular, dentro de una constante de caracter o de cadena, puede aparecer también la forma \uhhh: '\u00A0'.

   Sin embargo, no tenemos que confundirnos con este asunto: al escribir  '\u00A0' es lo mismo que si escribiéramos el caracter Unicode cuyo código es 00A0 y lo ponemos entre comillas. No es esa una manera de indicarle a C un valor hexadecimal de una constante.

Más concretamente:

   Se espera que el programador nunca tenga que escribir nombres universales de caracter como \uhhhh ó \UHHHHhhhh, sino que el editor de texto que utiliza ha de ser capaz de mostrarle los caracteres Unicode correspondiente en forma correcta (visualmente hablando). Internamente dicho editor de texto ha de ser quien maneje la conversión y escriba estos nombres universales de caracter en el formato \u ó \U que corresponda.
   Aún así, si el programador (nosotros) escribe esos nombres universales de caracter, todo tendría  que funcionar bien.

   ¿Se puede asegurar que un caracter dado tiene código hexadecimal idéntico al que tiene asignado en Unicode?
   Para preguntas como esa se tiene la macro __STDC_ISO_10646__. Si está definida, se pueden asegurar algunas cosas:

\( \bullet \) Todo caracter del conjunto requerido de Unicode cuando se guarda en un objeto de tipo wchar_t, tendrá un valor entero igual a la parte "corta" del identificador de dicho caracter.

   (Esto servirá para representar cualquier caracter Unicode con códigos por debajo de 65535).

\( \bullet \) Si se usa alguna otra codificación distinta a Unicode, la macro __STDC_ISO_10646__ no ha de estar presente.

   Como ejemplo, analicemos algunas situaciones (ejemplos extraídos de "The new C standard"):

\( \bullet \) ¿Son iguales L'\u00A3' y L'\x00A3'? La 1er constante tiene código Unicode 0x00A3. Pero el entorno de traducción no "ve" el código, sino el caracter propiamente dicho, y entonces (si no se está usando Unicode allí) puede estar codificado con otro valor numérico. Así, la 2da constante puede indicar un valor distinto. Mas, ambas serán iguales si __STDC_ISO_10646__ está definida.

\( \bullet \) ¿Son iguales L'\u00A3' y L'\( \textsf{\pounds} \)'? Sí, siempre, pues el caracter \( \textsf{\pounds} \) que aparece en la 2da constante... tiene código Unicode 0x00A3, y eso es lo que se ha escrito del lado izquierdo: el caracter cuyo código Unicode es 0x00A3.

\( \bullet \) ¿Son iguales L'\x00A3' y (wchar_t) 0x00A3? Ciertamente sí, ya que ahora en la 1er constante hemos puesto un caracter hexadecimal, cuyo código ha de ser ese mismo número hexadecimal. Dado que se trata de una constante de caracter ancho, su tipo es wchar_t, y entonces todo funciona bien cuando se convierte el número 0x00A3 del lado derecho al tipo wchar_t.

\( \bullet \) ¿Son iguales L'\x00A3' y L'\( \textsf{\pounds} \)' ? Del lado izquierdo ahora tenemos simplemente el valor hexadecimal 0x00A3, mientras que del lado derecho tenemos el número asociado al código del caracter \( \textsf{\pounds} \). Si la codificación no es Unicode, dichos valores podrían diferir. En cambio, son coincidentes si la macro __STDC_ISO_10646__ está definida.

\( \bullet \) ¿Son iguales L'\( \textsf{\pounds} \)' y (wchar_t) 0x00A3? De nuevo, esto depende del valor que le toque al caracter \( \pounds \) en la codificación utilizada. Coincidirán si la macro __STDC_ISO_10646__ está definida.



(En el siguiente post continuaremos con un tema que va directo a continuación de este: Constantes de cadena).



   Sólo una cosa más: ¡me cansé!  :'( ¡Esto de los caracteres en C es durísimo!  :banghead: :banghead: :banghead:



Organización

Comentarios y Consultas

08 Septiembre, 2013, 08:59 am
Respuesta #36

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
36. Caracteres en el lenguaje C (IV): Constantes de Cadena. Algunas reflexiones

Constantes de cadena.

\( \bullet \)   Una cadena de caracteres literal es una secuencia de cero o más caracteres multibyte encerrados entre comillas dobles "":

"xyz"
"Hello"
"@!=3"


\( \bullet \)   Cada elemento de la secuencia de la cadena es una constante de caracter, y sigue las mismas reglas de formación que antes se explicaron, incluyendo secuencias de escape, octales, hexadecimales, etc:
    "Ejemplo hexa: \xAA \t\t Todo bien?\n".

\( \bullet \)   Sin embargo, ahora el caracter comilla simple (') puede indicarse escribiendo tanto la secuencia de escape \' como la comilla por sí sola: '. Por otro lado, para indicar el caracter comilla doble, es obligatorio usar secuencia de escape: \.

   Por su parte:

\( \bullet \)   Las cadenas de caracteres anchos son secuencias de caracteres anchos, que se indican encerradas entre comillas dobles, y además tienen antepuesto el prefijo L:

L"\( \kappa\alpha\rho\alpha\kappa\tau\epsilon\rho\epsilon\sigma \) anchos"

¿Qué ocurre cuando se intercalan comentarios?

   En C hay dos maneras de intercalar comentarios:

(1) /* Comentario multilínea estilo C de K&R */
(2) // Comentario hasta fin de línea estilo C++

   ¿Qué pasa cuando ponemos esos símbolos en una cadena de caracteres?
   ¿Se confunde con los comentarios, o sale la cadena ilesa?
   En esos casos, es prioritaria la interpretación como cadena de caracteres.
   Así por ejemplo:

"A ver si esto /* funciona o... */ o no funciona"

equivale a la cadena de caracteres tal cual se ve ahí: A ver si esto /* funciona o... */ no funciona.

   En cambio:

A ver si esto /* funciona o... */ no funciona

sería interpretado como: A ver si esto   no funciona.

   Así que los comentarios no se han activado dentro de la constante literal de cadena.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Recapitulación. Reflexiones

   Los 3 posts anteriores han ido creciendo en dureza técnica, y para mí ha sido muy agotador investigar y explicar los detalles pertinentes. Tratemos de hacer un breve resumen de lo que allí desarrollamos, a fin de suavizar el tratamiento sobre este asunto.

\( \bullet \)   En la parte (I) de Caracteres en el lenguaje C hicimos un rápido recorrido histórico sobre los conjuntos de caracteres que hubo en el mundo hasta la fecha, y mencionamos la famosa tabla ASCII (que muchos se saben ya de memoria), las limitaciones que esa tabla tiene para caracteres internacionales, las soluciones que luego se propusieron a través de estrategias o codificaciones multibyte, y finalmente el advenimiento de Unicode, que es la norma ideal que todo sistema o programa informático tiene que adoptar.

\( \bullet \)   En la parte (II) nos introdujimos en los tecnicismos que el estándar C hace sobre los caracteres. Vimos allí que todo el tiempo hay una separación intencional entre el lugar donde los programas se escriben (entorno de traducción) y el lugar donde se lo piensa llevar ya compilado para ejecutarse (entorno de ejecución).

\( \bullet \)   Para cada uno de esos dos entornos se definen respectivos subconjuntos de caracteres: básicos y extendidos. Los básicos del entorno de traducción son los mismos para el entorno de ejecución. Así ambos entornos tienen un subconjunto de caracteres en común. El entorno de ejecución tiene al menos unos cuantos caracteres de control más. En particular, el entorno de ejecución contiene al caracter nulo, que se usa para indicar el fin de las cadenas de caracteres en C, y es por eso tan importante que figure allí.

\( \bullet \)   Se estudiaron los trigrafos: unas 9 secuencias específicas de 3 caracteres básicos que se reemplazan en etapa de compilación por 9 caracteres básicos específicos, que en algunas regiones del mundo puede que no estén debidamente disponibles.

\( \bullet \)   Se discutió el concepto de codificación: un número entero no negativo que se le asocia a cada caracter (siendo un caracter un objeto abstracto, un dibujito que vive en el imaginario colectivo).

\( \bullet \)   Explicamos el funcionamiento de los caracteres multibyte, unas secuencias de varios bytes que permiten representar mediante cambios de estado a muchos otros caracteres que no serían accesibles si sólo se usara 1 byte. Comentamos el hecho de que bien pueden ellos formar parte de un programa fuente, así como del programa ejecutable ya compilado. En cada caso la situación es distinta, como siempre. Sobretodo, en el programa fuente pueden introducirse caracteres multibyte desde el editor de texto utilizado, en el idioma del programador, y el método utilizado es fijo. En cambio, en el entorno de ejecución puede cambiar varias veces la "configuración local" dando lugar a varias posibilidades de codificación multibyte diferentes.

\( \bullet \)   En las partes (III) y (IV) nos zambullimos en las constantes de caracter y de cadena, y en los valores numéricos de las codificaciones asociadas. Aparecieron dos tipos de caracteres ahora: los "normales" (que pueden representarse con 1 solo byte), y los "anchos" (caracteres extendidos del entorno de ejecución que abarcan tanto a los normales como a aquellos que no caben en 1 byte). Los caracteres "anchos" no son multibyte, sino más bien caracteres con una codificación determinada, en un sistema que requiere varios bytes (en cantidad fija) para representarse (por ejemplo, Unicode).

\( \bullet \)   Los caracteres "normales" se representan encerrados entre comillas simples, y los caracteres "anchos" se representan del mismo modo, aunque precedidos por el prefijo L.

\( \bullet \)   Aprendimos que las constantes de caracter representan números enteros, ya sea de tipo int (para los caracteres "normales") o de tipo wchar_t (para los caracteres "anchos").

\( \bullet \)   Sin embargo, no se usan todos los valores de un int para representar constantes de caracteres, sino sólo los que caben en un char.

\( \bullet \)   Por otro lado, se pueden usar constantes de caracter octales (hasta 3 dígitos) y hexadecimales (sin límite en el número de dígitos) para indicar caracteres con códigos que quepan en un unsigned char.

\( \bullet \)   Dado que a veces char no es lo mismo que unsigned char (o sea, char puede ser signed char), un valor octal o hexadecimal muy grande puede volverse negativo.

\( \bullet \)   Los valores de los caracteres "ancho" caben en wchar_t.

\( \bullet \)   Estudiamos todas las secuencias de escape admitidas por el estándar C, que generan caracteres de uso reservado (comillas simples y dobles, contrabarra, interrogación) caracteres de control, códigos octales o hexadecimales.

\( \bullet \)   Investigamos la relación entre las constantes de caracteres "normales" y caracteres "anchos" con ayuda de la macro __STDC_MB_MIGHT_NEQ_WC__.

\( \bullet \)   Introdujimos los nombres universales de caracter (UCN), usados para denotar en hexadecimal los correspondientes caracteres de Unicode. Estos nombres utilizan dos variantes, una forma corta con \u y una forma larga con \U. Debe entenderse que en donde aparece una de estas construcciones sintácticas, en realidad hay un caracter Unicode con el código numérico correspondiente.

\( \bullet \)   Estudiamos entonces el soporte de C99 para caracteres unicode, a través de estos UCN.

\( \bullet \)   Dado que nada obliga a que una implementación dada codifique sus caracteres Unicode con el número de código que le otorgó el consorcio Unicode, nos hicimos la pregunta de si podemos confiar o no en esos números. La respuesta la brinda la macro __STDC_ISO_10646__. Si está presente, es que SÍ, y si no, es que NO.

\( \bullet \)   Por último hemos desarrollado lo poco que faltaba: las constantes literales de cadena, tanto las de caracteres "normales" como de caracteres "anchos".

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Ustedes se preguntarán por qué me he empecinado  :banghead: :banghead: :banghead: en estudiar con tanto detalle este asunto de los caracteres en C. Hay varios motivos para ello:

   (1) Un programa en C tiene varias fases de compilación, las cuales implican transformaciones de caracteres del código fuente en quién sabe qué cosas.

   (2) Un programa en C tiene reglas sintácticas que se dictan en función de la codificación de caracteres empleada en el entorno de traducción. En particular, podremos abordar con total rigor las reglas conque se escribe un programa correcto en C.

   (3) El tema de los caracteres es muy complejo, y es bueno saber que existen cosas como estas: caracteres básicos, extendidos, multibyte, caracteres anchos, nombres universales. Y además tener una idea de las diferencias y relaciones entre esos conceptos.

   (4) En las aplicaciones modernas hay que tener en cuenta Unicode como asunto esencial, y familiarizarse con cosas como wchar_t, <wchar.h>.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Nosotros haremos aplicaciones en la consola, que se conforman con ver los acentos y eñes del idioma castellano.
   Así que los caracteres "normales" serán suficientes, y no habrá que lidiar con Unicode ni posiblemente tampoco los caracteres multibyte.

   Nos basta con usar caracteres de 1 byte, y la codificación la elegimos en el sistema mediante un cambio de la página de códigos, pasando a 28591 o bien 1252. Usaríamos la sentencia:

system("CHCP 28591");

   Y listo.

   Pero entonces la primer excusa de todas: la de la sintaxis en C, es la que más me tenía preocupado.

   Otro asunto interesante es el que abordaremos en el siguiente post: ¿cómo funcionan los caracteres de control cuando se intenta que actúen sobre la salida estándar (o sea, el monitor de nuestra computadora).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

08 Septiembre, 2013, 10:42 pm
Respuesta #37

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
37. Caracteres en el lenguaje C (V): Visualizando caracteres de control

   Estamos acostumbrados a que en la pantalla vemos actuar caracteres de control que van acomodando el texto de un modo u otro.
   Nuestros programas en C funcionan en modo de consola, así que las capacidades de muestreo son muy limitadas.

   Hemos visto que en C se reconoce la existencia de algunos caracteres de control en el entorno de ejecución:

SPACE,  TAB,  VERTICAL TAB,  FORM FEED  (estos también están en el entorno de traducción)
BELL, BACKSPACE, FORMFEED, NEWLINE, CARRIAGE RETURN.

   Todos ellos se consideran caracteres básicos (del entorno de ejecución).

   El SPACE no es propiamente un caracter de control, sino un caracter cuyo dibujo no tiene nada que mostrar. O sea que, si la consola o la impresora es capaz de mostrar texto, el SPACE siempre se muestra como un caracter más, aunque en blanco.

   Hemos visto que el estándar C básicamente nos dice que cualquier cosa es posible a la hora de codificar caracteres. Desde el punto de vista teórico no podemos estar seguros de que cada caracter tenga asociado el numerito que nosotros queremos.

   Sin embargo, si la macro __STDC_ISO_10646__ está definida, podemos asegurar que los códigos ASCII de 7 bits tienen los valores que conocemos de toda la vida, y en particular el caracter SPACE tiene asociado el código número 32 (en hexa: 0x20).
   También se puede decir en este caso que los caracteres de control tienen códigos cuyos valores están entre 1 y 31.

   En cuanto al código 0, siempre se reserva para el obligatorio caracter nulo.

   ¿Y el caracter nulo, es un caracter de control? No sé responder con precisión. Desde el punto de vista de ASCII y Unicode sí es caracter de control.
   Desde el punto de vista del lenguaje C es un caracter no imprimible, pero más aún, se usa como marcador de fin de cadena de caracteres.

   En particular, dada una cadena de caracteres almacenada en memoria, no podemos saber a simple vista cuál es su longitud (como sí ocurre en Pascal), y tenemos que recorrer toda la cadena hasta alcanzar el caracter nulo para poder averiguar su longitud.

¿Y el caracter fin de archivo?

   En la consola de DOS/Windows se usa el caracter con código 26 para marcar que un archivo de texto termina ahí (es el famoso CTRL Z). En Linux se usa CTRL D para ese fin (el código es 4).

En el estándar C no se toma en cuenta al FIN-DE-ARCHIVO como un caracter propiamente dicho, sino como una forma, de las tantas posibles, que un sistema tendría para indicar que se ha llegado al final de un archivo de texto. Así, esta marca tiene asociado un valor entero, que es la constante EOF definida en <stdio.h>.

   El valor de EOF es un entero negativo de int.
   ¿Puede coincidir este valor con el código de algún caracter del entorno de ejecución?
   Si char equivale a signed char, esa situación no está impedida por el estándar.
   En cambio, si char es unsigned char, entonces EOF nunca coincidirá con el código de un caracter del entorno de ejecución.

   Cuando estamos ante un archivo de texto con caracteres anchos, se recurre a "otro" indicador de fin de archivo, que el lenguaje indica con la macro WEOF, que está definida en <wchar.h> y <wctype.h>.
   Podemos asegurar que WEOF es un número entero de tipo wint_t que no coincide con el código de ningún caracter ancho codificable en la implementación (sin importar la "configuración local" que esté activa en el programa).

   ¿Pueden diferir los valores de EOF y WEOF? Rotundamente, sí.

(Más delirios acerca de EOF y WEOF)

¿Podemos enviar nosotros un "caracter fin de archivo" a la consola o a un archivo de texto?

   Esto querría decir que estamos intentando enviar EOF o WEOF.
   En principio no tiene sentido querer hacer esto, porque son las mismas rutinas de manejo de entrada/salida las que se encargan de poner una marca de fin de archivo cuando se termina una operación de escritura.
   Por otro lado, enviar un caracter fin de archivo a la consola puede que no tenga efecto alguno, porque la consola "no tiene fin".

   En el caso de EOF, estaríamos enviando un número entero negativo a un archivo de caracteres. El resultado de esta operación depende de las operaciones o funciones que se utilicen para tan malicioso fin. En general dará resultados indefinidos.
   Si el código de EOF coincide con el de un caracter extendido, entonces puede que alguna vez enviemos dicho caracter "de casualidad". De nuevo, el resultado es indefinido.

   En el caso de WEOF, es claro según la norma C99 que se trata de un no-caracter. Así que no habrá de funcionar.
   Puede quedar la duda de lo que ocurre cuando wchar_t y wint_t tienen el mismo rango de valores.
   Allí, la información que se envía a un archivo de texto de caracteres anchos va en forma de paquetes de bytes de tamaño uniforme, y podemos colar cualquier número de tipo wchar_t que no corresponda a un caracter ancho.
   No sé qué ocurre en estos casos, y les invito a que no se interesen por saberlo. Lo más probable es que se obtengan resultados indefinidos o dependientes de la implementación.
[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   No se asegura en el estándar C la existencia de un dispositivo donde se visualicen caracteres (una consola en pantalla). Pero de haberlo, se aplican las siguientes consideraciones:

\( \bullet \)   La posición activa se define como la posición en el dispositivo de visualización dónde el siguiente caracter de salida va a aparecer. (La manera exacta en que esto ocurre es tal como se define para la función fputc() de la biblioteca estándar <stdio.h>).

   Según lo que nosotros acostumbramos, esto se refiere a la posición actual del cursor que titila en la consola marcando la posición del "próximo" caracter que se va a mostrar en pantalla.

   Si se refiere a un dispositivo diferente, por ejemplo una impresora matricial (de esas antiguas que aún hay en algunas oficinas, y que van moviendo un cabezal de impresión de izquierda a derecha, pinchando con unas miniagujas la cinta de impresión para estampar así los caracteres en el papel), se refiere a la posición del cabezal de impresión, que siempre queda "preparado" en la posición del próximo caracter.

   ¿Es posible tener un dispositivo de visualización diferente de la consola, de forma natural?
   Esto suele ser sencillo, pero depende de las posibilidades que nos otorgue nuestro sistema operativo. No depende, entonces, del lenguaje C. Por ejemplo, si queremos enviar el contenido de un archivo de texto a una impresora conectada al puerto paralelo, haríamos un redireccionamiento de la salida estándar, así:

TYPE ARCHIVO.TXT > LPT1

   Si bien el plan que estoy llevando en estas notas es tal que se posterga el estudio de funciones para mucho más adelante, de todos modos mostraré aquí el comportamiento esperado de la función fputc() descripto por el estándar C99:

Synopsis
1 #include <stdio.h>
    int fputc(int c, FILE *stream);

   La función fputc() espera dos argumentos: el primero es un número entero c, y el segundo es un archivo stream.
   Para nosotros, el archivo en cuestión es stdout, es decir, la salida estándar o consola o pantalla o ...

   Lo que hace es recibir un (código de un) caracter en el parámetro c.
   Envía el caracter hacia stream (en nuestro caso a la pantalla).
   Y retorna como resultado el mismo caracter c que se ha enviado, o bien EOF en caso de que haya habido algún problema.

Citar

Description
2 The fputc function writes the character speci?ed by c (converted to an unsigned
char
) to the output stream pointed to by stream, at the position indicated by the
associated ?le position indicator for the stream (if de?ned), and advances the indicator
appropriately. If the ?le cannot support positioning requests, or if the stream was opened
with append mode, the character is appended to the output stream.


   Aquí explica lo que ocurre cuando se especifica el envío de un caracter.
   Primero se convierte el caracter c al tipo unsigned char.
      (¿Recuerdan que en el post anterior el caracter era convertido a unsigned char, luego a char, y luego a int? Y ahora de nuevo se convierte a unsigned char...)  :banghead: :banghead:

   Los caracteres c con código positivo no verán alterado su valor, pero los que tengan código negativo sí resultarán alterados por esta conversión.
   Sin embargo, es importante notar que los caracteres básicos no sufren conversiones extrañas en esta operación de salida, ya que ellos siempre tienen, acorde al estándar, un código numérico positivo (que cabía en un char).

   Luego este valor así convertido se envía hacia stream (en nuestro caso, esto es stdout, la pantalla), y se lo coloca justo donde manda el indicador de posición del archivo que, en nuestro caso, está dado por la posición activa (del cursor) en la salida estándar (pantalla).

   Después de esto, se avanza el indicador de posición (en nuestro caso: la posición activa del cursor) "apropiadamente".
   La palabra "apropiadamente" en un documento de estandarización sólo puede ser indicio de una cosa: la gente del comité se hartó hasta de su propia normativa.  :banghead:

   En realidad hay muchas sutilezas envueltas en esto. La posición activa puede ir hacia la derecha o hacia la izquierda, arriba o abajo, según las características del idioma que se esté usando para mostrar la información en pantalla. Hay lenguajes en que la escritura va hacia la derecha (idiomas occidentales, latín, griego, inglés, castellano), otros en que va hacia la izquierda (hebreo, árabe), otros en que va hacia abajo (chino), otros en que va en zig-zag (¿egipcio?), y otros que van "hacia donde mejor le quede al tipo que está tallando la piedra ese día" (¿mayas?).
      Es importante tener la mente abierta en estas cuestiones. No todo el mundo va de izquierda a derecha y de arriba a abajo como nosotros.

   Si el stream (para nosotros sigue siendo stdout, la pantalla) no admite que se le obligue a poner caracteres en la posición que al programador más le gusta en ese momento, entonces el nuevo caracter se añade al final de los anteriores ya añadidos.

   Esto último depende enteramente de las convenciones conque funciona la consola.
La consola es un programa como cualquier otro.
   Nosotros conocemos la que viene por defecto en Windows: CMD.
   Pero en posts anteriores los insté a usar otra: ConEmu.

   La posibilidad de retroceder y subir a lo largo de las líneas de texto que muestra la consola, es algo que está o no está, según la consola que estemos usando.
   Tanto CMD como ConEmu funcionan de modo similar a este respecto: no admiten que el programador o el usuario elija la línea o posición donde va a escribir lo que sea que esté deseando escribir.
   Al contrario, la consola va avanzando el cursor hacia la derecha, dejando activa la futura posición, y cuando se acaba el espacio en un renglón, avanza hacia el siguiente desde la izquierda.

Este comportamiento no puede ser predicho por el estándar C.  :o
   Al contrario, no es más que una de las tantas posibilidades que un programa de consola puede elegir para exhibir y manejar la información.

   Es pues de gran importancia que entendamos que las limitaciones que encontramos al visualizar información o interactuar con la consola, son antes bien limitaciones de la consola misma, y que el lenguaje C nada decide allí, ni se inmiscuye en lo más mínimo.
   Resulta así que muchos elementos de la consola quedarán fuera de nuestro control, si es que pretendemos controlarlos desde un programa escrito en C.

Un último comentario: las consolas típicas parecen funcionar como un archivo de estos que "no permiten que se les exija posicionar el cursor en tal o cual lugar". En tal caso, la norma establece que los nuevos caracteres se van agregando con una operación de tipo "append", o sea, se los va agregando "al final", detrás de todos los caracteres que se hayan enviado previamente.

   Esto da como resutado, en la consola, lo que estamos acostumbrados a ver: que la nueva información se va agregando indefectiblemente, y cuando no hay más espacio, la pantalla o ventana de visualización desplaza el contenido más antiguo hacia arriba, haciendo espacio para que vengan los nuevos caracteres.
Citar
Returns
3 The fputc function returns the character written. If a write error occurs, the error
indicator for the stream is set and fputc returns EOF.


   Esta última especificación ya no nos concierne a nosotros, porque sólo intentamos entender lo que ocurre con los caracteres en pantalla.
   La comentamos sólo por completitud en la exposición: la función fputc() devuelve un valor int que es exactamente el mismo caracter que se ha enviado.
   En caso de error (cualquier evento en el Universo que haya impedido que el programa coloque el añorado caracter en la posición activa del dispositivo de salida estándar), la función fputc() retornará en su lugar el valor EOF.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El análisis anterior nos muestra ya un detalle importante:

Las operaciones de entrada/salida de caracteres retornan siempre valores que son enteros no negativos, a menos que haya habido un error, y es en ese caso, y sólo en ese caso, que se retorna un valor negativo (concretamente, la constante EOF).

   Esto puede resultar algo confuso, a la luz de lo analizado en posts anteriores. Por ejemplo, en la implementación que estamos usando nosotros, compilador GCC 4.8, con librerías MinGW, bajo el sistema operativo Windows, con procesadores tipo Pentium, y con opción de compilación -std=c99, obtendríamos que char es igual a signed char, con bytes de 8 bits.

   Eso es lo que yo obtengo al menos en mi sistema. Y si ustedes obtienen lo mismo, que es lo más probable, entonces el siguiente programita les dará los mismos resultados que a mí:

#include <stdio.h>
int main(void) {
   int c, out;
   out = fputc(c = '\xfe', stdout);
   printf("\n%d %d\n", c, out);

   getchar();
   return 0;
}

   Ese programa declara dos variables c, out, de tipo int, que usaremos para alojar códigos de caracteres, o lo que se le parezca...
   En la variable c alojamos la constante de caracter '\xFE', cuyo código hexadecimal FE corresponde al número decimal 254.
   Como (para nuestro sistema) CHAR_MAX es 127, deducimos que '\xFE' no corresponde a un caracter básico.
   Así que estamos arrojados a la suerte, a ver si eso representa o no un caracter de verdad en nuestro sistema. Por suerte sí lo es...

   Sin embargo, lo interesante aquí es que, si bien FE es en hexa el número positivo 254, resulta que la constante de caracter '\xFE', que previamente ha de tener un valor en el rango de char, que en nuestro sistema está siendo igual a signed char, obliga a que 254 se trastoque internamente a nivel de bits, resultando un número entero negativo. Para nosotros será el -2.
   Así que c termina alojando el valor negativo -2.

   Por otro parte, al pasarle ese valor como argumento a la función fputc(), con la intención de enviar el caracter '\xFE' a la salida estándar stdout, sabemos que previamente habrá de ocurrir una conversión previa a unsigned char.
   No tengo muy claro lo que el estándar dice respecto de conversiones del tipo:

unsigned char --> signed char --> unsigned char

   ¿Se recupera el valor original? ¿O bien se produce un comportamiento indefinido?

   Como sea, es claro que al convertir de nuevo el -2 a unsigned char, ya no tiene chances de seguir siendo el mismo número, pues tiene que convertirse a un número entero positivo.
   Lo que obtengo yo es que se obtiene otra vez el valor original de 254.

   A su vez, éste valor entero es el devuelto por fputc(). ¿Por qué? Porque lo que devuelve la función es el caracter "escrito", no el que "recibió como argumento".

   El resultado de la operación fputc(c, stdout) se alojó en la variable out.
   Finalmente, el programa muestra el caracter enviado (eso lo hace fputc(..., stdout)), y luego muestra el valor de c y el valor de out.
   Vemos los famosos números -2 y 254 allí.  ::)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

\( \bullet \)   La dirección en que avanza el texto en el dispositivo de visualización depende de la "configuración local".

   Esto se refiere, una vez más, al modo en que la consola utilizada es capaz de interpretar los diferentes idiomas cada vez que se le solicite.

\( \bullet \)   Si la posición activa (el cursor) está al final de una línea (si es que hay tal cosa en la consola utilizada), lo que ocurre luego queda indefinido.

¿Indefinido para quién? Para el estándar C. Es decir, el lenguaje C se desentiende de este problema, y deja que la consola que elegimos usar se las arregle, tomando la decisión que mejor le parezca: abrir espacio para una nueva línea, borrar la pantalla, mover todo el contenido preexistente hacia arriba, emitir un pitido, lanzar el programa Skynet para que los Terminators dominen el mundo, etc.

\( \bullet \)   Las secuencias de escape alfabéticas que representan caracteres no gráficos en el entorno de ejecución, tienen el comportamiento que se especifique en los ítems que siguen...

(En realidad el estándar C admite que hay gran variedad en los dispositivos de visualización, y que por lo tanto debe dejar muchos cabos sueltos, es decir, detalles que no pueden arreglarse desde un programa en C que siga estrictamente el estándar.)

\a (ALERT): Produce una alerta audible o visible, sin cambiar la posición activa (o sea, hace un ruido molesto o da una señal visual, pero no mueve el cursor).

\b (BAKSPACE): Mueve la posición activa a la posición previa de la línea actual en que se encuentra el dispositivo de visualización. Si la posición activa está justo al principio de una línea, el comportamiento queda sin especificar.
(O sea, retrocede el cursor una posición, aunque no necesariamente borra lo que había en la posición que recién se abandonó, ni tampoco se puede asegurar lo que ocurre cuando el cursor está al principio de una línea: ¿se queda estancado ahí, o sube a la línea anterior, o hace explotar dinamita o qué?)

\f (FORM FEED): Mueve la posición activa (el cursor) al inicio de la siguiente página "lógica" del dispositivo de salida.
(En pantalla, esto puede significar varias cosas: o bien pasa nada, o bien se salta al comienzo de la siguiente línea, o bien se borra la pantalla y se comienza desde el principio, etc. No hay modo de saber lo que hará una consola dada al recibir un caracter FORM FEED. Si se trata de dispositivos de impresión, puede interpretarse como "terminar de imprimir esta hoja, escupirla, y atrapar una nueva hoja para seguir imprimiendo".)

\n (NEW LINE): Mueve la posición activa (el cursor) al inicio de la siguiente línea en el dispositivo de visualización.
(El estándar no dice nada sobre lo que ocurre a las líneas anteriores, o si se borra la pantalla, ni tampoco qué ocurre en dispositivos que tienen una sola línea.)

   Hay dispositivos o sistemas en los que el caracter nueva línea produce en realidad varios caracteres que marcan el fin de una línea. Hay otros dispositivos que no tienen tal caracter fin de línea porque las líneas ya tienen un tamaño predeterminado.

   De todas maneras, poco importa el modo en que este caracter "se almacene" en el dispositivo de salida, puesto que lo que estamos discutiendo acá es el efecto de los caracteres de control en el dispositivo de salida.
   Lo concreto que podemos decir es que se inicia una nueva línea, y el cursor se posiciona al principio de ella.

   Lo demás no nos interesa demasiado, puesto que es poco común la pretensión de querer "leer" información desde el dispositivo de salida estándar.

\r (CARRIAGE RETURN): Mueve la posición activa (el cursor) al principio de la línea actual.
(Este movimiento de "retroceso" hasta el principio no borra los caracteres de la línea.)

   Tango en el caso de \b como \r, el estándar no dice nada acerca de lo que ocurre cuando se intenta sobre posiciones que previamente estaban ocupadas por otros caracteres. ¿Se borran, se superponen, se quema el monitor? El estándar no lo sabe.

\t (HORIZONTAL TAB): Mueve la posición activa (el cursor) al siguiente marcador de tabulación en la línea actual. Además, si aparece un TAB horizontal tras haber pasado ya la última marca de tabulación disponible en la línea actual, el comportamiento queda indefinido.
   (¿Alguien ve los marcardores de tabulación? Porque yo no. Como sea, lo típico es suponer que cada 8 espacios haya un "imaginario" marcador de tabulación. Así, un caracter TAB "salta" a la marca más próxima en la presente línea del dispositivo de visualización.)

   Las marcas de tabulación no las elige el estándar C, sino que las elige el sistema subyacente, o el programa de consola que hayamos instalado. No hay garantías sobre la posición que estas marcas tienen, ni si tan siquiera existen. No hay manera de averiguar "desde" C esta información (al menos no en una forma directa).

   Por esa razón, doy aquí recomendaciones a la hora de mostrar información en pantalla:

   (i)   O bien usar sólo tabuladores para alinear información en pantalla (en un programa sencillo), y confiar el control de las marcas de tabulación al sistema operativo,
   (ii)  o bien usar sólo opciones de formato específicas de las librerías del lenguaje C, y ser uno quien mantenga el control permanente de cómo es exhibida la información en pantalla.
   (iii) No mezclar ambos tipos de "formato" de la información enviada al dispositivo de visualización.

   Por otra parte, el estándar C no asume nada acerca de cuántas marcas de tabulación hay, ni cómo están distribuidas en una línea dada.
   De modo que si se llegó a la última marca, un TAB después de ella puede que no tenga significado alguno, y el estándar C se lava las manos sobre qué hacer en una situación semejante. La consola deberá elegir la acción a tomar (no hacer nada, escribir un espacio en blanco, volver a la última marca de tabulación, saltar a la primer marca de una nueva línea, lanzar un cohete a la Luna, etc.)

   Puede parecer extraño esta falta de consenso, pero pensemos en dos dispositivos tan diferentes como una pantalla y una impresora. ¿Qué ha de hacer la impresora cuando se le acaban las marcas de tabulación en una línea? ¿Avanza a la siguiente línea, o deja quieto el cabezal de impresión?

\v (VERTICAL TAB): Mueve la posición activa (el cursor) a la posición inicial de la siguiente marca de tabulación vertical. Si ya se ha sobrepasado la marca de la última tabulación vertical disponible, el comportamiento queda indefinido.

   Este tipo de control puede tener distintos significados, según el dispositivo. En las consolas corrientes, puede significar que se mueve el cursor a la siguiente línea, pero dejando la posición relativa del cursor en el mismo lugar. (O sea, baja un renglón).
   En una impresora, puede significar el salto a una determinada marca preestablecida, de una verdadera tabulación vertical. Es decir, puede que haya posiciones predeterminadas en la hoja a imprimir (quizá cada 10 o 15 líneas, vaya uno a saber), a los cuales se "salta" en forma automática para separar uniformemente secciones distintos de un cierto tipo de impresión.

   En cualquier caso, el estándar C no especifica lo que ocurre exactamente. Sólo podemos asegurar que el "avance" hacia nuevas líneas, dado por \n, funciona en la misma "dirección" que el "avance" realizado por la tabulación vertical \v. (Comunmente, van ambos "de arriba hacia abajo".)

   Implícitamente se involucra aquí el concepto de página actual. Los movimientos de salto se producen en una misma "página".
   Sin embargo, puede que no haya marcas de tabulación alguna, y así la consola puede terminar ignorando este tipo de caracter de control.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   En general, es común considerar al dispositivo de visualización (la consola, la pantalla), como a cualquier archivo de texto, al cual uno le "escupe" caracteres, saltos de línea, etc.
   Sin embargo, hay diferencias semánticas importantes, como hemos visto, ya que en un archivo corriente tan sólo almacenamos bytes correspondientes a los caracteres enviados, mientras que en la pantalla ocurren eventos de diversa índole, según los caracteres que le enviemos.

   Ante el análisis previamente efectuado, podemos concluir que el efecto de los tabuladores \t y \v es demasiado dependiente del entorno de ejecución, y no podemos controlar desde C con exactitud lo que ocurre.
   En menor grado, tenemos problemas similares también con el caracter de retroceso \b.

   Así que mi opinión es que estos caracteres no debieran usarse nunca mientras estemos usando funciones más especializadas para configurar el modo de visualización de los datos. En cambio, si planeamos usarlos, entonces no debemos mezclarlos con funciones de "formateo de datos" propias de C, en un mismo programa.

   Es decir, conviene hacer un programa "puramente de consola" o "puramente de C". Pero mezclar los dos sería una práctica "sucia", desordenada.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

En el siguiente post pondremos a prueba todo esto en programas concretos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

25 Septiembre, 2013, 10:27 pm
Respuesta #38

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
38. Caracteres en el lenguaje C (VI): Visualizando caracteres de control (cont.)

   Vamos a confeccionar un pequeño programa que se encargará de mostrar, de una vez por todas, cómo actúan los caracteres de control enviados por las secuencias de escape alfabéticas predefinidas en C. A ver si hacen lo que prometen.

ctrlchars.c

#include <stdio.h>
int main(void) {   
   printf(
      "\\a ALERT:\n"
      "    Hay pitido entre 4 y 5?\n"
      "01234\a56789 \n\n"
      "\\b BACKSPACE:\n"
      "    Hay retroceso despues de 9?\n"
      "    (si funciona, la X tacha al 9 que no se ve)\n"
      "0123456789\bX . \n\n"
      "\\f FORM FEED:\n"
      "    Hay avance de formulario entre 4 y 5? O que?\n"
      "01234\f56789 \n\n"
      "\\n NEW LINE:\n"
      "    Hay nueva linea entre 4 y 5?\n"
      "01234\n56789 \n\n"
      "\\r CARRIAGE RETURN:\n"
      "    Hay retorno de carro despues del 9?\n"
      "    (si funciona, las XX tachan 01 a principio de linea)\n"
      "0123456789\rXX\n\n"
      "\\t HORIZONTAL TAB:\n"
      "    Hay TAB entre 4 y 5?\n"
      "01234\t56789 \n\n"
      "\\v VERTICAL TAB:\n"
      "    Hay TAB vertical entre 4 y 5?\n"
      "01234\v56789 "
      "\n\n"
   );
   getchar();
   return 0;
}


   Al correr este programa, obtuve el siguiente resultado:



   La conclusión que obtengo sobre mi consola es la siguiente:

\( \bullet \)   He oído un pitido al principio, así que \a parece funcionar. Además, no inserta nada entre los caracteres 4 y 5, lo cual pone en evidencia que no ha habido ningún avance del cursor, tal como especifica la norma.

\( \bullet \)   El retroceso \b funciona bien: retrocede un caracter.

\( \bullet \)   El retroceso \b NO BORRA caracteres cuando retrocede.

\( \bullet \)   El avance de formulario \f imprime en pantalla un caracter de la fuente que usa mi consola. No hace saltos de ninguna índole, no avanza líneas ni pantallas, ni borra la pantalla, ni nada. Sólo muestra un símbolo asociado al caracter de control en cuestión.

\( \bullet \)   El caracter nueva línea \n avanza a la siguiente linea y "retorna el carro" al principio de la línea siguiente.

\( \bullet \)   El retorno de carro \r funciona: retorna al principio de la línea actual.

\( \bullet \)   El tabulador horizonal \t funciona: salta a la siguiente marca de tabulación (cada 8 espacios). Los espacios que se han "saltado" quedan en blanco.

\( \bullet \)   El tabulador vertical \v no funciona: sólo imprime un símbolo asociado al número valor del caracter \v, pero no toma ninguna acción (no hay marcas de tabulación vertical, no hay avance de línea, ni ninguna otra acción relevante).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Podemos ahora preguntarnos qué ocurre en situaciones más extremas.

   ¿Qué pasa si se retrocede al principio de una línea?
   ¿Qué pasa si se retorna el carro de una línea que es tan larga que ha debido continuar debajo?
   ¿Qué ocurre cuando hay muchos saltos de línea, tal que no caben en la pantalla?
   ¿Qué ocurre cuando los saltos de tabulación se combinan con retrocesos?

   Son todas preguntas que quitaron el sueño a Platón, y por las que hubo de escribir tratados extensos que, tristemente, fueron destruidos en la gran quema de la biblioteca de Alejandría.
   Las respuestas a estas incógnitas supremas son respondidas con la ayuda del siguiente programa:

CtrlChrs2.c

#include <stdio.h>
int main(void) {   
   printf(
      "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
      "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
      "Resultado tras poner NEWLINE en profusas cantidades...\n\n\n\n"
      "BACKSPACE al principio de linea:\n\n"
      "....................................................................\n"
      "....................................................................\n"
      "....................................................................\n"
      "\b\b\b\b" "XXX\n\n\n"
      "CARRIAGE RETURN tras una linea larga que se ha continuado debajo.\n\n"
      "00.............................................................."
      "11.............................................................."
      "22.............................................................."
      "\r" "XXXX\n\n\n"
      "BACKSPACE & TAB combinados.\n\n"
      "........@.......@.......@.......@.......@.......@\n"
      "\t@\t@\t@\t@\t@\t@\n"
      "0000\t\b11\t\b\b2222\t\t\t\b\b33......\n"
      "00\t1111\t2222\t\t\t\b\b\b33......\n"
      "\n\n"
   );
   getchar();
   return 0;
}


   El resultado que obtuve es el siguiente:



Las conclusiones que puedo extraer son éstas:

\( \bullet \)   El efecto de enviar varios caracteres nueva línea \n es el de "scrolling" hacia arriba, o sea, las líneas más antiguas se empujan hacia arriba, hasta hacerlas desaparecer.
\( \bullet \)   El efecto de enviar retrocesos \b al principio de línea es: nulo. El cursor queda estancado ahí, y no se mueve a ninguna parte.
\( \bullet \)   El efecto de enviar retorno de carro \r en una línea tan larga que ha debido cortarse y continuarse debajo es: que retrocede al principio de la línea que es visible en este momento en pantalla, y no al inicio lógico de la línea. Por lo tanto: el dispositivo ha insertado automáticamente un fin-de-línea al toparse con el límite máximo de caracteres que una línea admite en pantalla.
\( \bullet \)   El efecto de enviar TABs \t seguidos de retrocesos \b es: la consola "olvidó" que el salto al siguiente marcador de tabulación se produjo por un caracter de tabulación, sino que más bien parece que ha "rellenado con blancos" hasta el siguiente marcador. Así, el retroceso sólo retrocede el cursor una posición hacia la izquierda, en vez de volver al punto en que se hallaba antes antes de emitir el caracter TAB.

   Me refiero a que, cuando presionamos la tecla TAB seguida de BACKSPACE en la consola, comunmente el efecto es que se borra "todo el salto" de tabulación, y no este efecto que hemos obtenido de que "sólo se retrocede 1 posición del cursor en pantalla".

   Como imaginarán, estos hechos me impregnan el alma de profundas reflexiones.  8^)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

26 Septiembre, 2013, 09:54 pm
Respuesta #39

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,332
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
39. Identificadores en C

   Un identificador es, a grandes rasgos, el "nombre de alguna cosa" que el usuario define en un programa.
   No vamos a ver aquí la definición exacta del término identificador. Más bien nos conviene saber que son los nombres que se usan para variables, constantes, tipos de datos, funciones, etiquetas, macros, etc.
   En este post estudiaremos las reglas válidas para conformar un nombre válido de identificador.

   Un caracter \( \alpha \) es válido en un identificador si:

\( \bullet \) \( \alpha \) es uno de los caracteres alfabéticos:
    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
      a b c d e f g h i j k l m n o p q r s t u v w x y z

\( \bullet \) \( \alpha \) es el caracter subrayado:
    _

\( \bullet \) \( \alpha \) es un nombre universal de caracter (ó UCN):
    \uhhhh \UHHHHhhhh

         No son aceptados todos los UCN, sino sólo los que aparecen en el Anexo D del documento estándar C99.
\( \bullet \) \( \alpha \) es un caracter multibyte
         (los caracteres multibyte aceptados no son todos, y cuáles se aceptan o no depende del compilador usado)
\( \bullet \) \( \alpha \)es un dígito:
    0 1 2 3 4 5 6 7 8 9


   Todos los tipos de caracteres nombrados, excepto los últimos, se denominan no dígitos de identificador.

   Las reglas de formación de un identificador son las siguientes:

\( \bullet \) Un identificador es una secuencia de 1 o más caracteres no dígitos de identificador o bien dígitos.
\( \bullet \) El 1er caracter de un identificador no puede ser jamás un dígito ni tampoco un UCN que corresponda a un dígito.

   Ejemplos típicos de identificadores válidos serían los siguientes:

x
f_p32
eStack
arrayofchar
____my_printf_____status16
rr15fk_bb
_1a
_a1
a_1
a1_
_
varFloatInBlock23a


   Ejemplos que usan UCNs:

ret\u0b82x_23_\u0aef\u03c5
\u1f5d\u04bb__\u0380
__3\u00da\u00da


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

UCNs del Anexo D

   La lista siguiente contiene rangos de códigos hexadecimales de caracteres Unicode válidos para caracteres en C, y están clasificados por alfabetos, para mayor claridad.
   Lo importante es la última clasificación, que nos dice qué rangos de caracteres se usan para representar dígitos. Estos caracteres no pueden ser el 1ero en un identificador.

UCN válidos en identificadores

Idiomas latinos
00AA, 00BA, 00C0-00D6, 00D8-00F6, 00F8-01F5, 01FA-0217, 0250-02A8, 1E00-1E9B, 1EA0-1EF9, 207F
Griego:
0386, 0388-038A, 038C, 038E-03A1, 03A3-03CE, 03D0-03D6, 03DA, 03DC, 03DE, 03E0, 03E2-03F3, 1F00-1F15, 1F18-1F1D,
1F20-1F45, 1F48-1F4D, 1F50-1F57, 1F59, 1F5B, 1F5D, 1F5F-1F7D, 1F80-1FB4, 1FB6-1FBC, 1FC2-1FC4, 1FC6-1FCC, 1FD0-1FD3, 1FD6-1FDB, 1FE0-1FEC, 1FF2-1FF4, 1FF6-1FFC
Cyrillic:
0401-040C, 040E-044F, 0451-045C, 045E-0481, 0490-04C4, 04C7-04C8, 04CB-04CC, 04D0-04EB, 04EE-04F5, 04F8-04F9
Armenian:
0531-0556, 0561-0587
Hebrew:
05B0-05B9, 05BB-05BD, 05BF, 05C1-05C2, 05D0-05EA, 05F0-05F2
Arabic:
0621-063A, 0640-0652, 0670-06B7, 06BA-06BE, 06C0-06CE, 06D0-06DC, 06E5-06E8, 06EA-06ED
Devanagari:
0901-0903, 0905-0939, 093E-094D, 0950-0952, 0958-0963
Bengali:
0981-0983, 0985-098C, 098F-0990, 0993-09A8, 09AA-09B0, 09B2, 09B6-09B9, 09BE-09C4, 09C7-09C8, 09CB-09CD, 09DC-09DD, 09DF-09E3, 09F0-09F1
Gurmukhi:
0A02, 0A05-0A0A, 0A0F-0A10, 0A13-0A28, 0A2A-0A30, 0A32-0A33, 0A35-0A36, 0A38-0A39, 0A3E-0A42, 0A47-0A48, 0A4B-0A4D, 0A59-0A5C, 0A5E, 0A74
Gujarati:
0A81-0A83, 0A85-0A8B, 0A8D, 0A8F-0A91, 0A93-0AA8, 0AAA-0AB0, 0AB2-0AB3, 0AB5-0AB9, 0ABD-0AC5, 0AC7-0AC9, 0ACB-0ACD, 0AD0, 0AE0
Oriya:
0B01-0B03, 0B05-0B0C, 0B0F-0B10, 0B13-0B28, 0B2A-0B30, 0B32-0B33, 0B36-0B39, 0B3E-0B43, 0B47-0B48, 0B4B-0B4D, 0B5C-0B5D, 0B5F-0B61
Tamil:
0B82-0B83, 0B85-0B8A, 0B8E-0B90, 0B92-0B95, 0B99-0B9A, 0B9C, 0B9E-0B9F, 0BA3-0BA4, 0BA8-0BAA, 0BAE-0BB5, 0BB7-0BB9, 0BBE-0BC2, 0BC6-0BC8, 0BCA-0BCD
Telugu:
0C01-0C03, 0C05-0C0C, 0C0E-0C10, 0C12-0C28, 0C2A-0C33, 0C35-0C39, 0C3E-0C44, 0C46-0C48, 0C4A-0C4D, 0C60-0C61
Kannada:
0C82-0C83, 0C85-0C8C, 0C8E-0C90, 0C92-0CA8, 0CAA-0CB3, 0CB5-0CB9, 0CBE-0CC4, 0CC6-0CC8, 0CCA-0CCD, 0CDE, 0CE0-0CE1
Malayalam:
0D02-0D03, 0D05-0D0C, 0D0E-0D10, 0D12-0D28, 0D2A-0D39, 0D3E-0D43, 0D46-0D48, 0D4A-0D4D, 0D60-0D61
Thai:
0E01-0E3A, 0E40-0E5B
Lao:
0E81-0E82, 0E84, 0E87-0E88, 0E8A, 0E8D, 0E94-0E97, 0E99-0E9F, 0EA1-0EA3, 0EA5, 0EA7, 0EAA-0EAB,
0EAD-0EAE, 0EB0-0EB9, 0EBB-0EBD, 0EC0-0EC4, 0EC6, 0EC8-0ECD, 0EDC-0EDD
Tibetano:
0F00, 0F18-0F19, 0F35, 0F37, 0F39, 0F3E-0F47, 0F49-0F69, 0F71-0F84, 0F86-0F8B, 0F90-0F95, 0F97, 0F99-0FAD, 0FB1-0FB7, 0FB9
Georgiano:
10A0-10C5, 10D0-10F6
Hiragana:
3041-3093, 309B-309C
Katakana:
30A1-30F6, 30FB-30FC
Bopomofo:
3105-312C
CJK United Ideographs:
4E00-9FA5
Hangul:
AC00-D7A3
Caracteres especiales:
00B5, 00B7, 02B0-02B8, 02BB, 02BD-02C1, 02D0-02D1,
02E0-02E4, 037A, 0559, 093D, 0B3D, 1FBE, 203F-2040, 2102,
2107, 210A-2113, 2115, 2118-211D, 2124, 2126, 2128, 212A-2131,
2133-2138, 2160-2182, 3005-3007, 3021-3029
Dígitos:
0660-0669, 06F0-06F9, 0966-096F, 09E6-09EF, 0A66-0A6F, 0AE6-0AEF, 0B66-0B6F, 0BE7-0BEF, 0C66-0C6F, 0CE6-0CEF, 0D66-0D6F, 0E50-0E59, 0ED0-0ED9, 0F20-0F33

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Soporte multibyte y Unicode en identificadores

   En el viejo C casi no había soporte para caracteres internacionales, o bien dicho soporte era implementado de diversas formas por cada compilador.
   Ahora, mediante la inclusión de los UCNs queda estandarizado y aceptado de una vez por todas el soporte para caracteres internacionales.

   Nos puede parece inapropiada una especificación tan complicada como \u040e, por ejemplo. Eso no nos inspira nada intuitivamente. Representa el caracter cirílico \( \acute{\textsf{Y}} \).
   La idea es que no sea el programador que explícitamente escriba estos códigos todo el tiempo, sino que su editor de texto le permita introducir y ver caracteres de su propio idioma, o quizás de todos los idiomas.
   En particular, puede que haya editores de texto que no hayan implementado aún todo el repertorio de Unicode, y que en cambio mantengan alguna codificación multibyte anticuada y retorcida, de esas que nunca terminamos de explicar completamente.
   El estándar C99 da soporte para todas estas eventualidades, aunque en el caso de caracteres multibyte prefiere desentenderse un poco, y sólo dice que hay compiladores que pueden llegar a utilizarlos, y que esto es válido, aunque no dice cómo tienen que considerarse estos caracteres.
   Ahora podríamos definir identificadores, así:

    int país, corazón, pingüino, atenção;


   Por último, aunque el estándar C desde 1999 avale caracteres internacionales, no quiere decir que los compiladores de la vida real logren implementarlos del todo.
   Por ejemplo, nuestro compilador GCC 4.8 aún no incorpora soporte para identificadores multibyte.
   ¿Qué haremos?
   Bueno, existe aquí una discusión al respecto en lo referido al desarrollo de programas portables y legibles. Si nos pasan un programa con identificadores en chino, no vamos a entender nada. En cambio, si están en inglés, que es la lingua franca de esta época, podremos interpretarlo tanto nosotros como los chinos.
   La decisión de usar o no a los UCNs es del programador, o del entorno en donde le toque trabajar.
   Y en particular también del compilador que esté usando.

   Los compiladores que usaremos en este curso son: GCC, Pelles C y PCC.

   A este respecto podemos decir que, a la fecha (año 2013), el estado del soporte para caracteres internacionales en identificadores es el siguiente:
   
\( \bullet \)  GCC no tiene, aunque admite identificadores con UCNs si agregamos la opción del compilador -fextended-identifiers.
\( \bullet \)  Pelles C sí tiene (completo).
\( \bullet \)  PCC no tiene.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Dos identificadores distintos, son distintos?

   El estándar C asegura que si dos identificadores difieren hasta en sus primeros 63 caracteres, al menos, entonces se deben considerar distintos.
   Pero si coinciden sus primeros 63 caracteres, es el compilador el que decide cuántos caracteres son necesarios para decidir si son distintos o no.
   Se dice que en C los identificadores tienen, como mínimo, 63 caracteres iniciales significativos. El resto puede que "no signifique nada".

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Identificadores predefinidos

   En C99 sólo hay uno de estos identificadores: __func__.
   Se sobreentiende que, en cada función, está siempre definido un identificador llamado __func__, el cual es una cadena de caracteres conteniendo el nombre de la función en cuestión.

Así:

#include <stdio.h>
int main(void) {
   puts(__func__);
}

produce la salida en pantalla:

main


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Identificadores reservados

   Los archivos de cabecera estándar, así como la implementación local que estemos usando, suelen utilizar identificadores que comienzan con un caracter subrayado.
   Si el programador reutiliza estos identificadores para otros fines, sin estar al tanto de que está solapando un identificador ya usado en su sistema, puede obtener resultados extraños, impredecibles, o erróneos. Y para peor: sin entender la razón de dónde surgió el problema.
   Para evitar estas colisiones de nombres de identificador, el estándar C estipula una serie de reglas que indican claramente qué tipos de nombres de identificador son de uso reservado.

   Así, el programador debe evitar el uso de identificadores reservados, es decir, los siguientes:
   
\( \bullet \) Un identificador cuyo 1er caracter es el de subrayado _, y cuyo 2do caracter es otro subrayado _ o bien una letra mayúscula (esto se refiere exclusivamente a las letras del conjunto básico de caracteres en el entorno de traducción, vale decir, los caracteres: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z).
         Nota: Estos identificadores pueden ser usados por la implementación bajo cualquier circunstancia, así que siempre deben ser evitados.
\( \bullet \) Un identificador con alcance de "archivo" cuyo 1er caracter es un subrayado _.
         Nota: Si bien esto "sólo" nos afecta cuando incluimos archivos mediante #include, resulta que casi todo programa realmente útil en C incluye algún archivo de cabecera, y por lo tanto debemos evitar declarar todo identificador que comience con un subrayado.
\( \bullet \) Todas las macros de las librerías estándar, así como otras "reservadas para uso futuro", no deben usarse como identificadores.
         Los detalles de este punto en particular son muy extensos, y los iremos dando de a poco, a medida que estudiemos las librerías estándar. Más aún, puede que algunos identificadores estén reservados para uso como macros, pero permitidos como funciones, etc. No podemos desarrollar completamente este material aquí.
\( \bullet \) Los identificadores con enlace extero, así como también errno, no deben usarse como identificadores con enlace externo.
         El tema del enlace externo lo explicaremos en otra parte. Digamos sólo que muchas funciones importantes de la librería estándar tienen identificadores con enlace externo.
\( \bullet \) Cada identificador con alcance de archivo en las librerías estándar es reservado para uso como nombre de macro y como un identificador con alcance de archivo en el mismo espacio de nombres si se incluye cualquiera de sus archivos de cabecera asociados.
         Es más de "lo mismo". No es éste el momento de explicar en detalle todo esto.

   Las reglas anteriores se refieren a varios conceptos que aún no hemos estudiados, y así no podemos discutirlos apropiadamente.
   Podemos, sin embargo, dar un par de pautas sencillas para no cometer errores:
   
\( \bullet \) No definir identificadores que comiencen con el caracter subrayado _.
\( \bullet \) No utilizar identificadores que coincidan (o incluso sean muy "parecidos") a los que figuran en las librerías estándar.

   He perdido rigor aquí, en aras de la claridad. Ya habrá tiempo de profundizar en esto.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Palabras clave

   El lenguaje C tiene, como casi todo lenguaje de programación, una lista de palabras clave.
   En general, las palabras clave tienen la sintaxis de un identificador, pero que no pueden ser usados como tales por el programador, sino que tienen un significado específico atribuido por el lenguaje.

   En el lenguaje C hay varias etapas de compilación, lo cual puede confundirnos un poco acerca del modo en que se consideran las palabras clave.
\( \bullet \)   Mientras está actuando el preprocesador (lo cual involucra las inclusiones de archivos con #include y las expansiones y sustituciones de las macros), las palabras clave son interpretadas como cualquier otro identificador.
\( \bullet \)   Una vez terminadas las tareas del preprocesador se produce la compilación propiamente dicha del programa. Aquí se toma en cuenta el significado que el lenguaje da a cada palabra reservada.

Por ejemplo:

#include <stdio.h>
#define int "Hello!!!"
int main(void) {
    int x;
}

   Es un programa que no puede definir la variable x como de tipo int, porque la palabra reservada int ha sido declarada como una macro, que sustituye todas las ocurrencias de int por "Hello!!!".
   Equivale al siguiente programa, que no compila:

#include <stdio.h>
int main(void) {
    "Hello!!!" x;
}


   Las palabras clave van cambiando en las sucesivas versiones del estándar C.
   En general ocurre que "se agregan" nuevas palabras clave a las ya existentes.

Palabras clave del estándar C de 1990 (C90)


auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while


Palabras clave añadidas en el estándar C de 1999 (C99)


_Bool
_Complex
_Imaginary
inline
restrict


Palabras clave añadidas en el estándar C de 2011 (C11)


_Alignas
_Alignof
_Atomic
_Generic
_Noreturn
_Static_assert
_Thread_local


   Como podemos ver, las nuevas versiones de C introducen, principalmente, palabras clave que comienzan con un caracter subrayado _ seguido de una letra mayúscula.
   Este tipo de construcciones están dentro de lo que hemos visto como identificadores reservados. Esto no viola ninguna norma, ya que dichos identificadores están reservados para uso privado del lenguaje C, y así en futuras versiones pueden aparecer otros engendros del mismo estilo que extiendan el lenguaje.

   Si bien nosotros estamos enfocados principalmente en el estándar C99, no quiere decir que eso sea nuestra religión, y es interesante echar algún vistazo de vez en cuando al nuevo estándar C11.
   El C11 ha añadido soporte para ejecución en varios "hilos simultáneos", útil sobretodo en estos tiempos en que las computadoras vienen con procesadores de varios núcleos corriendo en paralelo.
   Sin embargo se ha introducido la odiosa palabra _Noreturn. ¡Eso sí que va contra la religión!

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   No debemos usar palabras clave del lenguaje para nuestros identificadores.

   Por otro lado, el significado de cada una de las palabras clave, lo iremos explorando de a poco en futuros posts.

Organización

Comentarios y Consultas