-
Manejo de Punteros y Memoria Dinmica en C
Every computer, at the unreachable memory address 0x-1, stores a
secret. I found it, and it is that all
humans ar-- SEGMENTATION FAULT.
Introduccin Punteros
Quesunpuntero? Cmodeclaramosunpuntero?
Cmoinicializamosunpuntero?(Malloc)
Operacionesdepunteros Direccindeunavariable(&)
Desreferencia(*) Desreferenciaenestructuras(>)
Funcionesqueretornanpunteros
Tuprogramachoreamemoria!(A.K.AMemoryLeaks) AritmticadePunteros
Otrosalloc Elproblemadeltipodedatoint EXTRA:PunterosaFunciones
Linkstiles
1
-
Introduccin
AntesdemeternosafondoenlascuestionesdelamanipulacindelamemoriadinmicaenC,definiremosalgunosconceptosbsicos.Un
proceso es un programa en ejecucin. Cuando nosotros abrimos el
binario de nuestro programa, se crea la imagen del proceso y
comienza a ejecutarse. Podra decirse que un
procesoestdivididoencuatrosegmentos bsicos:1
Cdigo Datos(globales) Heap Stack
La memoria esttica es memoria que se reserva al declarar
variables de cualquier tipo de dato: int, float, char, double,
estructuras como as tambin los punteros a tipos de datos (por 2
ejemplo: int*, char*, double*), y se aloja en el stack del
proceso. En ste caso, el programador no podr modificar el espacio
en memoria que ocupan ni tampoco tendr que encargarse de
liberarla.Al producirse una llamada a una funcin, se almacena en el
stack del proceso la direccin a la
quedeberetornarlaejecucintrasfinalizarlallamada,yotrosdatosadicionales
.3
La memoria dinmica es memoria que se reserva en tiempo de
ejecucin y se aloja en el heap del proceso (los datos apuntados por
los punteros). En C, el programador deber reservar dicha
memoriaparasuusoytambintendrlaresponsabilidaddeliberarlacuandonolautilicems.Una
diferencia importante es que el tamao de la memoria dinmica se
puede ir modificando durante la ejecucin del programa. Qu quiere
decir sto? Que, por ejemplo, podras ir agrandando/achicando una
determinada estructura (por ejemplo, un array) a medida
quelonecesits.
1Ojo,nonosestamosrefiriendoalesquemadesegmentacinparaelmanejodelamemoria.2Enelprximocaptulosedefinirelconceptodepuntero.3Porejemplo,registrosdelaCPUalmomentodelaejecucindelproceso.
2
-
Algunas ventajas que ofrece la memoria dinmica frente a la
memoria esttica es que podemos reservar espacio para variables de
tamao no conocido hasta el momento de la ejecucin (por ejemplo,
para listas o arrays de tamaos variables), o bloques de memoria
que, mientras mantengamos alguna referencia a l, pueden sobrevivir
al bloque de cdigo que lo cre. Sin embargo, como una vez dijo el to
Ben a Spiderman: Todo poder conlleva una gran responsabilidad.
4
Todos los datos tienen un tiempo de vida, nada persiste para
siempre. En C, hay tres tipos de duracin:
Esttica: son aquellas variables que se crean una nica vez junto
con la creacin del proceso y se destruyen junto con la destruccin
del mismo, son nicas y generalmente pueden ser utilizadas desde
cualquier parte del programa. Para generar una variable esttica se
la puede declarar por fuera de la funcin principal (arriba del
main() por ejemplo),obienusandoelcalificadorstatic.
Automtica: son aquellas variables locales que no son declaradas
con el especificador static. Se crean al entrar al bloque en el que
fueron declaradas y se destruyen al salir de ese bloque. Por
ejemplo, el tiempo de vida de las variables internas de una funcin
es lo quetomeejecutarla.
Asignada: es la memoria que se reserva de forma dinmica (en el
heap) y que se explicmsarriba.
4Enelcomicoriginal,elnarradoresquiendiceestafrasealfinaldelprimervolumen/NERD.
3
-
Punteros
Qu es un puntero? Un puntero es la direccin de algn dato en
memoria. Un puntero NO es el dato en s
mismo,sinosuposicinenlamemoria.Tambinseloconocecomoreferenciaamemoria.
Cmo declaramos un puntero? Supongamos que queremos declarar un
puntero a un tipo de datos int. En C sto se escribe de
lasiguientemanera:
int *p;
Bien,yadeclaramoselpuntero.Sinembargo,noestinicializadoyapuntaabasura.
Paraquenuestropunteronoesttriste,vamosadarlealgoparaqueapunte!.
Cmo inicializamos un puntero? (Malloc) La memoria se puede
reservar o liberar dinmicamente, es decir, segn necesitemos. Para
sto hay varias funciones estndar, que se encuentran en la
biblioteca estndar de C, en el 5
encabezadostdlib.h.Una de ellas es la funcin malloc , la cual
sirve para solicitar un bloque de memoria del tamao
6indicadoporparmetro.Devuelveunpunteroalazonadememoriaconcedida:void*
malloc (unsigned numero_de_bytes);
5LatraduccincorrectadelibraryesbibliotecayNOlibrera.6malloc()esunaabreviaturadememoryallocate.
4
-
El tamao se especifica en bytes. Malloc nos garantiza que la
zona de memoria concedida
noestocupadaporningunaotravariable.Groso,no?.Eso s, si malloc no
puede otorgarnos dicha zona de memoria, devuelve un puntero a NULL.
Por ende, cada vez que hacemos una llamada a malloc deberamos
chequear que no devuelveunpunteronulo.Como dijimos antes, malloc
devuelve un puntero a la zona de memoria concedida. Sin
embargo,stepunterodevueltonosabeaqutipodedatosapunta(void*significasto).
7Antes de llamar a malloc tenemos que saber cuntos bytes queremos
reservar en memoria. Como nuestro tipo de datos va a ser un int,
vamos a reservar 4 bytes. Entonces, la llamada
deberaquedaras:malloc (4); 8
Entonces, por cada tipo de puntero que tenga que declarar me
tengo que acordar los bytes
queocupa?.Nonecesariamente,podemosrecurriraloperadorsizeof.El
operador sizeof recibe por parmetro un tipo de dato y nos devolver
el tamao en bytes de ste. Tambin podemos pasarle una variable y l
se encargar de chequear el tipo y hacer el
clculo.Entonces,nuestrallamadaamallocquedaraas:malloc(sizeof(int));
Esta opcin es altamente preferible, no slo por su legibilidad y
correctitud, sino por su declaratividad: yo no quiero reservar 4
bytes, sino los bytes necesarios para guardar un int. Cuntos sean
esos bytes, no me interesa: es problema de la implementacin, y hay
alguien que loresuelveporm.Tal vez ste ejemplo suene muy trivial,
pero si nosotros tenemos que reservar en memoria un espacio
equivalente al tamao de una determinada estructura (struct)
tendramos que saber cuntos bytes requiere cada tipo de datos que la
estructura contenga y sumarlos. Para sto podemos recurrir a
sizeof(struct miEstructura) para saber la cantidad de bytes que
ocupa la
misma.Bien,yapedimoslosbytes,ahorasloquedaasignarloanuestropuntero:int
*p = malloc(sizeof(int));
7EnC89estbamosobligados,cadavezquellambamosamalloc,acastearelpunteroretornadopormallocaltipodedatoqueestemosusando.EnC99estoyanoesnecesario.8Sicorremosstoenunacomputadoraconunsistemaoperativoycompiladorde32bitsnovamosatenerproblemas.Sinembargo,sinuestrosistemaoperativo/compiladoresdeotracantidad(64bits)vamosatenerproblemas.Acestexplicado.
5
-
Enresumen,nuestrocdigoinicialquedaradestamanera:#include
int main(void){ int *p = malloc(sizeof(int)); return 0; } Sin
embargo, falta algo ms!. Si bien reservamos el espacio en memoria
al que va a apuntar
nuestropuntero,qudatocontieneseespacio?.Loquecontieneesbasura!.Paraasignarleunvalor,usamoseloperador*(asterisco),eloperadordedesreferencia
deC:9
#include int main(void){ int *p = malloc(sizeof(int)); *p = 2;
return 0; } Al hacer *p estamos diciendo al contenido de p o a lo
apuntado por p. Si hiciramos p = 2
estaramosmodificandoalpunterop,ynoalvalorapuntadoporelpunterop.Entonces,parainicializarunpuntero,tenemosquerealizardospasos:
Reservarelespacioenmemoria.
Darleunvalorinicialaldatocontenidoenseespacioenmemoria.
9http://en.wikipedia.org/wiki/Dereference_operator
6
http://www.google.com/url?q=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FDereference_operator&sa=D&sntz=1&usg=AFQjCNHtYESELeXGNUwHCzk4MlTuD2j11Q
-
Operaciones de punteros
Direccin de una variable (&) Un operador muy importante es
& (ampersand) el cual nos devuelve la direccin en
memoriadesuparmetro.Podscomprobarstoejecutandoelsiguientecdigo:#include
#include int main(void){ int *p = malloc(sizeof(int)); *p = 1;
printf("p = %d\n", p); printf("&p = %p\n", &p); return 0;
}
%dy%psonespecificadoresdeformato.
%destdiciendoqueensapartedelalneavaaimprimirunvalorentero. %p est
diciendo que en sa parte de la lnea va a imprimir la direccin en
memoria del
dato,porejemplo0x7fff78c088d8.Ojo!Eloperador&sepuedeusarcontodotipodevariablesyaquetodoestcontenidoenlamemoria.
Desreferencia (*) Veamoselsiguientecdigo: #include #include int
main(void){
int i = 1; int *p;
printf("Antes i vale: %d\n", i);
p = &i; //p apunta a i *p = 2; //se le asigna a donde este
apuntando p (i) el valor 2
7
-
printf("Ahora i vale: %d y el contenido de p vale: %d\n", i,
*p); return 0;
}
Ejecutamoselcdigoyveremosenconsolalosiguiente:Antesivale:1Ahoraivale:2yelcontenidodepvale:2
Declaramosunavariableidetipointconelvalor1. int i = 1;
Declaramosunpunteropauntipodedatoint.
int *p;
Imprimimosporpantallaelvalordei,mostrar1.
printf("Antes i vale: %d\n", i);
Leasignamosalpunteropladireccindememoriadei.
p = &i;
Le asignamos a la porcin de memoria a la que apunta p(con el
paso anterior hicimos
queapunteai)elvalor2.Astoseloconocecomodesreferenciar. *p =
2;
Imprimimoslosvaloresdeambos.Ambosvalen2.
printf("Ahora i vale: %d y el contenido de p vale: %d\n", i,
*p);Ambos tienen el mismo valor!. Y eso es porque el puntero p est
apuntando a la misma porcin
dememoriaqueitieneasignada.Porende,sepuedemanipulardichodatodesdeelpunterop.Con
el operador * (asterisco) podemos acceder al contenido al que
apunta nuestro puntero.
8
-
Loqueacabamosdehacerfuemanipularunaporcindememoriaajenaatravsdeunpuntero.Yparaqumesirve?Veamoselsiguienteejemplo:#include
#include void sumarUno(int unaVariable){
unaVariable = unaVariable + 1; printf("Dentro de la funcion, i
vale: %d\n", unaVariable);
} int main(void){
int i = 1;
printf("Antes de ejecutar la funcion, i vale: %d\n", i);
sumarUno(i); printf("Despues de ejecutar la funcion, i vale: %d\n",
i);
return 0;
} Tenemos una funcin sumarUno que le suma 1 a la variable que le
haya sido pasada por
parmetroylaimprimeenpantalla.Ejecutamoselcdigoyveremosenconsolalosiguiente:Antesdeejecutarlafuncion,ivale:1Dentrodelafuncion,ivale:2Despuesdeejecutarlafuncion,ivale:1En
C, cuando nosotros llamamos a una funcin y le pasamos parmetros,
los valores que recibe son copiados en una direccin de memoria
distinta y son operados desde all hasta que el bloque de cdigo
termina de ejecutarse. A sto se le conoce como parmetros por valor.
En otras palabras, no podremos modificar variables desde funciones
para que persistan luego dequefinalicelaejecucindelafuncin.Si
nosotros quisiramos que el cambio persista luego de la ejecucin de
la funcin, tendramos que decirle, a nuestra funcin, que lo que va a
recibir como parmetro es una referencia a
memoria,tambinconocidacomopuntero!.
9
-
Entonces, en vez de void sumarUno(int unaVariable) vamos a
tipear void sumarUno(int *unaVariable). Donde antes tenamos
unaVariable = unaVariable + 1; vamos a tener que poner
(*unaVariable) = (*unaVariable) + 1;. Por qu?, porque lo que
deseamos
modificareseldatoalqueapunta.stoesloquevimosantescomodesreferenciar.Sin
embargo, si corremos sto como est, no va a funcionar porque ahora
cuando llamamos a la funcin tenemos que pasarle un puntero o una
direccin de memoria. Como vimos antes,
podemosutilizareloperador&enstecaso.Entonces, en vez de hacer
llamar a la funcin de sta manera: sumarUno(i); lo hacemos de
stamanera:sumarUno(&i);.As,nuestrocdigoquedardelasiguienteforma:#include
#include void sumarUno(int *unaVariable){
(*unaVariable) = (*unaVariable) + 1; printf("Dentro de la
funcion, i vale: %d\n", *unaVariable);
} int main(void){
int i = 1;
printf("Antes de ejecutar la funcion, i vale: %d\n", i);
sumarUno(&i); printf("Despues de ejecutar la funcion, i vale:
%d\n", i);
return 0;
}
Yalcorrerlo,enconsolaleeremoslosiguiente:Antesdeejecutarlafuncion,ivale:1Dentrodelafuncion,ivale:2Despuesdeejecutarlafuncion,ivale:2
Desreferencia en estructuras (->)
Esteoperadorofreceunasintaxisalternativaalaccederalosdatosdeunpunterohaciauntipodedatosestructura.Ejemplo:
10
-
Supongamosquetenemosuntipot_personadefinidodelasiguientemanera:typedef
struct { char[20] nombre; char[20] apellido; int edad; } t_persona;
Queremoscrearunpunterohaciaunaestructurat_personaquecontengacomonombreEsteban,apellidoTrabajosyedad56.Siquisieseaccederalnombredeunt_persona,tendraprimeroqueaccederalaestructurayluegoasusdatos.Vamosahacerstoutilizandoeloperador*.t_persona
*p = malloc(sizeof(t_persona)); (*p).nombre = "Esteban";
(*p).apellido = "Trabajos"; (*p).edad = 56;
Sinembargo,Cofreceunaalternativaalasintaxis(*p).medianteeloperador>(flechita).Enstecasoparaquequedemslimpioelcdigo.t_persona
*p = malloc(sizeof(t_persona)); p->nombre = "Esteban";
p->apellido = "Trabajos"; p->edad = 56; Eloperador>
sepuedeutilizarsitenemosestructurasanidadas.Supongamosquealcampo10t_personaleagregamosuncampomsparaquehagareferenciaaunhijo:typedef
struct { char[20] nombre; char[20] apellido; int edad; t_persona*
hijo; } t_persona;
Siquisiramosaccederalnombredelhijo,bastaracontipear:p->hijo->nombre
10Ojo,sondoscaracteres:y>(menosymayor).Nobusquesuncaracterquesealaflecha:)
11
-
12
-
Funciones que retornan punteros Como vimos antes, el pasarle un
puntero como argumento a una funcin resulta de
mucha utilidad si queremos cambiar el contenido de esa variable
dentro de la funcin, o si no queremos copiar toda la informacin
devuelta, pero... Qu hay de las funciones que devuelven
punteros?Tomemosesteejemplo:
char* copiar(char* palabra){
char* tmp = malloc(sizeof(char) * strlen(palabra) + 1);
memcpy(tmp, palabra, strlen(palabra)); tmp[strlen(palabra)] = \0;
return tmp;
}
Estafuncintrivialnosvaaayudaracomprenderlautilidaddeesteconcepto.Desglosmosla:
Lafuncinrecibeunstring,ydevuelveunpunteroaunaposicinenmemoriadondese
encuentraunacopiadeestapalabra.
Primeroalocaunespacioconsecutivodeltamaodelapalabra(+1porquetodoslos
stringsterminanconun\0) malloc(sizeof(char) * strlen(palabra) +
1);
yasociaesteespacioaunavariabletmp char* tmp =
Luegocopialapalabraporargumentoalsegmentoreservado memcpy(tmp,
palabra, strlen(palabra)); 11
Insertael\0faltanteenlaltimaposicin,usandoelconjuntodebytescomosifueraunarray.
tmp[strlen(palabra)] = \0; 12
yretornaelpunteroalnuevosectordememoriaconlacopiadelapalabra
return tmp; Es notable mencionar que la funcin es la que crea el
segmento en memoria, y que si se llamara n veces, creara n
segmentos de memoria, cada uno con una copia de la palabra, por lo
tanto el que reciba el segmento de memoria tiene la responsabilidad
de liberarlo cuando no lo necesitems.
11memcpy(dest,source,length),dondedesteslavariabledondeseguardareldatocopiado,sourceeslavariablededondesecopiarlosdatosylengthlacantidaddebytesquedeseamoscopiar.12Unstringsediferenciadeunstreamenquealfinaldelarraydecaractereshayuncarcterespecial\0queindicafindecadena.Comonuestroobjetivoescopiarunstring,debemosagregarstecaracteramano.
13
http://www.google.com/url?q=http%3A%2F%2Flinux.die.net%2Fman%2F3%2Fmemcpy&sa=D&sntz=1&usg=AFQjCNEBko5F1PcfCTDbZCnNAeRuQRsocQhttp://www.google.com/url?q=http%3A%2F%2Flinux.die.net%2Fman%2F3%2Fmemcpy&sa=D&sntz=1&usg=AFQjCNEBko5F1PcfCTDbZCnNAeRuQRsocQ
-
Tu programa chorea memoria! (A.K.A Memory Leaks) Al hacer el
malloc , uno reserva un segmento continuo de memoria de el tamao
que se le 13
indica, y un poquito ms. Este extra contiene informacin sobre el
segmento, como el tamao, y
otrametadataqueelsistemaoperativocreaconveniente.Cuando nuestro
proceso finaliza, el SO se encarga de liberar toda la memoria
asociada a nuestro proceso. Sin embargo, durante la ejecucin de
nuestro proceso, es responsabilidad nuestra liberar la memoria
asignada dinmicamente para poder reutilizarla. De no hacer
sto,nuestrosprocesosestaranocupandomsmemoriadelaquerequierenrealmente.Seguramente
dirs: yo tengo 4GB de memoria, qu me importa si consumo un toque ms
de memoriaono?,deltimamecompro4GBmsqueestn200pman.En parte tens
razn y en parte no, por qu?, porque si nuestro proceso no finaliza
nunca (es decir, es un while gigante) y se encarga de hacer mucho
procesamiento, creeme que vas a empezar a ocupar memoria a lo loco
a tal punto que en un momento se va a ver afectada la performance
del proceso y te vas a quedar sin memoria. Y cuando te queds sin
memoria, el
SOpriorizasuvidaymataelprocesoquelaestpidiendo.Generalmente, las
fugas de memoria o memory leaks se dan cuando perdemos la
referenciadeunpunteroenalgnpunto.Retomemoselejemploanterior:
char* copiar(char* palabra){ char* tmp = malloc(sizeof(char) *
strlen(palabra) + 1); memcpy(tmp, palabra, strlen(palabra));
tmp[strlen(palabra)] = \0; return tmp;
}
Si nosotros no devolvisemos el puntero al que le estamos
asignando un espacio en memoria dinmica, estaramos perdiendo la
referencia al bloque ese y, por ende, pasara un memory leak (cmo
liberaramos el bloque? cmo adivinamos cul de TOOODAS las posiciones
de memoria que hay en nuestra computadora le corresponde a nuestro
bloquecito?). sto implica que ese espacio que reservamos nunca ms
vamos a poder volver a utilizarlo durante la
ejecucindelproceso.Ysinomecres,probsto:#include #include void
copiar(char* palabra){
char* tmp = malloc(sizeof(char) * strlen(palabra) + 1);
13Enrealidad,cualquieroperacindereservadememoriadinmica,comocalloc(),malloc()orealloc()
14
-
memcpy(tmp, palabra, strlen(palabra)); tmp[strlen(palabra)] =
\0;
} void main(void){
while(1){ copiar("no liberar la memoria dinamica que reservamos
cuando ya no
necesitamos de ella, es sinonimo de herejia"); }
}
Porsuerte,existeunasolucinfcil,elgloriosofree:
free(unPuntero)
Esta simple funcin se encarga de buscar el segmento que habamos
reservado, y marcarlo como libre para que otro o nosotros lo
volvamos a usar (notar que no limpia la informacin que
habaenelsegmento).Doscosasatenerencuenta:
1. Si tenemos una funcin que reserve memoria, podemos hacer free
fuera de ella, siempre y cuando tengamos alguna referencia al
espacio en memoria. Por ejemplo: #include char* reservarMemoria(int
n){ return malloc(n*sizeof(char)); } int main(void){ char* array;
array = reservarMemoria(3); //Reserva 3 chars consecutivos
free(array); }
2. Si tenemos estructuras con punteros, el orden de liberacin es
muy importante. Consideremoselejemplopreviamentevisto: char*
copiar(char* palabra){
char* tmp = malloc(sizeof(char) * strlen(palabra) + 1);
memcpy(tmp, palabra, strlen(palabra)); tmp[strlen(palabra)] = \0;
return tmp;
}
15
-
int main(void){
char** nombres; //Grabo espacio para 4 punteros a nombres
nombres = malloc(sizeof(char*) * 4);
//Grabo cada una de las palabras nombres[0] = copiar("Joaquin");
//7 + 1 chars nombres[1] = copiar("Matias"); //6 + 1 chars
nombres[2] = copiar("Santiago"); //8 + 1 chars nombres[3] =
copiar("Gaston"); //6 + 1 chars
free(nombres);
}
El programa reserva espacio para 4 punteros, y despues carga
cada uno de esos punteros con los nombres Joaquin, Matias, Santiago
y Gaston. Si yo libero la variable nombres, entonces no tengo forma
de liberar los restantes 35 chars que reserv para las letras, por
lo que
perd(leakie)35bytesdememoria.Laformacorrectadeterminarelprogramasera:
int i; for(i=0; i
-
queexistirunfreeporcadamalloc.Aclaracin:sibienfree()noborralosdatosyelpunterosigueestando,loquehaceinternamenteesdejarlibre(valgalaredundancia)sebloquedememoriaparaelrestodelosprocesos.soquieredecirqueotroprocesopuedepedirreservarunespaciodememoriaycasualmenteseleasignese.Nosotros,conservandoelpuntero,podramosseguirmanipulandosebloqueperoseraarriesgarnosaquesedeninconsistenciasyaqueahoranohayunprocesoslotocandodichoespacio,sinodos.Porende,pensarenutilizarendichascondicionesserapsimo.Laimportantedequereservemosmemoriaesquenosaseguramosdequeningnprocesomsaccedaaseespacio(salvoqueledemospermiso,perosoesotrahistoria).
17
-
Aritmtica de Punteros El lenguaje nos permite sumar o restar
cantidades enteras al puntero para que apunte a
direccionesdememoriadistintas,astoseloconocecomoaritmticadepunteros.Sirve
mucho a la hora de hacer manejos de memoria y es una sintaxis
alternativa a la del accesodeelementosdeunarray.Con un puntero, las
expresiones p+1, p+2, p+3, p+n tienen sentido. La expresin p+n es
un puntero que apunta a la direccin de p sumndole n veces el
espacio ocupado por un elemento del tipo al que apunta. Es decir,
la expresin sumada NO es el nmero de bytes que se
sumanaladireccin,eselnmerodeelementosdeltipoalqueapuntaalpuntero.Utilicemoselejemploanterior.char**
nombres; //Grabo espacio para 4 punteros a nombres nombres =
malloc(sizeof(char*) * 4); //Grabo cada una de las palabras
nombres[0] = copiar("Joaquin"); Ambos tienen el mismo valor!. Y eso
es porque el puntero p est apuntando a la misma porcin de memoria
que i tiene asignada. Por ende, se puede manipular dicho dato desde
el puntero p. Entonces, si quisiramos acceder al segundo elemento
del array de strings (nombres[1]), la sintaxis equivalente sera
*(nombres + 1). Teniendo en cuenta sto, en un array, *nombres sera
el primer elemento del array, entonces quedara: *(nombres+1) =
copiar("Matias"); //6 + 1 chars *(nombres+2) = copiar("Santiago");
//8 + 1 chars *(nombres+3) = copiar("Gaston"); //6 + 1 chars
18
-
Otros alloc calloc(n, bytes): reserva memoria para un array de n
elementos que ocupan un tamao
de x bytes cada uno, adems inicializa los bytes con un \0. Por
ejemplo, supongamos
quequeremosreservarmemoriaparaunarrayde5enteros,entonces:
int *arrayEnteros = calloc(5, sizeof(int))
Elequivalente,conlafuncinmalloc,sera:
int *arrayEnteros = malloc(5*sizeof(int)) realloc(*unPuntero,
bytes): cambia el tamao del bloque de memoria apuntado por
unPuntero a uno de x bytes. Devuelve un puntero al bloque con el
tamao indicado. Es importante saber que los datos no son alterados
y se guardan en el nuevo bloque siempre y cuando le hayamos
reasignado un tamao mayor o igual al del bloque anterior. Los bytes
agregados (es decir, si el tamao total que le pasamos por parmetro
es mayor al tamao del bloque apuntado por unPuntero) no estn
inicializados.Debemostenercuidadoenlosparmetrosquelepasemosporque:
SiunPunteroesNULL,lafuncinsecomportacomounmalloc(bytes). Si
unPuntero no es NULL y bytes = 0, la funcin se comporta como un
free(unPuntero).
19
http://www.google.com/url?q=http%3A%2F%2Flinux.die.net%2Fman%2F3%2Fcalloc&sa=D&sntz=1&usg=AFQjCNHEy_LsrnLzucbxSgkfFLXhdK3pGghttp://www.google.com/url?q=http%3A%2F%2Flinux.die.net%2Fman%2F3%2Frealloc&sa=D&sntz=1&usg=AFQjCNFRqefEU1aJHmUJ-3JBLjr6mI2zsQ
-
El problema del tipo de dato int Sin embargo, con el tipo de
datos int hay un tema muy importante a considerar. Dependiendo de
la arquitectura, sistema operativo, y del compilador en s mismo, el
tipo de dato int va a tener un tamao u otro. Generalmente int tiene
un tamao equivalente al tamao de la palabra del procesador. Por
ende, si estamos en una arquitectura de 32 bits, la palabra tendr
un tamao de 32 bits, int tendr un tamao de 32 bits o 4 bytes (8
bits = 1 byte). Si estamos en una arquitectura de 64 bits, la
palabra tendr un tamao de 64 bits, int tendr un tamao de 64
bitso8bytes.Entonces, si nosotros reservamos (malloc) 4 bytes para
un tipo int en una mquina con un procesador de 32 bits no vamos a
tener ningn problema pero si lo hacemos en una de 64 bits
vaavolartodoporlosaires.Parasolucionaresteproblemapodemosconsiderardosopciones:
Recurrir a un tipo de datos que no dependa de la arquitectura de
nuestro procesador, es decir, que tengan un tamao fijo lo corramos
donde lo corramos. Por ejemplo, int32_t o int64_t. 14
Utilizar el operador el sizeof() para que se acople a la
arquitectura en la que est corriendo.Ej:malloc(sizeofint)
Normalmente vamos a optar por usar el operador sizeof, los tipos
de dato entero de tamao fijo los vamos a dejar para aquellos
momentos en que no podemos dejar el tamao del entero al criterio
del sistema operativo. Un uso comn para estos tipos de datos es
cuando queremos realizar un intercambio de datos entre dos
computadoras diferentes,peroesoloveremosenelproximocapitulo.
14Msinfoac
20
http://www.google.com/url?q=http%3A%2F%2Fwww.nongnu.org%2Favr-libc%2Fuser-manual%2Fgroup__avr__stdint.html&sa=D&sntz=1&usg=AFQjCNGhF8muQqwJiCivlyUt9Fvkpmmq-Q
-
EXTRA: Punteros a Funciones Un puntero a una funcin es una
variable que almacena la direccin en memoria de una funcin que
luego podr ser invocada desde dicho puntero. Los punteros a
funciones se declaran de una manera similar a los punteros que
conocemos hasta ahora, con la diferencia de que hay que aclarar el
tipo de valor que retorna y los tipos de datos de los parmetros que
acepta. Al fin yalcabo,comounafuncin!.Porejemplo:void
(*f)(int,int);
Constoestamosdeclarandounpunterofaunafuncinquerecibirporparmetrodosenteros(int,int)ynoretornaningnvalor.Enelsiguientecdigoveremoscmounposibleuso:#include
#include void imprimirValor(int x) { printf("%d\n", x); } int
main() { void (*punteroAFuncion)(int); punteroAFuncion =
&imprimirValor; punteroAFuncion(1); return 0; }
Qusucedeenelmain?
Declaramosunpunteroaunafuncinquerecibeunenteroynoretornaningnvalor.
void (*punteroAFuncion)(int);
LeasignamosalpunteroladireccinenmemoriadelafuncinimprimirValor.
punteroAFuncion = &imprimirValor; 15
Llamamosalafuncinmedianteelpuntero,ntesequelasintaxisesidnticaalallamadadeunafuncincualquiera.
punteroAFuncion(1); 16
Los punteros a funciones nos pueden servir para, por ejemplo,
reutilizar cdigo en funciones
15Sepuedeomitirel&.16Unasintaxisalternativasera(*punteroAFuncion)(a)
21
-
genricas. Para ser ms claros, supongamos que tengo una lista de
alumnos en la que de cada
alumnoseconocesunombre,apellido,cursoynotasdecadaparcial.typedef
struct { char *nombre; char *apellido; int curso; int
notaPrimerParcial; int notaSegundoParcial; t_alumno *siguiente; }
t_alumno;
Elltimocamposeutilizaparaanidarlosdistintosalumnos,esdecir,paraformarunalista.Una
operacin comn en las listas es realizar un filtrado, es decir, a
partir de una lista obtener otra que cumple con unas determinadas
condiciones. Por ejemplo, todos los alumnos del curso 3020, todos
los alumnos que aprobaron los dos parciales (es decir,
notaPrimerParcial >= 4 &&
notaSegundoParcial>=4)otodoslosalumnoscuyonombreempiezaconlaletraA.Nosotros,
como programadores, decimos ah, quiero obtener todos los alumnos
cuyo nombre empiezan con la letra A, pero capaz maana quiero saber
quines son los que empiezan con la letra Z, entonces me adelanto y
hago que le pase la letra con la que empieza el nombre por
parmetro.Lomismoconelfiltradoporcurso.Entonces,programamosunasfuncionescuyadefinicinsera:t_alumno
*filtrarPorCurso(t_alumno *listaAlumnos, int curso); t_alumno
*filtrarPorLetraInicialNombre(t_alumno *listaAlumnos, char c);
UnaposibleimplementacindefiltrarPorCursosera:t_alumno
*filtrarPorCurso(t_alumno *listaAlumnos, int curso) { t_alumno *aux
= listaAlumnos; t_alumno *listaFiltrada = crearListaAlumnos();
while(aux != NULL) { if (aux->curso == curso) {
agregarALista(listaFiltrada, aux); } aux = aux->siguiente;
22
-
} return listaFiltrada; }
UnaposibleimplementacindefiltrarPorLetraInicialNombresera:t_alumno
*filtrarPorLetraInicialNombre(t_alumno *listaAlumnos, char c) {
t_alumno *aux = listaAlumnos; t_alumno *listaFiltrada =
crearListaAlumnos(); while(aux != NULL) { if (aux->nombre[0] ==
c) { agregarALista(listaFiltrada, aux); } aux = aux->siguiente;
} return listaFiltrada; }En qu cambia la funcin con respecto a la
otra? Solamente en el criterio de filtro, el resto de la lgica
crear una lista aparte, recorrer la lista original y agregar a la
lista nueva los que
cumplanconelcriterioesexactamentelamisma.Repetir cdigo es una mala
prctica pero no es el objetivo de ste documento abordar sas
cuestiones, sino de introducir una posible utilidad real que le
puedas dar a los punteros a funciones.En ste caso, podramos hacer
una funcin filtrar, genrica, que en base a un criterio determinado
me devuelva la lista de aquellos que cumplen dicho criterio. El
criterio ser una
funcinporparmetro.Porlovistoanteriormente,elfiltradoquedaradelasiguientemanera:t_alumno
*filtrarPorCriterio(t_alumno *listaAlumnos, bool
(*criterio)(t_alumno*)) {
23
-
t_alumno *aux = listaAlumnos; t_alumno *listaFiltrada =
crearListaAlumnos(); while(aux != NULL) { if (criterio(aux)) {
agregarALista(listaFiltrada, aux); } aux = aux->siguiente; }
return listaFiltrada; }
Ennegritaestmarcadoloquecambiconrespectoalaversinanterior.De sta
manera, slo tendramos que definir funciones criterio que respeten
la definicin dada enfiltrarPorCriterio. Y se usara de la siguiente
manera: int main(int argc, char **argv) { char inicial = 'a'; bool
nombreEmpiezaCon(t_alumno *alumno){ return alumno->nombre[0] ==
inicial; } //... inicializar lista ... t_alumno *filtrados =
filtrarPorCriterio(alumnos, nombreEmpiezaCon); return 0; }
24
-
Links tiles PunterosenlaguaBeej(eningls):link.
TutorialdeValgrind(pararesolvermemoryleaks):link.
Videosobrepunteros(eningls):link.
25
http://www.google.com/url?q=http%3A%2F%2Fbeej.us%2Fguide%2Fbgc%2Foutput%2Fhtml%2Fmultipage%2Fpointers.html&sa=D&sntz=1&usg=AFQjCNH3u86lFA4a6YjMs4KBmTCXeZX0kwhttps://docs.google.com/document/d/1flOJ2P2g9UGVRiruuA4OCF6nucbN_BWVI0WDlYTJNf4https://www.youtube.com/watch?v=5VnDaHBi8dM&feature=youtube_gdata_player