Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: fruz en 1 Mayo 2019, 00:04 am



Título: Duda sobre una parte del libro de k&r
Publicado por: fruz en 1 Mayo 2019, 00:04 am
Hola a todos. Tengo una duda:
En un ejemplo del libro de k&r se divide un programa en varios archivos fuente. Uno de ellos es getch.c:

#include <stdio.h>
#define BUFSIZE 100

char buf[BUFSIZE];
int bufp = 0;
itn getch(void) {
   ...
}

void ungetch(int) {
   ...
}

Dentro de getch y de ungetch se usan tanto buf como bufp.
Más adelante dice:
"La declaración static, aplicada a una variable o función externa, limita el alcance de ese objeto al resto del archivo fuente que se está compilando. Así las variables static externas proporcionan una forma de ocultar nombres como buf y bufp en la combinación getch-ungetch, que deben ser externas para que puedan ser compartidas, aunque no deben ser visibles a los usuarios de getch y ungetch."

¿Qué significa "ocultar nombres" y que "no son visibles"?



Título: Re: Duda sobre una parte del libro de k&r
Publicado por: MAFUS en 1 Mayo 2019, 00:46 am
Cuando creas una librería para que otros puedan usarla te interesa exponer ciertos identificadores, cómo las funciones, para que se puedan usar. printf, scanf, etc. son ejemplo de ello.

Pero no sólo existen esas funciones y por debajo usan muchas otras para poder realizar su trabajo que no quieres que tus clientes ni siquiera sepan que existen porque no necesitan usarlas o podrían poner en riesgo el buen funcionamiento de tu librería. Por eso, todo y que deben existir deben estar ocultas. Por eso se usa static. También funciona para los tipos de datos.

El que mejor representa esto es FILE, un tipo de dato incompleto para el cliente, es decir, no hay forma de que sepa que miembros tiene y sólo se puede trabajar con él a través de funciones que hacen referencia a un puntero. Otro tipo dato oculto es el usado por strtok: devuelve un puntero a una cadena pero ¿dónde está? ¿cuándo la creó? ¿cuándo fue dimensionada? Lo que si está claro es que si fuera local de strtok no podría regresarlo pues se destruiría nada mas volver de la función, pero está ahí aunque no se puede acceder a él por su nombre, sólo por su puntero. El dato está oculto.


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: Loretz en 1 Mayo 2019, 03:55 am
Citar
¿Qué significa "ocultar nombres" y que "no son visibles"?

En general creo que conviene tener algún cuidado y no olvidar que lo que se está leyendo es una traducción; así que al estilo de los autores, no siempre la mejor prosa posible, naturalmente, también hay que añadir la buena o mala suerte del traductor.

Yo entiendo que en la frase que citas, la relación es:
Código:
Las variables no son visibles (a los meros usuarios de getch y de ungetch) porque tienen sus nombres ocultos (gracias a que han sido declaradas "static"). 

El problema que veo en el ejemplo es que ni buf ni bufp están declaradas "static", lo que no ayuda mucho al entendimiento. Pero si pones:

Código
  1. #include <stdio.h>
  2. #define BUFSIZE 100
  3.  
  4. static char buf[BUFSIZE];  ///< ahora buf esta declarada "static"
  5. static int bufp = 0;       ///< bufp tambien.            
  6. itn getch(void) {
  7.   ...
  8. }
  9.  
  10. void ungetch(int) {
  11.   ...
  12. }

Pero... aunque las variables no fueran declaradas "static", de todos modos sus nombres tampoco serían "visibles" fuera del archivo getch.c, a no ser que se las declare como "extern" en un header, por ejemplo.

Como ves, la cosa no es muy lineal, ahora además de "static" apareció "extern". Bueno, creo que hubiera convenido presentar antes la declaración "extern", que hace que se puedan compartir variables entre archivos. ¿Me disculpas? Te lo dejo a ti.

De todos modos, la idea es que:
Código:
Al declarar las variables globales como "static", se está prohibiendo su acceso desde otros archivos (se ocultan sus nombres, ya no serán visibles desde otros archivos, incluso aunque algún pícaro o descuidado las declare "extern")

[Nota]
- Pero, ¿a quién se le ocultan esos nombres, si yo los estoy viendo y supongo que tú también?
- Al compilador hombre, ni a tí ni a mí, al compilador.
[/Nota]


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: fruz en 1 Mayo 2019, 04:22 am
No entiendo. ¿Qué significaría que SON visibles?


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: RayR en 1 Mayo 2019, 05:33 am
Kernighan y Ritchie llaman "external" a las variables globales. Para las variables declaradas como extern usan, precisamente, la palabra "extern". En español se tradujeron ambas como "externa(s)", y de ahí que pudiera haber confusión.

Ahora, no sé si copiaste el código incorrecto, pero en mi libro sí aparecen las variables como static:

Citar
static char buf[BUFSIZE]; /* buffer for ungetch */
static int bufp = 0; /* next free position in buf */

int getch(void) { ... }
void ungetch(int c) { ... }

En cuanto a tu duda: cuando declaras variables globales no static, de forma implícita tienen enlace externo, es decir, son accesibles ("visibles") fuera del archivo fuente en el que están declaradas (aunque no hayas usado la palabra extern). Esto quiere decir que si declaras variables globales no static con el mismo nombre en distintos archivos, en realidad se refieren a la misma variable. Cualquier modificación que hagas dentro de uno, afectará los otros. En esencia, estás compartiendo la variable a lo largo de más de un archivo. Al declararlas static, como pasa con buf y bufp, se "ocultan", es decir, sólo existen en el archivo en que se declararon. Cualquier otra variable global declarada en otro archivo de tu programa sería independiente de éstas.


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: Loretz en 1 Mayo 2019, 05:59 am
Tú dices:
Citar
Más adelante dice:
"La declaración static, aplicada a una variable o función externa, ...

En esta porción de tu pregunta encuentro dos problemas:

1.- Un error del traductor:
Donde dice:
Citar
La declaración static, aplicada a una variable o función externa, ... "
El original dice:
Citar
"The static declaration, applied to an external variable or function, ...."

El problema aquí es que las variables pueden ser "externas" (en el sentido de que no están definidas en el cuerpo de una función sino fuera), pero no las funciones. Quizá pudo haberse traducido por:
Código:
La declaración static, aplicada a una variable externa o a una función, ..."

Este punto no hace a la cuestión, pero me pareció que valía la pena aclararlo. El otro problema que encuentro es:

2.- Un descuido tuyo:
Comienzas diciendo
Citar
"Más adelante dice: ..."

El problema aquí es que esa frase que citas está incompleta, la primera parte es importante a la idea esta de nombres de cosas "visibles" u "ocultas" para quién y desde dónde.

La frase de la versión en inglés que veo ahora comienza diciendo
Citar
"The variables sp and val in stack.c , and buf and bufp in getch.c , are for the private
use of the functions in their respective source files, and are not meant to be accessed by
anything else."

Que aunque no tengo tu traducción, supongo que debe estar de alguna manera. Mal, bien o más o menos, como siempre, pero algo debe decir ¿verdad?. Supongo que si lees esa primera parte te podrás formar una mejor idea sobre a qué se refieren los autores con "visible" o "no visible" en este contexto.




Título: Re: Duda sobre una parte del libro de k&r
Publicado por: fruz en 1 Mayo 2019, 17:45 pm
En el texto dice que las variables buf y bufp "deben ser externas para que puedan ser compartidas, aunque no deben ser visibles a los usuarios de getch y ungetch."
¿Quiénes son los usuarios de getch y ungetch?
¿Cómo es que al declararlas static no son visibles en getch y ungetch, estando en el mismo archivo fuente?


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: MAFUS en 1 Mayo 2019, 18:28 pm
Las variables y funciones globales marcadas como static se comportan cómo las private en JAVA. Sirven para todo el código del archivo fuente pero fuera de él no se puede acceder a ellas.

En cambio las variables y funciones globales marcadas como extern se pueden usar en cualquier sitio del programa.

Por defecto las variables son static y las funciones extern.

http://www.it.uc3m.es/pbasanta/asng/course_notes/variables_es.html
https://www.learncpp.com/cpp-tutorial/42-global-variables/
Te dije que no te quedaras sólo con ese texto porqué es complicado, busca en otras fuentes.


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: fruz en 1 Mayo 2019, 18:45 pm
La parte del libro que dice que con static las variables buf y bufp no son visibles en getch y ungetch, ¿está mal? (Tanto las variables como las funciones están en el mismo archivo fuente.


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: Serapis en 1 Mayo 2019, 19:00 pm
Vamos a lo prosaico...

...tienes un vehículo, lo llenas de combustible y recorres varios días con él el país... de repente el vehículo se para... tu llamas al mecánico y le dices "el motor se ha parado". Abres el capó del motor, y ves eso, una mostruosa masa de metal, que genéricamente llamamos motor. ¿Acaso no se te ocultan todas las piezas internas que tiene?.

El vehículo como tal solo precisa del motor unas pocas cosas; aquellas a las que encesita estar conectadas, para 'recibir' o enviar' (algo). El eje de transmisión, el embrague, el acelerador, el motor de arranque engranando al motor, etc... sin embargo el motor dentro tiene muchas piezas internamente. Cada pieza tiene su función y la función básica del motor es dar vueltas, cad apieza que se accede desde fuera, tiene una funcionalidad precisamente relaccionada de alguna manera con el motor, uno lo pone en marcha, otra lo para, otro le cambia la velocidad de giro, aún otra sin pararlo lo desconecta de la transmisión de giro... toda la funcionalidad de cada pieza interna, está 'invisible' al resto del vehículo. Al vehículo, en sí le importa 3 narices como el motor consigue hacer que dé vueltas, todo lo que precisa son esas 'conexiones' EnMarcha, Paro, Acelera, Frena, Puntomuerto, etc... allá el motor se las vea como conseguir todo eso.

El mecánico no dirá el vehículo-motor se ha parado, el mecánico podría decir más acertadamente, por ejemplo algo como: "se quedó sin combustible", "no le llega 'chispa' para la ignición", "el filtro de aire está obstruído", "las válvulas están desgastadas", "un segmento del segundo cilindro se ha roto y ha tenido consecuencias...", etc...

Cuando el mecánico retire la tapa del motor, verás todas las piezas que tiene, están ocultas, hacen su trabajo y no precisan ser consideradas desde fuera, porque desde fuera nada tiene necesidad de interaccionar con ellas, luego por ello conviene que queden ocultas, evitando en lo posible que alguien (por ejemplo), dejare caer alguna cosa y lo dañara).

En programación viene a ser lo mismo. Para hacer una tarea específíca, para cumplir una determinada funcionalidad puedes necesitar ninguna o muchas variables/funciones adicionales, pero la mayoría quedarán ocultas al que deba usar la funcionalidad, así finalmente lo que haces público, quizás alguna variable y constantes, o alguna función con los parámetros que precise y si acaso una devolución, todo el intríngulis interno que precise para llevar a cabo dicha funcionalidad debe quedar oculta, por varias razones, desde la simple: 'no aburrir al personal con miles de variables (que al final ni podrá cambiar)' hasta la lógica: 'una alteración de cualquiera de ellas arbitrariamente defenestraría los resultados de lo que deba hacer la función'.

Un ejemplo claro:
Código:
enumeracion operaciones
   DEC = -1
   RET = 0
   ADD = 1
fin enumeracion

static int total=0
static int n=0

int = public function Acumulador(int valor, operaciones op)
    
    si op = ADD
        total = (total + valor)
        // return 0
    si op =  DEC
        total = total - valor
        // return 0
    ysino
        n = total
        total = valor  // si es un 'suma y sigue', valor sería distinto de 0 y si no sería conforme recibir en valor =0, cuando se pida bascular el resultado.
        return n
    fin si
fin funcion

Esta función (puede resultar tonta como tal, pero que vale para explicar), simplemente sirve como un acumulador, vas sumando o restando valores (positivos o negativos), y en algún momento, cuando ya has terminado... quieres obtener el total, pasas en el parámetro de Op el valor 'RET' (0), y te devuelve el resultado... si la variable 'total'  fueran accesibles desde fuera, se podría cambiar su valor desde fuera en cualquier momento, luego todas las operaciones que has estado, serían malogradas porque el resultado no reflejaría el cómputo de las operaciones que has estado haciendo, no habría forma de garantizar el resultado final a ser lo que las operaciones postulan hacer, pués fue cambiado desde fuera...

Fíjate que el único propósito de 'n' es devolver el resultado, aunque 'n' no fuera declarada estática y fuera accesible desde fuera, su funcionalidad no podría quedar alterada, porque sea el valor que sea que le otorgase desde fuera, cuando se usa internamente se le asigna el valor total y acto seguido se entrega, luego la 'integridad de su funcionalidad' queda a salvo... ahora bien, para qué rayos se precisa dejar accesible esa variable desde fuera????... la  función es: Acumular(23, 1) ... desde fuera 'n' ni tiene utilidad práctca ni su nombre refleja nada útil, luego procede ocultarla... y al caso incluso podría estar encerrada dentro de la propia función ni siquiera en el nivel del módulo.

Todo eso, forma parte de lo que se conoce como 'mecanismos de encapsulación' y que consiste en ocultar todos los detalles que son prescindibles para su uso. Ojo, nota la diferencia entre ocultar y eliminar, ocultar implica que hacen su trabajo, pero desde fuera no se ve... visto desde fuera es casi lo mismo que si no existiera, pero no se ha eliminado. Décadas atrás se tenía muy claro el asunto, pero hasta la llegada de la programación oprientada a objetos, no ha quedado tan patente, la necesidad de la encapsulación y abordarlo desde una perspectiva más ambiciosa... de una forma u otra cada lenguaje tenía sus sentencias, palabras clave, etc... para hacer exactamente eso, encapsular y separar/dividir cada problema en partes lógicas en sí mismas.

Así que ya sabes dos propiedades que tienen las variables:
- Una variable contiene algún dato (sea dle tipo que sea).
- Una variable tiene un 'alcance' determinado. Ese alcance es la visibilidad, es decir desde que otras partes puede ser visto y cambiado.
Fíjate que visto y cambiado son cosas distintas... lo primero hace relación a la 'lectura' la segunda a su 'escritura', así cuando una variable se separa claramente en esos dos aspectos se tiene una 'propiedad' donde existe una variable 'privada' inaccesible desde fuera dle módulo y donde sendas funciones permiten leer y/o escribir su valor... dichas funciones a su vez pueden tener distinto alcance (visibilidad)...

...tu sigue leyendo y ya irás comprendiendo. Puede parecerte un puzzle, pero en algún momento con la colocación de una nueva pieza en el puzzle, acabas por ver y entender la 'imagen' que dicho 'puzzle' encierra... incluso aunque te falten piezas por colocar. ...de repente todo cobra sentido... sigue...


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: MAFUS en 1 Mayo 2019, 19:01 pm
Las variables son visibles para todos los elementos del archivo, por tanto sí son  visibles para las funciones, pero al ser static el resto del mundo que está fuera del archivo no sabrá ni que existen.


Título: Re: Duda sobre una parte del libro de k&r
Publicado por: RayR en 1 Mayo 2019, 20:31 pm
No tendría sentido decir que getch y ungetch no pueden verlas. O es un error grave del traductor, o lo estás malinterpretando.

Dado que apenas empiezas, es normal que mucho de esto te sea confuso. La palabra static en C (y C++) es un asunto medio complicado. Por ejemplo, tiene distinto significado según dónde se use. Esto no es una exageración, literalmente, quienes crearon las reglas del lenguaje decidieron reutilizar la palabra static para no aumentar "innecesariamente" el número de palabras reservadas. Eso ha sido motivo de debate prácticamente desde el principio, con muchos programadores considerando que las reglas fueron un error, que se debió haber recurrido a  diferentes palabras reservadas para los distintos usos, etc. pero pues esto es lo que hay.

Te doy una explicación muy breve, limitándome a lo relevante para este ejemplo. Ten en cuenta que el asunto es mucho más complejo que la simplificación que estoy haciendo, pero tratar de ser más completo y riguroso resultaría en un texto muy largo y no tiene caso complicar más esto (igual si no lo entiendes del todo, lo puedes guardar como referencia para cuando ya sepas más).

Los dos usos de static que son relevantes ahora son en referencia a la duración de una variable, y a su "visibilidad" (realmente en inglés se le llama linkage).

La duración se refiere a durante cuánto tiempo "existe" una variable. Las variables locales (no static) tienen duración automática, es decir, sólo existen dentro del bloque (por ejemplo, función) donde fueron definidos. Las variables globales tienen duración estática: existen durante toda la ejecución del programa.

En cuanto a la visibilidad, por defecto, las variables globales tienen visibilidad externa: pueden ser accedidas desde otros archivos fuente. Lo único que se necesita es que desde esos otros archivos el programador indique al compilador que va a usar estas variables. La forma correcta es, desde esos otros archivos, declararlas como extern. Por ejemplo, siguiendo el ejemplo de getch.c, si yo incluyera ese archivo en un proyecto mío, y agregara otro archivo (digamos, externo.c) y pusiera esta declaración:

Código:
extern int bufp;

Esto le avisa al compilador que voy a usar una variable entera llamada bufp, que está definida en algún otro lado (en este caso, en getch.c). Fíjate que el hecho de que yo haya puesto la palabra extern indica que yo sé que esa variable está definida en otro archivo, y que de forma intencionada quiero acceder a ella. Pero resulta que la palabra extern no es del todo indispensable. Si yo la omitiera, y simplemente la declarara así en mi archivo:

Código:
int bufp;

Creyendo que estoy definiendo una variable únicamente para uso mío, estaría equivocado. En realidad estaría compartiendo la misma variable que getch, justo como si la hubiera declarado como extern. Porque aunque no es lo más correcto, muchos compiladores permiten omitir el especificador extern (siempre que se cumplan ciertas condiciones). En este caso, mi programa compilaría perfectamente y sin errores, pero, de forma totalmente accidental e indeseada, estaría compartiendo una variable con el otro archivo  :-\. Ojo, esto no funciona en todos los compiladores (y los compiladores de C++ son más estrictos), pero es muy probable que el compilador de C que uses lo permita sin marcar error.

Al declarar una variable global como static, especificas que tiene visibilidad (o alcance) interna, y no se puede acceder desde fuera. Con eso evitas accesos, intencionales o no, desde el exterior. Muchos piensan que se debió usar una palabra reservada nueva (por ejemplo "intern") para este fin, ya que en realidad no estamos declarando la variable como estática; desde el momento en que es variable global, ya es estática, y nosotros simplemente estamos cambiando su visibilidad. La palabra static no tiene realmente sentido en este contexto, pero en fin...