+ All Categories
Home > Documents > Manejo de Punteros y Memoria Dinámica en C

Manejo de Punteros y Memoria Dinámica en C

Date post: 16-Nov-2015
Category:
Upload: celezab
View: 16 times
Download: 2 times
Share this document with a friend
Description:
Programación en C
Popular Tags:
25
Manejo de Punteros y Memoria Dinámica 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. Introducción Punteros ¿Qué es un puntero? ¿Cómo declaramos un puntero? ¿Cómo inicializamos un puntero? (Malloc) Operaciones de punteros Dirección de una variable (&) Desreferencia (*) Desreferencia en estructuras (>) Funciones que retornan punteros “Tu programa chorea memoria!” (A.K.A Memory Leaks) Aritmética de Punteros Otros alloc El problema del tipo de dato “int” EXTRA: Punteros a Funciones Links útiles 1
Transcript
  • 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


Recommended