15.02.2014 Views

Cap. 3 Administración de la memoria en C. - Inicio

Cap. 3 Administración de la memoria en C. - Inicio

Cap. 3 Administración de la memoria en C. - Inicio

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>Cap</strong>ítulo 3<br />

1<br />

Administración <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C.<br />

Los datos se almac<strong>en</strong>an <strong>en</strong> uno <strong>de</strong> los tres segm<strong>en</strong>tos <strong>de</strong> <strong>memoria</strong> que el programador dispone.<br />

La zona estática para datos, que permite almac<strong>en</strong>ar variables globales durante <strong>la</strong> ejecución <strong>de</strong> un<br />

programa.<br />

El stack que permite almac<strong>en</strong>ar los argum<strong>en</strong>tos y variables locales durante <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong>s<br />

funciones.<br />

El heap que permite almac<strong>en</strong>ar variables adquiridas dinámicam<strong>en</strong>te durante <strong>la</strong> ejecución <strong>de</strong> un<br />

programa.<br />

Direcciones<br />

bajas<br />

estática<br />

heap<br />

Direcciones<br />

altas<br />

stack<br />

Figura 3.1. Segm<strong>en</strong>tos <strong>de</strong> <strong>memoria</strong>.<br />

Los segm<strong>en</strong>tos son provistos por el sistema operativo.<br />

3.1. Manejo estático <strong>de</strong> <strong>la</strong> <strong>memoria</strong>.<br />

La zona estática para datos, permite almac<strong>en</strong>ar variables globales y estáticas.<br />

Si se <strong>en</strong>cu<strong>en</strong>tra una variable <strong>de</strong>finida fuera <strong>de</strong> <strong>la</strong>s funciones, se <strong>la</strong> consi<strong>de</strong>ra global; el<br />

compi<strong>la</strong>dor le asigna un espacio <strong>de</strong>terminado y g<strong>en</strong>era <strong>la</strong> refer<strong>en</strong>cia para accesar<strong>la</strong> <strong>en</strong> <strong>la</strong> zona<br />

estática. El tamaño <strong>de</strong> <strong>la</strong>s variables no pue<strong>de</strong> cambiarse durante <strong>la</strong> ejecución <strong>de</strong>l programa, es<br />

asignado <strong>en</strong> forma estática.<br />

El tiempo <strong>de</strong> vida <strong>de</strong> <strong>la</strong>s variables <strong>de</strong> <strong>la</strong> zona estática es <strong>la</strong> duración <strong>de</strong>l programa.<br />

Estas variables son visibles para todas <strong>la</strong>s funciones que estén <strong>de</strong>finidas <strong>de</strong>spués <strong>de</strong> el<strong>la</strong>s.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


2 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Si se prece<strong>de</strong> con <strong>la</strong> pa<strong>la</strong>bra static a una variable local a una función, ésta también es ubicada <strong>en</strong><br />

<strong>la</strong> zona estática, y existe durante <strong>la</strong> ejecución <strong>de</strong>l programa; no <strong>de</strong>saparece al terminar <strong>la</strong><br />

ejecución <strong>de</strong> <strong>la</strong> función, y conserva su valor, <strong>en</strong>tre l<strong>la</strong>mados a <strong>la</strong> función.<br />

3.2. Manejo automático <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C.<br />

3.2.1. Asignación, Refer<strong>en</strong>cias y tiempo <strong>de</strong> vida.<br />

El compi<strong>la</strong>dor asigna un espacio <strong>de</strong>terminado para <strong>la</strong>s variables y g<strong>en</strong>era <strong>la</strong>s refer<strong>en</strong>cias para<br />

accesar a <strong>la</strong>s variables <strong>de</strong>l stack y <strong>de</strong> <strong>la</strong> zona estática. El tamaño <strong>de</strong> <strong>la</strong>s variables <strong>de</strong> estas zonas<br />

no pue<strong>de</strong> cambiarse durante <strong>la</strong> ejecución <strong>de</strong>l programa, es asignado <strong>en</strong> forma estática.<br />

El tiempo <strong>de</strong> vida <strong>de</strong> <strong>la</strong>s variables <strong>de</strong> <strong>la</strong> zona estática es <strong>la</strong> duración <strong>de</strong>l programa; <strong>la</strong>s variables<br />

<strong>de</strong>nominadas automáticas, o <strong>en</strong> <strong>la</strong> zona <strong>de</strong>l stack, exist<strong>en</strong> durante <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong> función que<br />

<strong>la</strong>s refer<strong>en</strong>cia.<br />

Los argum<strong>en</strong>tos y variables locales, son asignados y <strong>de</strong>sasignados <strong>en</strong> forma dinámica durante <strong>la</strong><br />

ejecución <strong>de</strong> <strong>la</strong>s funciones; pero <strong>en</strong> forma automática, el programador no ti<strong>en</strong>e responsabilidad<br />

<strong>en</strong> ese proceso.<br />

Esta organización permite direccionar efici<strong>en</strong>tem<strong>en</strong>te variables que serán usadas<br />

frecu<strong>en</strong>tem<strong>en</strong>te; a <strong>la</strong> vez posibilita ahorrar espacio <strong>de</strong> direccionami<strong>en</strong>to ya que se pue<strong>de</strong><br />

reutilizar el espacio <strong>de</strong> <strong>memoria</strong> <strong>de</strong>dicado a <strong>la</strong> función cuando ésta termina; y también posibilita<br />

el diseño <strong>de</strong> funciones recursivas y re<strong>en</strong>trantes, asociando un espacio difer<strong>en</strong>te para <strong>la</strong>s variables<br />

por cada invocación <strong>de</strong> <strong>la</strong> función.<br />

Es importante notar que varias funciones pue<strong>de</strong>n emplear los mismos nombres para <strong>la</strong>s variables<br />

locales y argum<strong>en</strong>tos y esto no provoca confusión; existe in<strong>de</strong>p<strong>en</strong><strong>de</strong>ncia temporal <strong>de</strong> <strong>la</strong>s<br />

variables <strong>de</strong> una función. Si se emplea una global, con igual nombre que una local, <strong>de</strong>ntro <strong>de</strong> <strong>la</strong><br />

función se ve <strong>la</strong> local; y fuera existe <strong>la</strong> global.<br />

Por <strong>la</strong> misma razón, no pue<strong>de</strong>n comunicarse los valores <strong>de</strong> variables locales <strong>de</strong> una función a<br />

otra.<br />

Cuando un programa se carga <strong>en</strong> <strong>la</strong> <strong>memoria</strong>, <strong>de</strong>s<strong>de</strong> el disco, sólo se tra<strong>en</strong> <strong>la</strong> zona <strong>de</strong> códigos y<br />

los datos <strong>de</strong> <strong>la</strong> zona estática. Las zonas <strong>de</strong> stack y heap, son creadas <strong>en</strong> <strong>memoria</strong> antes <strong>de</strong> <strong>la</strong><br />

ejecución <strong>de</strong>l programa.<br />

3.2.2. Argum<strong>en</strong>tos y variables locales.<br />

Cada función al ser invocada crea un frame o registro <strong>de</strong> activación <strong>en</strong> el stack, <strong>en</strong> el cual se<br />

almac<strong>en</strong>an los valores <strong>de</strong> los argum<strong>en</strong>tos y <strong>de</strong> <strong>la</strong>s variables locales. Los valores <strong>de</strong> los<br />

argum<strong>en</strong>tos son escritos <strong>en</strong> <strong>memoria</strong>, antes <strong>de</strong> dar inicio al código asociado a <strong>la</strong> función. Es<br />

responsabilidad <strong>de</strong> <strong>la</strong> función escribir valores iniciales a <strong>la</strong>s variables locales, antes <strong>de</strong> que éstas<br />

sean utilizadas; es <strong>de</strong>cir, que aparezcan <strong>en</strong> expresiones <strong>en</strong> don<strong>de</strong> se le<strong>en</strong> estas variables.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 3<br />

Ejemplo 3.1. Función con dos argum<strong>en</strong>tos <strong>de</strong> tipo valor, con dos locales, retorno <strong>de</strong> <strong>en</strong>tero.<br />

La sigui<strong>en</strong>te <strong>de</strong>finición <strong>de</strong> función, ti<strong>en</strong>e argum<strong>en</strong>tos, variables locales y un retorno <strong>de</strong> tipo<br />

<strong>en</strong>tero.<br />

int función1(int arg1, int arg2)<br />

{<br />

int local1;<br />

int local2=5;<br />

/* no pue<strong>de</strong> usarse local1 para lectura, por ejemplo: local2=local1+2; es un error */<br />

local1=arg1 + arg2 + local2;<br />

return ( local1+ 3);<br />

}<br />

A continuación un ejemplo <strong>de</strong> uso <strong>de</strong> <strong>la</strong> función.<br />

Si una variable x, se <strong>de</strong>fine fuera <strong>de</strong> <strong>la</strong>s funciones, se <strong>la</strong> consi<strong>de</strong>ra global y se le asigna espacio<br />

<strong>en</strong> <strong>la</strong> zona estática. Si <strong>en</strong> el texto escrito, su <strong>de</strong>finición aparece antes <strong>de</strong> <strong>la</strong> función1, se dice que<br />

es visible por ésta, o que está <strong>de</strong>ntro <strong>de</strong>l alcance <strong>de</strong> <strong>la</strong> función.<br />

int x=7;<br />

/*En este punto aún no exist<strong>en</strong> <strong>la</strong>s variables: local1, local2, arg1 y arg2. */<br />

x = función1(4, 8);<br />

/*Des<strong>de</strong> aquí <strong>en</strong> a<strong>de</strong><strong>la</strong>nte no existe espacio asociado a los argum<strong>en</strong>tos y variables locales <strong>de</strong> <strong>la</strong><br />

función 1 */<br />

El diagrama <strong>de</strong> <strong>la</strong> Figura 3.1, ilustra el espacio <strong>de</strong> <strong>memoria</strong> asignado a <strong>la</strong>s variables, <strong>de</strong>spués <strong>de</strong><br />

invocada <strong>la</strong> función y justo <strong>de</strong>spués <strong>de</strong> <strong>la</strong> <strong>de</strong>finición <strong>de</strong> <strong>la</strong> variables local2. La variable local1, no<br />

está iniciada y pue<strong>de</strong> cont<strong>en</strong>er cualquier valor.<br />

Zona estática<br />

Stack<br />

x<br />

7<br />

local1<br />

local2<br />

arg1<br />

5<br />

4<br />

Frame <strong>de</strong> función1<br />

arg2<br />

8<br />

Figura 3.1a. Stack <strong>de</strong>spués <strong>de</strong> invocar a <strong>la</strong> función<br />

Al salir <strong>de</strong> <strong>la</strong> función, el espacio <strong>de</strong> <strong>memoria</strong> asignado a <strong>la</strong>s variables, pue<strong>de</strong> visualizarse según:<br />

Profesor Leopoldo Silva Bijit 20-01-2010


4 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Zona estática<br />

Stack<br />

x<br />

20<br />

3.2.3. Visión lógica <strong>de</strong>l stack.<br />

Figura 3.2. Stack al salir <strong>de</strong> <strong>la</strong> función.<br />

Los diagramas anteriores <strong>de</strong>scrib<strong>en</strong> lógicam<strong>en</strong>te el espacio asignado <strong>en</strong> <strong>la</strong> <strong>memoria</strong> para <strong>la</strong>s<br />

variables estáticas y automáticas. Cada compi<strong>la</strong>dor implem<strong>en</strong>ta físicam<strong>en</strong>te <strong>la</strong> creación <strong>de</strong> estos<br />

espacios <strong>de</strong> manera difer<strong>en</strong>te; <strong>en</strong> el frame <strong>de</strong> <strong>la</strong> función se suel<strong>en</strong> almac<strong>en</strong>ar: <strong>la</strong> dirección <strong>de</strong><br />

retorno, los valores <strong>de</strong> los registros que <strong>la</strong> función no <strong>de</strong>be alterar; a<strong>de</strong>más <strong>de</strong>l espacio para<br />

locales y argum<strong>en</strong>tos. Adicionalm<strong>en</strong>te cada compi<strong>la</strong>dor establece conv<strong>en</strong>ios para pasar los<br />

valores y obt<strong>en</strong>er el retorno <strong>de</strong> <strong>la</strong> función, <strong>en</strong> algunos casos lo hace a través <strong>de</strong> registros, <strong>en</strong><br />

otras a través <strong>de</strong>l stack. El uso <strong>de</strong>tal<strong>la</strong>do <strong>de</strong>l stack según lo requiere un programador assembler<br />

es cubierto <strong>en</strong> cursos <strong>de</strong> estructuras <strong>de</strong> computadores.<br />

Se <strong>de</strong>scribe aquí una visualización lógica <strong>de</strong>l segm<strong>en</strong>to <strong>de</strong>l stack, que es <strong>la</strong> que requiere un<br />

programador <strong>en</strong> l<strong>en</strong>guajes <strong>de</strong> alto nivel.<br />

Ejemplo 3.2. Riesgos <strong>de</strong> <strong>la</strong> <strong>de</strong>saparición <strong>de</strong>l frame.<br />

La sigui<strong>en</strong>te función plocal retorna un puntero a una variable local, lo cual es un gran error, ya<br />

que al salir <strong>de</strong> <strong>la</strong> función, <strong>de</strong>ja <strong>de</strong> existir <strong>la</strong> local; y el puntero retornado apunta a una dirección<br />

<strong>de</strong>l stack que no está asignada.<br />

int* plocal(void)<br />

{<br />

int local;<br />

// ****<br />

return(&local); // retorna puntero a local<br />

}<br />

La función que invoca a plocal posiblem<strong>en</strong>te produzca una fal<strong>la</strong> seria <strong>de</strong>l programa, o g<strong>en</strong>erará<br />

un error <strong>de</strong> difícil <strong>de</strong>puración.<br />

Tampoco pue<strong>de</strong> tomarse <strong>la</strong> dirección <strong>de</strong> una variable local, <strong>de</strong>c<strong>la</strong>rada <strong>de</strong> tipo registro. Ya que<br />

los registros no ti<strong>en</strong><strong>en</strong> asociada una dirección <strong>de</strong> <strong>la</strong> <strong>memoria</strong>. El calificar una variable local o a<br />

un argum<strong>en</strong>to <strong>de</strong> tipo register, es una indicación para que el compi<strong>la</strong>dor int<strong>en</strong>te emplear<br />

registros <strong>en</strong> su manipu<strong>la</strong>ción, con v<strong>en</strong>tajas temporales <strong>de</strong> acceso.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 5<br />

3.2.4. Copia <strong>de</strong> argum<strong>en</strong>tos.<br />

Pue<strong>de</strong> l<strong>la</strong>marse a una función con los valores <strong>de</strong> los argum<strong>en</strong>tos o variables locales (o más<br />

g<strong>en</strong>eralm<strong>en</strong>te mediante una expresión <strong>de</strong> éstas) <strong>de</strong> otra función. Sin embargo <strong>la</strong> función que es<br />

l<strong>la</strong>mada, crea una copia <strong>de</strong> los valores <strong>de</strong> sus argum<strong>en</strong>tos, <strong>en</strong> su propio frame. Lo cual garantiza<br />

<strong>la</strong> in<strong>de</strong>p<strong>en</strong><strong>de</strong>ncia <strong>de</strong> funcionami<strong>en</strong>to <strong>de</strong> <strong>la</strong>s funciones, ya que una función que es invocada por<br />

otra, pue<strong>de</strong> cambiar sus argum<strong>en</strong>tos sin alterar los argum<strong>en</strong>tos <strong>de</strong> <strong>la</strong> que <strong>la</strong> invocó.<br />

A su vez esta organización implica crear <strong>la</strong> copia, lo cual pue<strong>de</strong> ser muy costoso <strong>en</strong> tiempo si el<br />

tipo <strong>de</strong>l argum<strong>en</strong>to ti<strong>en</strong>e un gran tamaño <strong>en</strong> bytes. Para solucionar el problema <strong>de</strong> <strong>la</strong> copia, se<br />

pue<strong>de</strong> pasar una refer<strong>en</strong>cia a un argum<strong>en</strong>to <strong>de</strong> gran<strong>de</strong>s dim<strong>en</strong>siones, esto se logra pasando el<br />

valor <strong>de</strong> un puntero (se copia el valor <strong>de</strong>l puntero, el cual ocupa normalm<strong>en</strong>te el tamaño <strong>de</strong> un<br />

<strong>en</strong>tero). Esto se verá <strong>en</strong> pasos <strong>de</strong> argum<strong>en</strong>tos por refer<strong>en</strong>cia.<br />

Ejemplo 3.3. Una función f que invoca a otra función g.<br />

Se ti<strong>en</strong><strong>en</strong> <strong>la</strong>s sigui<strong>en</strong>tes <strong>de</strong>finiciones <strong>de</strong> <strong>la</strong>s funciones. Nótese que los nombres <strong>de</strong> los<br />

argum<strong>en</strong>tos y una <strong>de</strong> <strong>la</strong>s locales ti<strong>en</strong><strong>en</strong> iguales nombres.<br />

int g(int a, int b)<br />

{ int c;<br />

printf("Al <strong>en</strong>trar <strong>en</strong> g: a = %d b = %d \n", a, b);<br />

a = a + b; /*cambia argum<strong>en</strong>to a */<br />

c = a + 4;<br />

printf("Antes <strong>de</strong> salir <strong>de</strong> g: a = %d b = %d c = %d \n", a, b, c);<br />

return( c );<br />

}<br />

int f(int a, int b)<br />

{ int c;<br />

int d=5;<br />

c = a+b+d;<br />

printf("Antes <strong>de</strong> g: a = %d b = %d c = %d d = %d \n", a, b, c, d);<br />

d = g( a, b+c ); /*se copian los valores <strong>en</strong> el frame <strong>de</strong> g */<br />

a = a + d;<br />

b = b + a;<br />

printf("Después <strong>de</strong> g: a = %d b = %d c = %d d = %d \n", a, b, c, d);<br />

return( d + 2);<br />

}<br />

Si consi<strong>de</strong>ramos x <strong>de</strong>finida <strong>en</strong> <strong>la</strong> zona estática, el sigui<strong>en</strong>te segm<strong>en</strong>to:<br />

x=3;<br />

x=f(5,6);<br />

printf(" x = %d \n", x);<br />

G<strong>en</strong>era <strong>la</strong> sigui<strong>en</strong>te salida.<br />

Antes <strong>de</strong> g: a = 5 b = 6 c = 16 d = 5<br />

Al <strong>en</strong>trar <strong>en</strong> g: a = 5 b = 22<br />

Profesor Leopoldo Silva Bijit 20-01-2010


6 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Antes <strong>de</strong> salir <strong>de</strong> g: a = 27 b = 22 c = 31<br />

Después <strong>de</strong> g: a = 36 b = 42 c = 16 d = 31<br />

x = 33<br />

Se ilustran los frames, con el estado <strong>de</strong> <strong>la</strong>s variables, <strong>de</strong>spués <strong>de</strong> los printf.<br />

Antes <strong>de</strong> invocar a g, se ti<strong>en</strong>e el esquema <strong>de</strong> <strong>la</strong> Figura 3.3:<br />

Zona estática<br />

Stack<br />

x<br />

3<br />

local1 c<br />

local2 d<br />

arg1 a<br />

16<br />

5<br />

5<br />

Frame <strong>de</strong> f<br />

arg2 b<br />

6<br />

Figura 3.3. Stack antes <strong>de</strong> invocar a <strong>la</strong> función g.<br />

Al <strong>en</strong>trar <strong>en</strong> g, se copian valores <strong>en</strong> los argum<strong>en</strong>tos. Los frames se api<strong>la</strong>n hacia arriba, lo cual se<br />

muestra <strong>en</strong> <strong>la</strong> Figura 3.4.<br />

Zona estática<br />

Stack<br />

Frame activo<br />

x<br />

3<br />

local1 c<br />

?<br />

arg1 b<br />

22<br />

Frame <strong>de</strong> g<br />

arg2 a<br />

5<br />

local1 c<br />

16<br />

local2 d<br />

arg1 a<br />

5<br />

5<br />

Frame <strong>de</strong> f<br />

arg2 b<br />

6<br />

Figura 3.4. Al <strong>en</strong>trar <strong>en</strong> <strong>la</strong> función g.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 7<br />

Antes <strong>de</strong> salir <strong>de</strong> <strong>la</strong> función g:<br />

Zona estática<br />

Stack<br />

Frame activo<br />

x<br />

3<br />

local1 c<br />

31<br />

arg1 b<br />

22<br />

Frame <strong>de</strong> g<br />

arg2 a<br />

27<br />

local1 c<br />

16<br />

local2 d<br />

arg1 a<br />

5<br />

5<br />

Frame <strong>de</strong> f<br />

arg2 b<br />

6<br />

Figura 3.5. Stack justo antes <strong>de</strong> salir <strong>de</strong> <strong>la</strong> función g.<br />

Al salir <strong>de</strong> <strong>la</strong> función g, ya no está disponible el frame <strong>de</strong> g, y el frame activo es el <strong>de</strong> <strong>la</strong> función<br />

f:<br />

Zona estática<br />

Stack<br />

x<br />

3<br />

local1 c<br />

local2 d<br />

arg1 a<br />

16<br />

31<br />

36<br />

Frame <strong>de</strong> f<br />

arg2 b<br />

42<br />

Figura 3.6. Stack al salir <strong>de</strong> <strong>la</strong> función g.<br />

Al salir <strong>de</strong> <strong>la</strong> función f, se <strong>de</strong>svanece su frame, como se ilustra <strong>en</strong> <strong>la</strong> Figura 3.7:<br />

Profesor Leopoldo Silva Bijit 20-01-2010


8 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Zona estática<br />

Stack<br />

x<br />

33<br />

Figura 3.7. Stack al salir <strong>de</strong> <strong>la</strong> función f.<br />

Ejemplo 3.4. La misma función anterior invocada dos veces.<br />

Veamos un caso <strong>en</strong> el cual se ejecuta dos veces <strong>la</strong> misma función1, <strong>de</strong>l Ejemplo 3.1.<br />

int x=4;<br />

x = función1(4, función1(2,3)) + x;<br />

Se ilustra un diagrama <strong>de</strong>l espacio, cuando ha terminado <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong> asignación a local1,<br />

<strong>de</strong>ntro <strong>de</strong> <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong> función, pero antes <strong>de</strong> salir por primera vez <strong>de</strong> ésta. Los frames se<br />

api<strong>la</strong>n hacia arriba, <strong>en</strong> <strong>la</strong> gráfica. Se ha marcado como activo, el frame <strong>de</strong> <strong>la</strong> segunda invocación<br />

a <strong>la</strong> función1.<br />

Si una función invoca a otra, <strong>en</strong> este caso invoca a <strong>la</strong> misma función, manti<strong>en</strong>e sus locales y<br />

argum<strong>en</strong>tos. Dichas variables exist<strong>en</strong> hasta el término <strong>de</strong> <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong> función.<br />

Zona estática<br />

Stack<br />

Frame activo<br />

x<br />

4<br />

local1<br />

local2<br />

arg1<br />

arg2<br />

10<br />

5<br />

2<br />

3<br />

Frame <strong>de</strong> segunda<br />

invocación a<br />

función1<br />

local1<br />

?<br />

local2<br />

arg1<br />

?<br />

4<br />

Frame <strong>de</strong> función1<br />

arg2<br />

?<br />

Figura 3.8. Stack <strong>de</strong>spués <strong>de</strong> <strong>la</strong> segunda invocación a f.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 9<br />

Una vez que retorna, por primera vez <strong>de</strong> <strong>la</strong> función, se ti<strong>en</strong>e el valor 13 <strong>de</strong> arg2, <strong>de</strong> <strong>la</strong> primera<br />

invocación, y <strong>de</strong>saparece el frame que se creó <strong>en</strong> <strong>la</strong> segunda invocación. Se ilustra <strong>la</strong> zona<br />

<strong>de</strong>spués <strong>de</strong> <strong>la</strong> asignación a local1.<br />

Zona estática<br />

Stack<br />

x<br />

4<br />

local1<br />

local2<br />

arg1<br />

22<br />

5<br />

4<br />

Frame <strong>de</strong> función1<br />

arg2<br />

13<br />

Figura 3.9. Al salir <strong>de</strong> <strong>la</strong> segunda invocación.<br />

Finalm<strong>en</strong>te queda x con valor 29, y no está disponible el frame <strong>de</strong> <strong>la</strong> función <strong>en</strong> el stack.<br />

3.2.4. Recursión.<br />

La organización <strong>de</strong> <strong>la</strong>s variables automáticas, a través <strong>de</strong> un stack, permite el <strong>de</strong>sarrollo <strong>de</strong><br />

algoritmos recursivos.<br />

Se <strong>de</strong>fine recursión como un proceso <strong>en</strong> el cual una función se l<strong>la</strong>ma a sí misma repetidam<strong>en</strong>te,<br />

hasta que se cump<strong>la</strong> <strong>de</strong>terminada condición.<br />

Un algoritmo recursivo pue<strong>de</strong> usarse para computaciones repetitivas, <strong>en</strong> <strong>la</strong>s cuales cada acción<br />

se p<strong>la</strong>ntea <strong>en</strong> términos <strong>de</strong> resultados previos. Dos condiciones <strong>de</strong>b<strong>en</strong> t<strong>en</strong>erse <strong>en</strong> cu<strong>en</strong>ta <strong>en</strong> estos<br />

diseños: Una es que cada l<strong>la</strong>mado a <strong>la</strong> función conduzca a acercarse a <strong>la</strong> solución <strong>de</strong>l problema;<br />

<strong>la</strong> otra, es que se t<strong>en</strong>ga un criterio para <strong>de</strong>t<strong>en</strong>er el proceso.<br />

En g<strong>en</strong>eral los diseños recursivos requier<strong>en</strong> más espacio <strong>de</strong> <strong>memoria</strong> y ocupan mayor tiempo <strong>en</strong><br />

ejecutarse. Sin embargo g<strong>en</strong>eran diseños simples cuando <strong>la</strong>s estructuras <strong>de</strong> datos quedan<br />

naturalm<strong>en</strong>te <strong>de</strong>finidas <strong>en</strong> términos recursivos. Es el caso <strong>de</strong> los árboles y <strong>de</strong> algunas funciones<br />

matemáticas.<br />

La recursión es un método para resolver problemas <strong>en</strong> forma jerárquica (top-down), se parte<br />

reduci<strong>en</strong>do el problema final (top) <strong>en</strong> partes más simples, hasta llegar a un caso (bottom), <strong>en</strong> el<br />

cual se conoce <strong>la</strong> solución; y se <strong>de</strong>vuelve asc<strong>en</strong>di<strong>en</strong>do hasta el tope, calcu<strong>la</strong>ndo con los valores<br />

que se van obt<strong>en</strong>i<strong>en</strong>do. Cada vez que se activa una invocación <strong>de</strong> una función recursiva, se crea<br />

espacio para sus variables automáticas <strong>en</strong> un frame; es <strong>de</strong>cir cada una ti<strong>en</strong>e sus propias<br />

variables. El cambio <strong>de</strong> <strong>la</strong>s locales <strong>de</strong> una invocación no afecta a <strong>la</strong>s locales <strong>de</strong> otra invocación<br />

que esté p<strong>en</strong>di<strong>en</strong>te (que t<strong>en</strong>ga aún su frame <strong>en</strong> el stack). Las difer<strong>en</strong>tes <strong>en</strong>carnaciones <strong>de</strong> <strong>la</strong>s<br />

funciones se comunican los resultados a través <strong>de</strong> los retornos.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


10 Estructuras <strong>de</strong> Datos y Algoritmos<br />

En un procedimi<strong>en</strong>to iterativo, también <strong>de</strong>nominado bottom-up, se parte <strong>de</strong> <strong>la</strong> base conocida y<br />

se construye <strong>la</strong> solución paso a paso, hasta llegar al caso final.<br />

Más a<strong>de</strong><strong>la</strong>nte veremos una estructura básica <strong>de</strong> datos, <strong>de</strong>nominada stack <strong>de</strong> usuario (no<br />

confundir con el stack que maneja <strong>la</strong>s variables automáticas). Se pue<strong>de</strong> <strong>de</strong>mostrar que un<br />

algoritmo recursivo siempre se pue<strong>de</strong> p<strong>la</strong>ntear <strong>en</strong> forma iterativa con <strong>la</strong> ayuda <strong>de</strong>l stack <strong>de</strong><br />

usuario. Y un programa iterativo, que requiera <strong>de</strong> un stack <strong>de</strong> usuario, se pue<strong>de</strong> p<strong>la</strong>ntear <strong>en</strong><br />

forma recursiva (sin stack).<br />

Si existe una forma recursiva <strong>de</strong> resolver un problema, <strong>en</strong>tonces existe también una forma<br />

iterativa <strong>de</strong> hacerlo; y viceversa.<br />

Consi<strong>de</strong>remos <strong>la</strong> función matemática factorial, que tradicionalm<strong>en</strong>te está <strong>de</strong>finida <strong>en</strong> forma<br />

recursiva (a través <strong>de</strong> sí misma).<br />

factorial( 0 ) = 1<br />

factorial( n ) = n * factorial( n-1 )<br />

La condición para <strong>de</strong>t<strong>en</strong>er <strong>la</strong> recursión, el caso base, es que factorial <strong>de</strong> cero es uno. También se<br />

pue<strong>de</strong> <strong>de</strong>t<strong>en</strong>er con factorial(1) = 1.<br />

Ejemplo 3.5. Diseño recursivo.<br />

El sigui<strong>en</strong>te diseño recursivo, condiciona <strong>la</strong> re-invocación <strong>de</strong> <strong>la</strong> función cuando se llega al caso<br />

base:<br />

unsigned int factorial( unsigned int n)<br />

{<br />

if ( n==0) return (1);<br />

else return n*factorial(n-1);<br />

}<br />

El diseño está restringido a valores <strong>de</strong> n positivos y pequeños; ya que existe un máximo <strong>en</strong>tero<br />

repres<strong>en</strong>table, y <strong>la</strong> función factorial crece rápidam<strong>en</strong>te.<br />

Si se invoca: factorial(4), se produc<strong>en</strong> cinco frames <strong>en</strong> el stack.<br />

El último frame es producido por <strong>la</strong> invocación <strong>de</strong> factorial(0), hasta ese mom<strong>en</strong>to ninguna <strong>de</strong><br />

<strong>la</strong>s funciones ha retornado (todas están ejecutando <strong>la</strong> acción asociada al else, pero no pue<strong>de</strong>n<br />

retornar ya que requier<strong>en</strong> para calcu<strong>la</strong>r el producto, el valor <strong>de</strong> retorno <strong>de</strong> <strong>la</strong> función).<br />

Exist<strong>en</strong> cinco argum<strong>en</strong>tos, <strong>de</strong> nombre n, con valores difer<strong>en</strong>tes. La ejecución, <strong>de</strong>l caso base<br />

(n=0), no invoca nuevam<strong>en</strong>te a <strong>la</strong> función, ya que ésta está condicionada, y retorna el valor 1; lo<br />

cual <strong>de</strong>sactiva el frame con n=0, y pasa a completar <strong>la</strong> ejecución <strong>de</strong>l l<strong>la</strong>mado factorial(1) que<br />

estaba p<strong>en</strong>di<strong>en</strong>te. En este mom<strong>en</strong>to: conoce n, que es uno, y el valor retornado por factorial(0),<br />

que también es uno; <strong>en</strong>tonces retorna 1, y elimina el frame.<br />

Sigue <strong>la</strong> ejecución <strong>de</strong> factorial(2) <strong>de</strong>l mismo modo, hasta eliminar el último frame, retornando el<br />

valor 24.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 11<br />

Ejemplo 3.6. Diseño iterativo.<br />

Simi<strong>la</strong>r cálculo se pue<strong>de</strong> realizar <strong>en</strong> forma iterativa.<br />

unsigned int factorial(unsigned int n)<br />

{ int i;<br />

unsigned int producto = 1;<br />

}<br />

for (i = 1; i


12 Estructuras <strong>de</strong> Datos y Algoritmos<br />

En un caso más g<strong>en</strong>eral, el resultado <strong>de</strong> <strong>la</strong> función pue<strong>de</strong> formar parte <strong>de</strong> una expresión, cuyo<br />

tipo <strong>de</strong>be ser compatible con el <strong>de</strong> <strong>la</strong> variable a <strong>la</strong> cual se asigna dicha expresión:<br />

x = f(a, b) + c<br />

Estas limitaciones pue<strong>de</strong>n superarse si <strong>la</strong> función escribe o lee variables globales. En este caso<br />

no hay restricciones para el número <strong>de</strong> variables que una función pue<strong>de</strong> modificar. Lo anterior<br />

también elimina el t<strong>en</strong>er que copiar los valores <strong>en</strong> argum<strong>en</strong>tos, pudiéndose diseñar funciones<br />

con m<strong>en</strong>or número <strong>de</strong> argum<strong>en</strong>tos <strong>de</strong> <strong>en</strong>trada. Esta práctica g<strong>en</strong>era efectos <strong>la</strong>terales <strong>de</strong> difícil<br />

<strong>de</strong>puración, <strong>en</strong> un ambi<strong>en</strong>te <strong>en</strong> el que varias personas difer<strong>en</strong>tes diseñan <strong>la</strong>s funciones <strong>de</strong> un<br />

programa.<br />

El concepto <strong>de</strong> puntero permite pasar <strong>la</strong> refer<strong>en</strong>cia a una variable que existe fuera <strong>de</strong> <strong>la</strong> función,<br />

eliminando, <strong>de</strong> esta forma, <strong>la</strong> copia <strong>de</strong> variables <strong>de</strong> gran<strong>de</strong>s dim<strong>en</strong>siones, ya que sólo es preciso<br />

copiar el valor <strong>de</strong>l puntero.<br />

También pue<strong>de</strong> modificarse <strong>la</strong> comunicación <strong>de</strong> más <strong>de</strong> un valor si <strong>la</strong> función retorna el valor<br />

<strong>de</strong> un puntero a una colección (arreglo, string, estructura) agrupada <strong>de</strong> datos. De esta forma se<br />

manti<strong>en</strong>e el concepto <strong>de</strong> que una función se comunica con el resto <strong>de</strong>l ambi<strong>en</strong>te, sólo a través <strong>de</strong><br />

sus argum<strong>en</strong>tos y por único valor <strong>de</strong> retorno.<br />

La evitación <strong>de</strong> <strong>la</strong> copia consiste <strong>en</strong> mant<strong>en</strong>er <strong>en</strong> <strong>memoria</strong> sólo una copia <strong>de</strong> <strong>la</strong> variable <strong>de</strong><br />

interés. Se pasan como argum<strong>en</strong>tos los valores <strong>de</strong> los punteros que refer<strong>en</strong>cian a <strong>la</strong>s variables<br />

que se <strong>de</strong>sea ver o modificar, si se <strong>de</strong>sea sólo refer<strong>en</strong>ciar variables aparec<strong>en</strong> éstas precedidas <strong>de</strong>l<br />

operador que obti<strong>en</strong>e <strong>la</strong> dirección, el &. Mediante <strong>la</strong> indirección <strong>de</strong> los punteros se pue<strong>de</strong>n leer<br />

o escribir variables externas, <strong>de</strong>ntro <strong>de</strong> <strong>la</strong> función, lo cual implica <strong>la</strong> aparición <strong>de</strong> asteriscos <strong>en</strong> el<br />

código.<br />

La copia local conti<strong>en</strong>e el valor <strong>de</strong>l puntero, y a través <strong>de</strong> <strong>la</strong> indirección se pue<strong>de</strong> modificar <strong>la</strong><br />

variable externa a <strong>la</strong> función, a<strong>de</strong>más <strong>de</strong>l valor retornado; lo cual <strong>de</strong>be ser consi<strong>de</strong>rado un efecto<br />

<strong>la</strong>teral, y siempre pres<strong>en</strong>ta riesgos <strong>de</strong> g<strong>en</strong>erar errores <strong>de</strong> difícil <strong>de</strong>puración.<br />

Ejemplo 3.8. Se <strong>de</strong>sea diseñar función que retorne dos resultados.<br />

Sean estos resultados, <strong>la</strong> suma y <strong>la</strong> resta <strong>de</strong> dos variables o valores.<br />

Paso por refer<strong>en</strong>cia.<br />

Se <strong>de</strong>sea obt<strong>en</strong>er <strong>la</strong> suma y difer<strong>en</strong>cia <strong>de</strong> dos valores cualesquiera, éstos pue<strong>de</strong>n ser pasados por<br />

valor.<br />

Si se elige que el retorno <strong>de</strong> <strong>la</strong> función <strong>en</strong>tregue <strong>la</strong> suma <strong>de</strong> los argum<strong>en</strong>tos, es preciso agregar<br />

un argum<strong>en</strong>to que pase el valor <strong>de</strong> <strong>la</strong> dirección <strong>de</strong> variable externa a <strong>la</strong> función, <strong>en</strong> don<strong>de</strong> se<br />

escribirá <strong>la</strong> difer<strong>en</strong>cia:<br />

int f(int x, int y, int *dif)<br />

{<br />

*dif = x – y ; //escribe <strong>la</strong> difer<strong>en</strong>cia <strong>en</strong> variable fuera <strong>de</strong> <strong>la</strong> función. Aparece operador *.<br />

return (x + y); //retorna <strong>la</strong> suma<br />

}<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 13<br />

Es conv<strong>en</strong>i<strong>en</strong>te que el código <strong>de</strong> <strong>la</strong> función sólo escriba una vez, <strong>en</strong> <strong>la</strong> variable pasada por<br />

refer<strong>en</strong>cia. Lo cual <strong>de</strong>limita los efectos <strong>la</strong>terales.<br />

Nótese que <strong>la</strong> <strong>de</strong>c<strong>la</strong>ración <strong>de</strong>l tipo <strong>de</strong> <strong>la</strong> variable por refer<strong>en</strong>cia, recuerda cómo <strong>de</strong>be ser<br />

empleada <strong>la</strong> variable <strong>de</strong>ntro <strong>de</strong> <strong>la</strong> función. Des<strong>de</strong> este punto <strong>de</strong> vista convi<strong>en</strong>e colocar el<br />

asterisco precedi<strong>en</strong>do a <strong>la</strong> variable.<br />

Un ejemplo <strong>de</strong> invocación, escribi<strong>en</strong>do <strong>la</strong> suma <strong>en</strong> c y <strong>la</strong> difer<strong>en</strong>cia <strong>en</strong> d, variables que se<br />

asum<strong>en</strong> <strong>de</strong>finidas:<br />

c = f(3, 4, &d); //aparece el operador &<br />

Don<strong>de</strong> los valores 3 y 4, pue<strong>de</strong>n ser reemp<strong>la</strong>zados por expresiones con valores <strong>en</strong>teros.<br />

Si se tuviera <strong>de</strong>finida una variable para almac<strong>en</strong>ar el valor <strong>de</strong>l puntero a <strong>la</strong> variable d, según:<br />

int *vpd = &d; // vpd es <strong>de</strong> tipo puntero a <strong>en</strong>tero<br />

El l<strong>la</strong>mado pue<strong>de</strong> efectuarse sin el &, según:<br />

c = f(3, 4, vpd);<br />

Lo cual refleja que se está pasando el valor <strong>de</strong>l puntero.<br />

Si se tuviera <strong>de</strong>finido el tipo: puntero a variable <strong>de</strong> tipo <strong>en</strong>tero según:<br />

type<strong>de</strong>f int* pi;<br />

La codificación <strong>de</strong> los argum<strong>en</strong>tos <strong>de</strong> <strong>la</strong> función podría escribirse sin asteriscos.<br />

int f(int x, int y, pi pvar)<br />

{<br />

*pvar = x – y ; //De todas maneras aparece operador *.<br />

return (x + y); //retorna <strong>la</strong> suma<br />

}<br />

Ejemplo <strong>de</strong> uso:<br />

pi pd=&d;<br />

c = f(3, 4, pd);<br />

Retorno <strong>de</strong> estructura.<br />

Esta forma posibilita <strong>de</strong>volver más <strong>de</strong> un resultado <strong>en</strong> el retorno <strong>de</strong> <strong>la</strong> función.<br />

Se <strong>de</strong>fine <strong>la</strong> estructura dosretornos, para almac<strong>en</strong>ar el resultado.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


14 Estructuras <strong>de</strong> Datos y Algoritmos<br />

struct dosretornos<br />

{<br />

int suma;<br />

int difer<strong>en</strong>cia;<br />

}; //<strong>de</strong>c<strong>la</strong>ración <strong>de</strong> tipo<br />

struct dosretornos g( int x, int y)<br />

{<br />

struct dosretornos temp;<br />

temp.suma = x + y;<br />

temp.difer<strong>en</strong>cia = x-y;<br />

return (temp);<br />

}<br />

Un ejemplo <strong>de</strong> uso, si se ti<strong>en</strong>e <strong>de</strong>finida una variable c <strong>de</strong> tipo dos retornos:<br />

struct dosretornos c; //<strong>de</strong>finición <strong>de</strong> variable<br />

c = g(3, 4);<br />

La suma queda <strong>en</strong> c.suma, <strong>la</strong> difer<strong>en</strong>cia <strong>en</strong> c.difer<strong>en</strong>cia.<br />

Retorno <strong>en</strong> arreglo.<br />

Un caso particu<strong>la</strong>r es cuando todos los valores que se <strong>de</strong>sean retornar son <strong>de</strong> igual tipo. En esta<br />

situación se pue<strong>de</strong> efectuar los retornos <strong>en</strong> un arreglo.<br />

Se ti<strong>en</strong>e un arreglo <strong>de</strong> dos posiciones:<br />

int a[2]; //variable global<br />

La función pue<strong>de</strong> escribir <strong>en</strong> <strong>la</strong> variable global<br />

void g2( int x, int y)<br />

{<br />

*a = x + y; //por global<br />

*(a+1) = x-y;<br />

}<br />

Los resultados quedan <strong>en</strong> <strong>la</strong>s primeras posiciones <strong>de</strong>l arreglo.<br />

g2(5, 2);<br />

printf(" %d %d\n", a[0], a[1]);<br />

Se pue<strong>de</strong> diseñar una función g3, que escriba <strong>en</strong> <strong>la</strong>s dos primeras posiciones <strong>de</strong> un arreglo que<br />

se pasa por refer<strong>en</strong>cia:<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 15<br />

void g3( int x, int y, int *arr)<br />

{<br />

*arr = x + y;<br />

*(arr+1) = x-y;<br />

}<br />

Ahora el l<strong>la</strong>mado se efectúa pasando como tercer argum<strong>en</strong>to el nombre <strong>de</strong>l arreglo.<br />

g3(3, 4, a);<br />

printf(" %d %d\n", a[0], a[1]);<br />

3.2.6. Evitación <strong>de</strong> <strong>la</strong> copia <strong>de</strong> argum<strong>en</strong>tos que ocupan muchos bytes.<br />

En g<strong>en</strong>eral, <strong>en</strong> el l<strong>en</strong>guaje C, los arreglos, strings y estructuras se manipu<strong>la</strong>n con funciones a <strong>la</strong>s<br />

cuales se les pasa una refer<strong>en</strong>cia. Las funciones pue<strong>de</strong>n retornar una refer<strong>en</strong>cia a una<br />

agrupación.<br />

Ejemplo 3.9. Árbol binario.<br />

Veamos un primer ejemplo <strong>de</strong> una estructura que pue<strong>de</strong> ocupar gran<strong>de</strong>s dim<strong>en</strong>siones.<br />

Se ti<strong>en</strong><strong>en</strong> los tipos <strong>de</strong> datos: nodo y pnodo, <strong>de</strong>finidos según:<br />

type<strong>de</strong>f struct tno<strong>de</strong><br />

{<br />

int valor;<br />

struct tno<strong>de</strong> *left;<br />

struct tno<strong>de</strong> *right;<br />

} nodo, * pnodo;<br />

Y se ha formado un árbol binario, con los sigui<strong>en</strong>tes nodos.<br />

raiz<br />

pn<br />

1<br />

3<br />

6<br />

4<br />

8<br />

Figura 3.10. Árbol Binario.<br />

Nótese que para cada nodo: los valores <strong>de</strong> los nodos que están vincu<strong>la</strong>dos por <strong>la</strong> <strong>de</strong>recha son<br />

mayores que los valores <strong>de</strong> los nodos que están conectados por <strong>la</strong> izquierda. En <strong>la</strong> Figura 3.10,<br />

se ti<strong>en</strong><strong>en</strong> dos variables <strong>de</strong> tipo puntero a nodo, una apunta a un nodo <strong>de</strong>nominado raíz, el otro<br />

(pn) es una variable auxiliar.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


16 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Los nodos que no ti<strong>en</strong><strong>en</strong> <strong>de</strong>sc<strong>en</strong>di<strong>en</strong>tes <strong>de</strong>b<strong>en</strong> t<strong>en</strong>er valores nulos <strong>en</strong> los punteros left y right.<br />

Se <strong>de</strong>sea diseñar una función que <strong>en</strong>cu<strong>en</strong>tre el nodo con mayor valor.<br />

No es razonable pasar como argum<strong>en</strong>to el árbol completo a <strong>la</strong> función, por el espacio requerido<br />

<strong>en</strong> el stack, y porque sería necesario a<strong>de</strong>más copiar <strong>la</strong> estructura completa. Entonces po<strong>de</strong>mos<br />

pasar un puntero a un nodo como argum<strong>en</strong>to, esto sólo requiere crear el espacio y copiar el valor<br />

<strong>de</strong> un puntero (normalm<strong>en</strong>te el tamaño ocupado por un <strong>en</strong>tero). El retorno <strong>de</strong> <strong>la</strong> función, será un<br />

puntero al nodo que t<strong>en</strong>ga el mayor valor.<br />

pnodo busca_max(pnodo T)<br />

{ if (T != NULL)<br />

while (T->right != NULL) T = T->right; /* Iterativo. Siempre por <strong>la</strong> <strong>de</strong>recha */<br />

return(T);<br />

}<br />

La función consi<strong>de</strong>ra que se <strong>la</strong> podría invocar con un argum<strong>en</strong>to apuntando a un árbol vacío, <strong>en</strong><br />

ese caso se <strong>de</strong>ci<strong>de</strong> retornar un puntero con valor nulo, que indica un árbol (o subárbol vacío).<br />

T<strong>en</strong>i<strong>en</strong>do <strong>en</strong> cu<strong>en</strong>ta <strong>la</strong> propiedad <strong>de</strong>l árbol binario, basta <strong>de</strong>sc<strong>en</strong><strong>de</strong>r por <strong>la</strong> vía <strong>de</strong>recha, hasta<br />

<strong>en</strong>contrar un nodo sin <strong>de</strong>sc<strong>en</strong>di<strong>en</strong>te u hoja.<br />

Un ejemplo <strong>de</strong> uso:<br />

pn = busca_max(raiz);<br />

En el caso <strong>de</strong>l ejemplo, retornaría un puntero al nodo con valor 8.<br />

Otro ejemplo <strong>de</strong> uso:<br />

pn = busca_max(raiz->left);<br />

Retornaría un puntero al nodo con valor 1.<br />

Ejemplo 3.10 Manipu<strong>la</strong>ción <strong>de</strong> strings.<br />

Veremos <strong>la</strong> forma <strong>de</strong> tratar funciones que manipul<strong>en</strong> arreglos o strings.<br />

Antes <strong>de</strong> <strong>de</strong>sarrol<strong>la</strong>r el ejemplo repasaremos <strong>la</strong> estructura <strong>de</strong> datos asociada a un string.<br />

Definición <strong>de</strong> string.<br />

a) Arreglo <strong>de</strong> caracteres.<br />

La sigui<strong>en</strong>te <strong>de</strong>finición reserva espacio para un string como un arreglo <strong>de</strong> caracteres.<br />

La <strong>de</strong>finición <strong>de</strong> un string como arreglo <strong>de</strong> caracteres, <strong>de</strong>be incluir un espacio para el carácter<br />

fin <strong>de</strong> string (el carácter NULL, con valor cero). Quizás es mejor <strong>de</strong>finir el terminador <strong>de</strong> string<br />

como null (o eos: <strong>en</strong>d of string), para evitar confusiones con el valor <strong>de</strong> un puntero nulo.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 17<br />

char str[6]; /*crea arreglo <strong>de</strong> chars con espacio para 6 caracteres. Índice varía <strong>en</strong>tre 0 y 5 */<br />

str[5] = NULL; //coloca el fin <strong>de</strong> string.<br />

str<br />

\0<br />

Figura 3.11. Repres<strong>en</strong>tación <strong>en</strong> <strong>memoria</strong> <strong>de</strong> un string.<br />

Con <strong>la</strong> <strong>de</strong>finición anterior el string almac<strong>en</strong>ado <strong>en</strong> el arreglo pue<strong>de</strong> t<strong>en</strong>er un <strong>la</strong>rgo máximo <strong>de</strong><br />

cinco caracteres.<br />

El nombre <strong>de</strong>l arreglo str, es un puntero constante que apunta al primer carácter <strong>de</strong>l string, por<br />

ser constante no se le pue<strong>de</strong> asignar nuevos valores o modificar. Las direcciones <strong>de</strong> <strong>memoria</strong><br />

<strong>de</strong>b<strong>en</strong> consi<strong>de</strong>rarse consecutivas; <strong>en</strong> <strong>la</strong> dirección más alta se almac<strong>en</strong>a el fin <strong>de</strong> string.<br />

b) Puntero a carácter.<br />

La <strong>de</strong>finición <strong>de</strong> un string como un puntero a carácter, pue<strong>de</strong> ser inicializada asignándole una<br />

constante <strong>de</strong> tipo string. La que se <strong>de</strong>fine como una secu<strong>en</strong>cia <strong>de</strong> cero o más caracteres <strong>en</strong>tre<br />

comil<strong>la</strong>s dobles; el compi<strong>la</strong>dor agrega el carácter ‘\0’ automáticam<strong>en</strong>te al final.<br />

Si <strong>de</strong>ntro <strong>de</strong>l string se <strong>de</strong>sea emplear <strong>la</strong> comil<strong>la</strong> doble <strong>de</strong>be precedérse<strong>la</strong> por un \.<br />

En caso <strong>de</strong> escribir, <strong>en</strong> el texto <strong>de</strong> un programa, un string <strong>de</strong> varias líneas, <strong>la</strong> secu<strong>en</strong>cia <strong>de</strong> un \ y<br />

el retorno <strong>de</strong> carro (que es invisible <strong>en</strong> <strong>la</strong> pantal<strong>la</strong>) no se consi<strong>de</strong>ran parte <strong>de</strong>l string.<br />

char * str1 = "123456789"; /* ti<strong>en</strong>e 10 caracteres, incluido el NULL que termina el string.*/<br />

Un argum<strong>en</strong>to <strong>de</strong> tipo puntero a carácter pue<strong>de</strong> ser reemp<strong>la</strong>zado <strong>en</strong> una lista <strong>de</strong> parámetros, <strong>en</strong><br />

<strong>la</strong> <strong>de</strong>finición <strong>de</strong> una función por un arreglo <strong>de</strong> caracteres sin especificar el tamaño. En el caso<br />

<strong>de</strong>l ejemplo anterior, podría escribirse: char str1[ ]. La elección <strong>en</strong>tre estas alternativas suele<br />

realizarse según sea el tratami<strong>en</strong>to que se realice <strong>de</strong>ntro <strong>de</strong> <strong>la</strong> función; es <strong>de</strong>cir, si <strong>la</strong>s<br />

expresiones se e<strong>la</strong>boran <strong>en</strong> base a punteros o si se emplea manipu<strong>la</strong>ción <strong>de</strong> arreglos.<br />

En <strong>la</strong> variable str1 se almac<strong>en</strong>a <strong>la</strong> dirección <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> don<strong>de</strong> se almac<strong>en</strong>a el primer byte<br />

<strong>de</strong>l string, el cual <strong>en</strong> este caso es el número 1, con equival<strong>en</strong>te hexa<strong>de</strong>cimal 0x31.<br />

Nótese que str ocupa el espacio con que fue <strong>de</strong>finido el arreglo, mi<strong>en</strong>tras que str1 es un puntero.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


18 Estructuras <strong>de</strong> Datos y Algoritmos<br />

str1<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

\0<br />

Figura 3.12. Puntero a carácter y el string vincu<strong>la</strong>do.<br />

c) Strcpy. Copia el string fu<strong>en</strong>te <strong>en</strong> el string <strong>de</strong>stino.<br />

Se <strong>de</strong>ti<strong>en</strong>e <strong>la</strong> copia <strong>de</strong>spués <strong>de</strong> haber copiado el carácter nulo <strong>de</strong>l fin <strong>de</strong>l string.<br />

Retorna <strong>la</strong> dirección <strong>de</strong>l string <strong>de</strong>stino.<br />

char *strcpy(char * <strong>de</strong>stino, register const char * fu<strong>en</strong>te)<br />

{ register char * cp= <strong>de</strong>stino;<br />

while(*cp++ = *fu<strong>en</strong>te++) continue;<br />

return <strong>de</strong>stino;<br />

}<br />

Los argum<strong>en</strong>tos y valor <strong>de</strong> retorno son punteros a carácter. Lo cual evita <strong>la</strong> copia <strong>de</strong> los<br />

argum<strong>en</strong>tos. A continuación se dan explicaciones <strong>de</strong> diversos aspectos <strong>de</strong> <strong>la</strong> sintaxis <strong>de</strong>l<br />

l<strong>en</strong>guaje.<br />

<strong>de</strong>stino<br />

cp<br />

fu<strong>en</strong>te<br />

Figura 3.13. Copia <strong>de</strong> string.<br />

El diagrama ilustra los punteros fu<strong>en</strong>te y cp, <strong>de</strong>spués <strong>de</strong> haberse realizado <strong>la</strong> copia <strong>de</strong>l primer<br />

carácter. Se muestra el movimi<strong>en</strong>to <strong>de</strong> copia y el <strong>de</strong> los punteros.<br />

Cuando el cont<strong>en</strong>ido <strong>de</strong> *fu<strong>en</strong>te es el carácter NULL, primero lo copia y <strong>la</strong> expresión resultante<br />

<strong>de</strong> <strong>la</strong> asignación toma valor cero, que ti<strong>en</strong>e valor falso para <strong>la</strong> condición, terminando el <strong>la</strong>zo<br />

while. Copiando correctam<strong>en</strong>te un string nulo.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 19<br />

La instrucción continue pue<strong>de</strong> aparecer <strong>en</strong> el bloque <strong>de</strong> acciones <strong>de</strong> un while, do o for. Su<br />

ejecución lleva a reevaluar <strong>la</strong> condición <strong>de</strong> continuación <strong>de</strong>l bloque <strong>de</strong> repetición más interno<br />

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

instrucción continue; ya que un punto y coma se consi<strong>de</strong>ra una acción nu<strong>la</strong>.<br />

El operador <strong>de</strong> postincrem<strong>en</strong>to opera sobre un left value (que recuerda un valor que pue<strong>de</strong><br />

colocarse a <strong>la</strong> izquierda <strong>de</strong> una asignación). Un lvalue es un i<strong>de</strong>ntificador o expresión que está<br />

re<strong>la</strong>cionado con un objeto que pue<strong>de</strong> ser accesado y cambiado <strong>en</strong> <strong>la</strong> <strong>memoria</strong>.<br />

El uso <strong>de</strong> estos operadores <strong>en</strong> expresiones produce un efecto <strong>la</strong>teral, <strong>en</strong> el s<strong>en</strong>tido que se<br />

efectúan dos acciones. Primero se usa el valor <strong>de</strong>l objeto <strong>en</strong> <strong>la</strong> expresión y luego éste es<br />

increm<strong>en</strong>tado <strong>en</strong> uno.<br />

El operador <strong>de</strong> indirección (el *) y el operador ++ ti<strong>en</strong><strong>en</strong> <strong>la</strong> misma prece<strong>de</strong>ncia, <strong>en</strong>tonces se<br />

resuelve cuál operador recibe primero el operando mediante su asociatividad, que <strong>en</strong> el caso <strong>de</strong><br />

los operadores unarios es <strong>de</strong> <strong>de</strong>recha a izquierda. Es <strong>de</strong>cir *fu<strong>en</strong>te++ se interpreta según:<br />

( * (fu<strong>en</strong>te++) ) .<br />

La expresión toma el valor <strong>de</strong>l puntero fu<strong>en</strong>te y lo indirecciona, posteriorm<strong>en</strong>te increm<strong>en</strong>ta <strong>en</strong><br />

uno al puntero.<br />

En <strong>la</strong> expresión (* fu<strong>en</strong>te) ++, mediante el uso <strong>de</strong> paréntesis se cambia <strong>la</strong> asociatividad, <strong>la</strong><br />

expresión toma el valor <strong>de</strong>l objeto apuntado por fu<strong>en</strong>te, y luego increm<strong>en</strong>ta <strong>en</strong> uno el valor <strong>de</strong>l<br />

objeto, no <strong>de</strong>l puntero.<br />

Pue<strong>de</strong> evitarse <strong>la</strong> acción doble re<strong>la</strong>cionada con los operadores <strong>de</strong> pre y postincrem<strong>en</strong>to, usando<br />

éstos <strong>en</strong> expresiones que sólo cont<strong>en</strong>gan dichos operadores. En el caso <strong>de</strong> <strong>la</strong> acción <strong>de</strong><br />

repetición:<br />

while(*cp++ = *fu<strong>en</strong>te++) continue;<br />

Pue<strong>de</strong> codificarse:<br />

while( *cp = *fu<strong>en</strong>te) {cp++, fu<strong>en</strong>te++};<br />

Sin embargo los programadores no suel<strong>en</strong> emplear esta forma. Adicionalm<strong>en</strong>te no produc<strong>en</strong><br />

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

allá <strong>de</strong> los caracteres <strong>de</strong> fin <strong>de</strong> string; <strong>la</strong> segunda forma <strong>de</strong>ja los punteros apuntando a los<br />

terminadores <strong>de</strong> los strings. Ambas formas satisfac<strong>en</strong> los requerimi<strong>en</strong>tos <strong>de</strong> srtcpy.<br />

La primera forma sólo t<strong>en</strong>dría v<strong>en</strong>tajas si el procesador ti<strong>en</strong>e mecanismos <strong>de</strong> direccionami<strong>en</strong>tos<br />

autoincrem<strong>en</strong>tados, y si el compi<strong>la</strong>dor emplea dichos mecanismos al compi<strong>la</strong>r <strong>la</strong> primera forma.<br />

Cuando <strong>en</strong> <strong>la</strong> lista <strong>de</strong> parámetros <strong>de</strong> una función aparece <strong>la</strong> pa<strong>la</strong>bra reservada const precedi<strong>en</strong>do<br />

a una variable <strong>de</strong> tipo puntero, el compi<strong>la</strong>dor advierte un error si <strong>la</strong> función modifica <strong>la</strong> variable<br />

a <strong>la</strong> que el puntero apunta. A<strong>de</strong>más cuando se dispone <strong>de</strong> difer<strong>en</strong>tes tipos <strong>de</strong> <strong>memoria</strong>s (RAM,<br />

EEPROM o FLASH) localiza <strong>la</strong>s constantes <strong>en</strong> ROM o FLASH. Si se <strong>de</strong>sea que que<strong>de</strong> <strong>en</strong> un<br />

segm<strong>en</strong>to <strong>de</strong> RAM, se prece<strong>de</strong> con vo<strong>la</strong>tile, <strong>en</strong> lugar <strong>de</strong> const.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


20 Estructuras <strong>de</strong> Datos y Algoritmos<br />

No se valida si el espacio a continuación <strong>de</strong> <strong>de</strong>stino pue<strong>de</strong> almac<strong>en</strong>ar el string fu<strong>en</strong>te sin<br />

sobreescribir <strong>en</strong> el espacio asignado a otras variables. Este es un serio problema <strong>de</strong>l l<strong>en</strong>guaje, y<br />

se lo ha empleado para introducir código malicioso <strong>en</strong> aplicaciones que no vali<strong>de</strong>n el rebalse <strong>de</strong><br />

buffers.<br />

Esta función ti<strong>en</strong>e su prototipo <strong>de</strong>finido <strong>en</strong> <br />

Un ejemplo <strong>de</strong> uso.<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

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

char * str1 = "abc<strong>de</strong>fghi"; /* ti<strong>en</strong>e 10 caracteres, incluido el NULL que termina el string.*/<br />

int main(void)<br />

{ strcpy(string, str1);<br />

printf("%s\n", string);<br />

printf(str1); //sin string <strong>de</strong> formato<br />

return 0;<br />

}<br />

Note que <strong>en</strong> <strong>la</strong> invocación se pasan los nombres <strong>de</strong> los strings, que son consi<strong>de</strong>rados punteros<br />

constantes. En lugar <strong>de</strong> string, se podría haber escrito: &string[0].<br />

No se ocupa el retorno <strong>de</strong> <strong>la</strong> función, <strong>en</strong> este caso se usa como procedimi<strong>en</strong>to no como función.<br />

Arreglos <strong>de</strong> gran<strong>de</strong>s dim<strong>en</strong>siones no convi<strong>en</strong>e <strong>de</strong>finirlos <strong>de</strong>ntro <strong>de</strong> <strong>la</strong> función, ya que podrían<br />

producir un rebalse <strong>de</strong>l stack; es preferible <strong>de</strong>finirlos <strong>en</strong> zona estática o <strong>en</strong> el heap.<br />

Tradicionalm<strong>en</strong>te se m<strong>en</strong>ciona que el diseño <strong>de</strong> strcpy pue<strong>de</strong> ser difícil <strong>de</strong> <strong>en</strong>t<strong>en</strong><strong>de</strong>r. Se<br />

muestran a continuación, dos diseños basados <strong>en</strong> arreglos; y su evolución a códigos basados <strong>en</strong><br />

punteros.<br />

void strcpy1(char <strong>de</strong>stino[], const char fu<strong>en</strong>te[])<br />

{ int i = 0;<br />

while (1)<br />

{<br />

<strong>de</strong>stino[i] = fu<strong>en</strong>te[i];<br />

if (<strong>de</strong>stino[i] == '\0') break; // copió fin <strong>de</strong> string<br />

i++;<br />

}<br />

}<br />

Debido a que <strong>en</strong> el l<strong>en</strong>guaje C, <strong>la</strong> asignación es una expresión, y por lo tanto ti<strong>en</strong>e un valor, se<br />

pue<strong>de</strong> escribir:<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 21<br />

void strcpy2(char <strong>de</strong>stino[], const char fu<strong>en</strong>te[])<br />

{ int i = 0;<br />

while ((<strong>de</strong>stino[i] = fu<strong>en</strong>te[i]) != '\0') i++;<br />

}<br />

// Movi<strong>en</strong>do los punteros. Resolvi<strong>en</strong>do prece<strong>de</strong>ncia por asociatividad.<br />

void strcpy3(char *<strong>de</strong>stino, const char *fu<strong>en</strong>te)<br />

{<br />

while ((*<strong>de</strong>stino++ = *fu<strong>en</strong>te++) != '\0') ; //Trae el valor, luego increm<strong>en</strong>ta puntero.<br />

}<br />

// Finalm<strong>en</strong>te el fin <strong>de</strong> string '\0' equivale a valor lógico falso<br />

void strcpy4(char *<strong>de</strong>stino, const char *fu<strong>en</strong>te)<br />

{ while (*<strong>de</strong>stino++ = *fu<strong>en</strong>te++) ; }<br />

Las versiones 3 y 4, reflejan <strong>en</strong> sus argum<strong>en</strong>tos que el diseño está basado <strong>en</strong> punteros.<br />

La última versión, strcpy4 pue<strong>de</strong> ser difícil <strong>de</strong> <strong>en</strong>t<strong>en</strong><strong>de</strong>r. Empleando un compi<strong>la</strong>dor que<br />

optimice el código assembler <strong>en</strong> velocidad, <strong>en</strong>trega costos simi<strong>la</strong>res para los cuatro diseños.<br />

El assembler es para el microcontro<strong>la</strong>dor MSP430, el que dispone <strong>de</strong> instrucciones con<br />

autoincrem<strong>en</strong>tos.<br />

strcpy1o2: mov.b @R14+, 0x0(R12) ;M[R12] = M[R14]; R14++;<br />

mov.b @R12+, R15 ;R15 = M[R12]; R12++;<br />

tst.b R15 ;test <strong>de</strong> R15;<br />

jne strcpy1o2 ;Si R15 no es igual a cero repite bloque<br />

ret<br />

;Si R15 es cero retorna<br />

strcpy3o4: mov.b @R14+, R15 ; R15 = M[R14]; R14++;<br />

mov.b R15, 0x0(R12) ; M[R12] = R15<br />

inc.w R12 ; R12++;<br />

tst.b R15<br />

jne strcpy3o4<br />

ret<br />

En este caso <strong>la</strong> versión mediante arreglos emplea una instrucción m<strong>en</strong>os que <strong>la</strong>s versiones con<br />

punteros. La <strong>de</strong>cisión <strong>de</strong> cual es el mejor código resultará <strong>de</strong> <strong>la</strong> comparación <strong>de</strong> los ciclos <strong>de</strong><br />

reloj que tarda <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong>s instrucciones <strong>de</strong>l bloque repetitivo, ya que cada instrucción<br />

pue<strong>de</strong> durar difer<strong>en</strong>tes ciclos <strong>de</strong> reloj.<br />

La invocación a <strong>la</strong>s funciones se logra pasando los argum<strong>en</strong>tos vía registros R12 y R14.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


22 Estructuras <strong>de</strong> Datos y Algoritmos<br />

mov.w #str1, R10 ; dirección <strong>de</strong> str1 (<strong>de</strong>stino) <strong>en</strong> R10<br />

mov.w #str2, R11 ; dirección <strong>de</strong> str2 (fu<strong>en</strong>te) <strong>en</strong> R11<br />

mov.w @R11,R14 ; R14 = M[R11] = cont<strong>en</strong>ido str2<br />

mov.w @R10,R12 ; R12 = M[R10] = cont<strong>en</strong>ido str1<br />

call strcpy<br />

La moraleja <strong>de</strong> esto es escribir código <strong>de</strong>l cual se t<strong>en</strong>ga seguridad <strong>de</strong> lo que realiza.<br />

Y <strong>de</strong>jar a los compi<strong>la</strong>dores optimizantes el resto.<br />

3.3. Manejo dinámico <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C.<br />

3.3.1. Asignación, Refer<strong>en</strong>cias y tiempo <strong>de</strong> vida.<br />

El compi<strong>la</strong>dor asigna un espacio <strong>de</strong>terminado para <strong>la</strong>s variables y g<strong>en</strong>era <strong>la</strong>s refer<strong>en</strong>cias para<br />

accesar a <strong>la</strong>s variables <strong>de</strong>l stack y <strong>de</strong> <strong>la</strong> zona estática. El tamaño <strong>de</strong> <strong>la</strong>s variables no pue<strong>de</strong><br />

cambiarse durante <strong>la</strong> ejecución <strong>de</strong>l programa, es asignado <strong>en</strong> forma estática.<br />

El tiempo <strong>de</strong> vida <strong>de</strong> <strong>la</strong>s variables <strong>de</strong> <strong>la</strong> zona estática es <strong>la</strong> duración <strong>de</strong>l programa; <strong>la</strong>s variables<br />

<strong>de</strong>nominadas automáticas, o <strong>en</strong> <strong>la</strong> zona <strong>de</strong>l stack, exist<strong>en</strong> durante <strong>la</strong> ejecución <strong>de</strong> <strong>la</strong> función que<br />

<strong>la</strong>s refer<strong>en</strong>cia. Los frames <strong>en</strong> el stack, son asignados y <strong>de</strong>sasignados <strong>en</strong> forma dinámica durante<br />

<strong>la</strong> ejecución <strong>de</strong> <strong>la</strong>s funciones; pero <strong>en</strong> forma automática, el programador no ti<strong>en</strong>e<br />

responsabilidad <strong>en</strong> ese proceso.<br />

En el heap el programador <strong>de</strong>be solicitar <strong>la</strong> asignación <strong>de</strong> espacio, establecer <strong>la</strong>s refer<strong>en</strong>cias<br />

<strong>en</strong>tre el espacio asignado y <strong>la</strong>s variables <strong>en</strong> <strong>la</strong>s otras zonas, liberar el espacio, <strong>de</strong>sasignar <strong>la</strong>s<br />

refer<strong>en</strong>cias. Cualquier equivocación lleva a errores, <strong>en</strong> tiempo <strong>de</strong> ejecución, difíciles <strong>de</strong> <strong>de</strong>purar.<br />

Este mecanismo permite al programador t<strong>en</strong>er un mayor control <strong>de</strong> <strong>la</strong> <strong>memoria</strong>, tanto <strong>en</strong> tamaño<br />

como <strong>en</strong> tiempo <strong>de</strong> vida, pero al mismo tiempo le da <strong>la</strong> responsabilidad <strong>de</strong> administrar<strong>la</strong><br />

correctam<strong>en</strong>te. Un arreglo <strong>en</strong> <strong>la</strong> zona estática <strong>de</strong>be ser <strong>de</strong>finido con un tamaño <strong>de</strong>terminado, el<br />

cual no pue<strong>de</strong> cambiar durante <strong>la</strong> ejecución <strong>de</strong>l programa, sin embargo <strong>en</strong> el heap se pue<strong>de</strong><br />

solicitar un arreglo <strong>de</strong>l tamaño que se <strong>de</strong>see, siempre que no exceda el tamaño máximo asignado<br />

al heap.<br />

Escribir programas que manej<strong>en</strong> el heap es notablem<strong>en</strong>te más difícil, por esta razón l<strong>en</strong>guajes<br />

más mo<strong>de</strong>rnos efectúan automáticam<strong>en</strong>te <strong>la</strong> programación <strong>de</strong>l heap, y no le permit<strong>en</strong> al<br />

programador realizar esta tarea. Algunos errores <strong>de</strong> programas que manejan el heap, compi<strong>la</strong>n<br />

correctam<strong>en</strong>te, sin embargo al ejecutarlos se produc<strong>en</strong> errores difíciles <strong>de</strong> <strong>de</strong>purar.<br />

3.3.2. Funciones para el manejo <strong>de</strong>l heap.<br />

En están los prototipos <strong>de</strong> <strong>la</strong>s funciones <strong>de</strong> biblioteca que asignan y <strong>de</strong>sasignan<br />

bloques <strong>de</strong> <strong>memoria</strong>. Describiremos <strong>la</strong>s dos fundam<strong>en</strong>tales.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 23<br />

3.3.2.1. void * malloc(size_t tamaño)<br />

Solicita un bloque contiguo <strong>de</strong> <strong>memoria</strong> <strong>de</strong>l segm<strong>en</strong>to heap, <strong>de</strong>l tamaño especificado <strong>en</strong> bytes, y<br />

retorna un puntero <strong>de</strong> tipo g<strong>en</strong>érico, el cual pue<strong>de</strong> ser mol<strong>de</strong>ado (cast) a cualquier tipo<br />

<strong>de</strong>terminado, al inicio <strong>de</strong>l bloque; retorna NULL si no exist<strong>en</strong> bloques <strong>de</strong>l tamaño solicitado<br />

<strong>de</strong>ntro <strong>de</strong>l heap.<br />

El programador <strong>de</strong>be asignar el valor retornado a una variable <strong>de</strong> tipo puntero, estableci<strong>en</strong>do <strong>la</strong><br />

refer<strong>en</strong>cia; dicho puntero <strong>de</strong>be existir <strong>en</strong> alguna <strong>de</strong> <strong>la</strong>s otras zonas <strong>de</strong> <strong>memoria</strong>. La única forma<br />

<strong>de</strong> establecer <strong>la</strong> refer<strong>en</strong>cia es mediante un puntero, que inicialm<strong>en</strong>te <strong>de</strong>be apuntar a NULL.<br />

El cont<strong>en</strong>ido <strong>de</strong>l bloque <strong>de</strong>be ser inicializado antes <strong>de</strong> ser usado, ya que inicialm<strong>en</strong>te conti<strong>en</strong>e<br />

los valores que estaban previam<strong>en</strong>te almac<strong>en</strong>ados <strong>en</strong> el bloque <strong>de</strong>l heap.<br />

Suele emplearse el operador sizeof(tipo o nombre_<strong>de</strong>_variable) que retorna un valor <strong>en</strong>tero sin<br />

signo con el número <strong>de</strong> bytes con que se repres<strong>en</strong>ta el tipo o <strong>la</strong> variable, para calcu<strong>la</strong>r el tamaño<br />

<strong>de</strong>l bloque que <strong>de</strong>be solicitarse.<br />

3.3.2.2. void free(void * puntero)<br />

Free libera el bloque apuntado por puntero y lo <strong>de</strong>vuelve al heap. El valor <strong>de</strong>l puntero <strong>de</strong>be<br />

haber sido obt<strong>en</strong>ido a través <strong>de</strong> malloc; produce errores difíciles <strong>de</strong> <strong>de</strong>purar invocar a free con<br />

un puntero no <strong>de</strong>vuelto por malloc.<br />

Después <strong>de</strong> ejecutar free, el programador no pue<strong>de</strong> volver a refer<strong>en</strong>ciar datos a través <strong>de</strong> ese<br />

puntero; <strong>de</strong>bería asignarle NULL, para prev<strong>en</strong>ir errores. Tampoco pue<strong>de</strong> liberarse un bloque<br />

más <strong>de</strong> una vez.<br />

Es importante liberar el bloque cuando el programa no siga utilizando los datos que almac<strong>en</strong>ó<br />

<strong>en</strong> el bloque, <strong>de</strong> esta forma pue<strong>de</strong> volver a utilizarse dicho espacio. En caso <strong>de</strong> no hacerlo, van<br />

quedando bloques inutilizables <strong>en</strong> el heap, lo cual origina <strong>en</strong> el <strong>la</strong>rgo p<strong>la</strong>zo <strong>la</strong> fragm<strong>en</strong>tación y<br />

finalm<strong>en</strong>te el rebalse <strong>de</strong> éste.<br />

El administrador <strong>de</strong>l heap, que es invocado a través <strong>de</strong> <strong>la</strong>s funciones anteriores, manti<strong>en</strong>e sus<br />

propias estructuras <strong>de</strong> datos <strong>en</strong> <strong>la</strong>s cuales registra los bloques que están ocupados y los que están<br />

disponibles, y también el tamaño <strong>de</strong> los bloques. También interactúa con el sistema operativo<br />

para solicitar <strong>memoria</strong> adicional <strong>en</strong> caso <strong>de</strong> crecimi<strong>en</strong>to <strong>de</strong>l heap.<br />

Ejemplo 3.11. Arreglo dinámico <strong>de</strong> <strong>en</strong>teros.<br />

El sigui<strong>en</strong>te ejemplo emplea una función para crear, usar y liberar un arreglo dinámico <strong>de</strong><br />

<strong>en</strong>teros <strong>de</strong> tamaño <strong>de</strong>terminado por el argum<strong>en</strong>to size <strong>de</strong> <strong>la</strong> función.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


24 Estructuras <strong>de</strong> Datos y Algoritmos<br />

void UsaArregloDinámico(unsigned int size)<br />

{<br />

int * Arreglo;<br />

int i;<br />

/*Usar el arreglo antes <strong>de</strong> asignarlo, provoca errores */<br />

if ( (Arreglo = (int *) malloc(size * sizeof(int)) ) == NULL) {<br />

printf ("Memoria insufici<strong>en</strong>te para Arreglo\n");<br />

exit(1);<br />

}<br />

/* Se pue<strong>de</strong> usar el Arreglo. Pero sus valores no están iniciados. */<br />

for(i=0; i


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 25<br />

Ejemplo 3.12. Strings dinámicos.<br />

La función CreaString ilustra un uso común <strong>de</strong>l heap, el cual es permitir almac<strong>en</strong>ar strings <strong>de</strong><br />

<strong>la</strong>rgo variable con efici<strong>en</strong>te uso <strong>de</strong> <strong>la</strong> <strong>memoria</strong>. Si se emplean variable estáticas, el programador<br />

<strong>de</strong>be asegurar que el tamaño <strong>de</strong> éstas sea sufici<strong>en</strong>te para almac<strong>en</strong>ar los strings, lo cual lleva a<br />

reservar un espacio, que <strong>en</strong> <strong>la</strong> mayoría <strong>de</strong> <strong>la</strong>s ocasiones no será empleado. A pesar <strong>de</strong> esto pue<strong>de</strong><br />

que se pres<strong>en</strong>te un string aún mayor que el espacio fijo reservado, llevando a un rebalse <strong>de</strong>l<br />

buffer, con resultados impre<strong>de</strong>cibles <strong>en</strong> <strong>la</strong> ejecución.<br />

char* CreaString(const char* fu<strong>en</strong>te){<br />

char* nuevoString;<br />

nuevoString = (char*) malloc(strl<strong>en</strong>(fu<strong>en</strong>te) + 1);<br />

/*agrega espacio para el fin <strong>de</strong> string '\0' */<br />

assert(nuevoString != NULL);<br />

strcpy(nuevoString, fu<strong>en</strong>te);<br />

return(nuevoString);}<br />

La función CreaString retorna un puntero al string creado <strong>en</strong> el heap, e iniciado con el valor <strong>de</strong>l<br />

string fu<strong>en</strong>te. Las funciones strl<strong>en</strong> y strcpy ti<strong>en</strong><strong>en</strong> sus prototipos <strong>en</strong> <br />

La función que invoca a CreaString es responsable <strong>de</strong> liberar el espacio, lo cual se ilustra <strong>en</strong> el<br />

sigui<strong>en</strong>te segm<strong>en</strong>to:<br />

char * texto;<br />

texto = CreaString(“espacio “);<br />

/*A partir <strong>de</strong> aquí se pue<strong>de</strong> usar el string texto. */<br />

free(texto);<br />

La omisión <strong>de</strong> <strong>la</strong> liberación <strong>de</strong>l espacio limita <strong>la</strong> reutilización <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>de</strong>dicada al heap,<br />

pudi<strong>en</strong>do producir el rebalse <strong>de</strong> éste, ocasionando errores <strong>de</strong> difícil <strong>de</strong>puración. El programador<br />

<strong>de</strong>be estar consci<strong>en</strong>te <strong>de</strong> cuáles funciones invocan a malloc, para acompañar<strong>la</strong>s <strong>de</strong> <strong>la</strong> liberación<br />

<strong>de</strong>l espacio.<br />

Ejemplo 3.13. Matriz <strong>de</strong> <strong>en</strong>teros, <strong>de</strong> r r<strong>en</strong>glones y n columnas.<br />

t<br />

j<br />

i<br />

t[i][j] ó *(*(t+i)+j)<br />

t[i] ó *(t+i)<br />

Figura 3.14. Matriz. Arreglo <strong>de</strong> punteros a r<strong>en</strong>glones.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


26 Estructuras <strong>de</strong> Datos y Algoritmos<br />

La matriz se inicia con el valor val, y retorna un puntero a un puntero a <strong>en</strong>tero.<br />

int **CreaMatriz(int r, int c, int val)<br />

{ int i, j;<br />

int **t = malloc(r * sizeof(int *)); /*crea arreglo <strong>de</strong> punteros a <strong>en</strong>teros */<br />

assert(t !=NULL);<br />

for (i = 0; i < r; i++)<br />

{ t[i] = malloc(c * sizeof(int)); /*crea arreglo <strong>de</strong> c <strong>en</strong>teros */<br />

assert(t[i] !=NULL);<br />

}<br />

for (i = 0; i < r; i++)<br />

for (j = 0; j < c; j++)<br />

t[i][j] = val;<br />

return t;<br />

}<br />

void BorreMatriz(int ** p, int r)<br />

{ int i;<br />

int **t = p;<br />

for (i = 0; i < r; i++) free(t[i]); /*<strong>de</strong>b<strong>en</strong> liberarse primero los r<strong>en</strong>glones */<br />

free(t);<br />

}<br />

El sigui<strong>en</strong>te segm<strong>en</strong>to ilustra el uso <strong>de</strong> <strong>la</strong>s funciones:<br />

int **m;<br />

m=CreaMatriz(5, 6, -1);<br />

/*se usa <strong>la</strong> matriz*/<br />

BorreMatriz(m, 5); /*una vez empleada. Se libera el espacio*/<br />

La creación <strong>de</strong> listas, árboles, co<strong>la</strong>s, stacks, grafos, etc. pue<strong>de</strong> realizarse efici<strong>en</strong>tem<strong>en</strong>te <strong>en</strong> el<br />

heap.<br />

Ejemplo 3.14. Crear nodo <strong>de</strong> un árbol binario.<br />

Se <strong>de</strong>c<strong>la</strong>ra <strong>la</strong> estructura <strong>de</strong> un nodo.<br />

type<strong>de</strong>f struct tno<strong>de</strong><br />

{<br />

int valor;<br />

struct tno<strong>de</strong> *left;<br />

struct tno<strong>de</strong> *right;<br />

} nodo, * pnodo;<br />

La sigui<strong>en</strong>te función solicita y asigna el nodo obt<strong>en</strong>ido <strong>en</strong> el heap, si es que había espacio<br />

disponible, retornado un puntero al nodo, y <strong>de</strong>jando iniciadas <strong>la</strong>s variables <strong>de</strong>l nodo.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 27<br />

pnodo CreaNodo(int dato)<br />

{<br />

pnodo pn=NULL;<br />

if ( (pn= (pnodo) malloc(sizeof(nodo))) ==NULL) exit(1);<br />

else<br />

{<br />

pn->valor=dato; pn->left=NULL; pn->right=NULL;<br />

}<br />

return(pn);<br />

}<br />

void LiberaNodo( pnodo pn)<br />

{<br />

free( pn); //Libera el espacio<br />

}<br />

El sigui<strong>en</strong>te segm<strong>en</strong>to ilustra el uso <strong>de</strong> <strong>la</strong>s funciones.<br />

pnodo root=NULL; /* el espacio <strong>de</strong> <strong>la</strong> variable root existe <strong>de</strong>s<strong>de</strong> su <strong>de</strong>finición.<br />

root=CreaNodo(5); //se pega el nodo a <strong>la</strong> raíz<br />

LiberaNodo(root);<br />

Profesor Leopoldo Silva Bijit 20-01-2010


28 Estructuras <strong>de</strong> Datos y Algoritmos<br />

Índice g<strong>en</strong>eral.<br />

CAPÍTULO 3 .............................................................................................................................................. 1<br />

ADMINISTRACIÓN DE LA MEMORIA EN C. .................................................................................... 1<br />

3.1. MANEJO ESTÁTICO DE LA MEMORIA. .................................................................................................. 1<br />

3.2. MANEJO AUTOMÁTICO DE LA MEMORIA EN C. .................................................................................... 2<br />

3.2.1. Asignación, Refer<strong>en</strong>cias y tiempo <strong>de</strong> vida. ................................................................................. 2<br />

3.2.2. Argum<strong>en</strong>tos y variables locales. ................................................................................................ 2<br />

Ejemplo 3.1. Función con dos argum<strong>en</strong>tos <strong>de</strong> tipo valor, con dos locales, retorno <strong>de</strong> <strong>en</strong>tero. ......................... 3<br />

3.2.3. Visión lógica <strong>de</strong>l stack. .............................................................................................................. 4<br />

Ejemplo 3.2. Riesgos <strong>de</strong> <strong>la</strong> <strong>de</strong>saparición <strong>de</strong>l frame. ......................................................................................... 4<br />

3.2.4. Copia <strong>de</strong> argum<strong>en</strong>tos. ................................................................................................................ 5<br />

Ejemplo 3.3. Una función f que invoca a otra función g. ................................................................................. 5<br />

Ejemplo 3.4. La misma función anterior invocada dos veces........................................................................... 8<br />

3.2.4. Recursión. .................................................................................................................................. 9<br />

Ejemplo 3.5. Diseño recursivo. ...................................................................................................................... 10<br />

Ejemplo 3.6. Diseño iterativo. ........................................................................................................................ 11<br />

3.2.6. Parámetros pasados por refer<strong>en</strong>cia y valor único <strong>de</strong> retorno. ................................................ 11<br />

Ejemplo 3.7. Comunicación <strong>de</strong>l valor retornado por una función. ................................................................. 11<br />

Ejemplo 3.8. Se <strong>de</strong>sea diseñar función que retorne dos resultados. ............................................................... 12<br />

Paso por refer<strong>en</strong>cia. ................................................................................................................................... 12<br />

Retorno <strong>de</strong> estructura. ............................................................................................................................... 13<br />

Retorno <strong>en</strong> arreglo. .................................................................................................................................... 14<br />

3.2.6. Evitación <strong>de</strong> <strong>la</strong> copia <strong>de</strong> argum<strong>en</strong>tos que ocupan muchos bytes. ............................................ 15<br />

Ejemplo 3.9. Árbol binario. ............................................................................................................................ 15<br />

Ejemplo 3.10 Manipu<strong>la</strong>ción <strong>de</strong> strings. .......................................................................................................... 16<br />

Definición <strong>de</strong> string. .................................................................................................................................. 16<br />

a) Arreglo <strong>de</strong> caracteres. ............................................................................................................................ 16<br />

b) Puntero a carácter. ................................................................................................................................. 17<br />

c) Strcpy. Copia el string fu<strong>en</strong>te <strong>en</strong> el string <strong>de</strong>stino. .............................................................................. 18<br />

3.3. MANEJO DINÁMICO DE LA MEMORIA EN C. ....................................................................................... 22<br />

3.3.1. Asignación, Refer<strong>en</strong>cias y tiempo <strong>de</strong> vida. ............................................................................... 22<br />

3.3.2. Funciones para el manejo <strong>de</strong>l heap. ........................................................................................ 22<br />

3.3.2.1. void * malloc(size_t tamaño) ............................................................................................................ 23<br />

3.3.2.2. void free(void * puntero) .................................................................................................................. 23<br />

Ejemplo 3.11. Arreglo dinámico <strong>de</strong> <strong>en</strong>teros. ............................................................................................. 23<br />

Ejemplo 3.12. Strings dinámicos. .............................................................................................................. 25<br />

Ejemplo 3.13. Matriz <strong>de</strong> <strong>en</strong>teros, <strong>de</strong> r r<strong>en</strong>glones y n columnas. ................................................................ 25<br />

Ejemplo 3.14. Crear nodo <strong>de</strong> un árbol binario. .......................................................................................... 26<br />

ÍNDICE GENERAL. .................................................................................................................................... 28<br />

ÍNDICE DE FIGURAS. ................................................................................................................................ 29<br />

Profesor Leopoldo Silva Bijit 20-01-2010


Manejo <strong>de</strong> <strong>la</strong> <strong>memoria</strong> <strong>en</strong> C. 29<br />

Índice <strong>de</strong> figuras.<br />

FIGURA 3.1. SEGMENTOS DE MEMORIA. ........................................................................................................ 1<br />

FIGURA 3.1A. STACK DESPUÉS DE INVOCAR A LA FUNCIÓN ........................................................................... 3<br />

FIGURA 3.2. STACK AL SALIR DE LA FUNCIÓN. .............................................................................................. 4<br />

FIGURA 3.3. STACK ANTES DE INVOCAR A LA FUNCIÓN G. ............................................................................ 6<br />

FIGURA 3.4. AL ENTRAR EN LA FUNCIÓN G. .................................................................................................. 6<br />

FIGURA 3.5. STACK JUSTO ANTES DE SALIR DE LA FUNCIÓN G. ..................................................................... 7<br />

FIGURA 3.6. STACK AL SALIR DE LA FUNCIÓN G. ........................................................................................... 7<br />

FIGURA 3.7. STACK AL SALIR DE LA FUNCIÓN F. ........................................................................................... 8<br />

FIGURA 3.8. STACK DESPUÉS DE LA SEGUNDA INVOCACIÓN A F. ................................................................... 8<br />

FIGURA 3.9. AL SALIR DE LA SEGUNDA INVOCACIÓN. ................................................................................... 9<br />

FIGURA 3.10. ÁRBOL BINARIO. ................................................................................................................... 15<br />

FIGURA 3.11. REPRESENTACIÓN EN MEMORIA DE UN STRING. .................................................................... 17<br />

FIGURA 3.12. PUNTERO A CARÁCTER Y EL STRING VINCULADO. ................................................................. 18<br />

FIGURA 3.13. COPIA DE STRING. ................................................................................................................. 18<br />

FIGURA 3.14. MATRIZ. ARREGLO DE PUNTEROS A RENGLONES. ................................................................. 25<br />

Profesor Leopoldo Silva Bijit 20-01-2010

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!