You are on page 1of 51

Tutorial de Ponteiros e Arrays em C

Ted Jensen (Traduo Csar A. K. Grossmann)

Prefcio o Agradecimentos o Sobre o Autor o Uso deste Material o Outras Verses Deste Documento Introduo 1. O Que Um Ponteiro? o Referncias 2. Tipos Ponteiros e Arrays 3. Ponteiros e Strings 4. Mais Sobre Strings 5. Ponteiros e Estruturas 6. Um Pouco Mais Sobre Strings, e Arrays de Strings 7. Mais Sobre Arrays Multi-Dimensionais 8. Ponteiros Para Arrays 9. Ponteiros e Alocao Dinmica de Memria o Mtodo 1 o Mtodo 2 o Mtodo 3 o Mtodo 4 10. Ponteiros Para Funes o Referncias Eplogo

Prefcio
Este documento foi feito como uma introduo aos ponteiros para programadores iniciantes na linguagem C. Depois de vrios anos de leituras e contribuies a vrias conferncias sobre C, incluindo as da FidoNet e UseNet, eu notei que um grande nmero de novatos no C parecem ter uma dificuldade em entender os fundamentos do ponteiros. Eu resolvi ento assumir a tarefa de tentar explicar a eles em uma linguagem simples e com muitos exemplos. A primeira verso deste documento foi colocada no domnio pblico, assim como esta. Ela foi escolhida por Bob Stout, que incluiu o mesmo como o arquivo PTR-HELP.TXT, em sua coleo largamente distribuda de SNIPPETS. Desde o lanamento original em 1995, eu acrescentei uma quantia significativa de material e fiz algumas correes menores ao trabalho original. Na verso HTML 1.1, eu fiz um pequeno nmero de mudanas na apresentao como resultado de comentrios que recebi por email de todo o mundo. Na verso 1.2 eu atualizei os dois primeiros captulos para refletir a mudana de compiladores 16 bits para compiladores 32 bits nos PCs.

Agradecimentos
Existem tantas pessoas que contribuiram sem saber a este trabalho, por causa das questes que eles colocaram no FidoNet C Echo, ou no Newsgroup UseNet comp.lang.c, ou vrias outras conferncias em outras redes, que seria impossvel listar a todas. Agradecimentos em especial a Bob Stout, que foi gentil em incluir a primeira verso deste material em seu arquivo SNIPPETS.

Sobre o Autor
Ted Jensen um Engenheiro Eletrnico aposentado que trabalhou como projetista de hardware ou gerente de projetistas de hardware no campo de gravao magntica. Programao tem sido um hobby seu desde 1968, quando ele aprendeu a perfurar cartes para serem executados em um mainframe (o mainframe tinha 64K de memria de ncleo magntico!).

Uso deste Material


Tudo que est contido aqui est liberado para o Domnio Pblico. Qualquer pessoa pode copiar ou distribuir este material da maneira que desejar. A nica coisa que eu peo que se este material for usado como auxlio em uma class, eu apreciaria se ele fosse distribudo completo, ou seja, incluindo todos os captulos, prefcio e introduo. Eu tambm apreciaria se, nestas circunstncias, o instrutor desta classe me enviasse uma nota em um dos endereos abaixo para me informar disto. Eu escrevi este material na esperana que o 2

mesmo fosse til para outros e como eu no estou pedindo nenhuma remunerao financeira, a nica forma de eu saber que eu atingi pelo menos parcialmente meu objetivo atravs deste feedback dos que acharam este material til. A propsito, voc no precisa ser um instrutor ou professor para contactar-me. Eu apreciarei uma nota de qualquer um que achar este material til, ou que tiver alguma crtica construtiva a oferecer. Tambm estou disposto a responder a perguntas enviadas via email para o endereo abaixo.

Outras Verses Deste Documento


Alm da verso hipertexto deste documento, eu disponibilizei outras verses mais apropriadas para impresso ou para download do documento completo. Se voc est interesssado em manter-se atualizado com meu progresso nesta rea, ou quiser verificar se h verses mais recentes deste documento, veja meu Wev Site em http://www.netcom.com/ ~tiensen/ptr/cpoint.hm Ted Jensen Redwood City, California tjensen@ix.netcom.com Feb. 2000

Introduo
Se voc quer ter eficincia em escrever cdigo na linguagem C, tem que ter um bom conhecimento sobre como usar ponteiros. Infelizmente, os ponteiros C parecem representar um obstculo para novatos, particularmente para os que vem de outras linguagens de computador, como FORTRAN, Pascal ou BASIC. Para ajudar estes novatos a entender ponteiros eu escrevi o material presente. Para obter o mximo benefcio deste material, eu acho importante que o usurio consiga executar o cdigo das vrias listagens contidas no artigo. Eu tentei, portanto, fazer com que todo o cdigo atendesse s normas ANSI, de forma que ir funcionar com qualquer compilador que tambm atenda as mesmas normas ANSI. Eu tambm tentei colocar o cdigo em blocos no texto. Desta forma, com a ajuda de um editor de textos ASCII, voc pode copiar um dado bloco de cdigo para um novo arquivo e compilar o mesmo em seu sistema. Eu recomendo que os leitores faam isto j que vai ajudar a entender o material.

1. O Que Um Ponteiro?
Uma das coisas que os novatos em C tem dificuldade o conceito de ponteiros. O objetivo deste tutorial fornecer uma introduo aos ponteiroes e o seu uso para estes novatos. Eu descobri que uma das razes principais para os problemas que os novatos tem com ponteiros que eles tem um conhecimento fraco ou mnimo sobre variveis (da forma que so usadas em C). Assim, vamos comear a discusso com as variveis C em geral. Uma varivel em um programa algo que tem um nome, e cujo valor pode variar. A forma que o compilador e o linker tratam-nas que eles atribuem um bloco especfico da memria do computador para guardar o valor da varivel. O tamanho do bloco depende do intervalo de valores permitido varivel. Por exemplo, em um PC de 32 bits, o tamanho de uma varivel integer 4 bytes. Em velhos PCs de 16 bits, era 2 bytes. Em C o tamanho de um tipo de varivel como o inteiro no precisa ser o mesmo em todos os tipos de mquinas. Alm disto, existe mais de um tipo de varivel inteira em C. Temos os integers, os long integers, e os short integers que voc vai encontrar em qualquer texto bsico sobre C. Este documento assume o uso de um sistema de 32 bits com integers de 4 bytes. Se voc quiser saber o tamanho dos vrios tipos de inteiros no seu sisetma, execute o cdigo abaixo que ele lhe dar esta informao.

#include <stdio.h> int main() { printf("size of a short is %d\n", sizeof(short)); printf("size of a int is %d\n", sizeof(int)); printf("size of a long is %d\n", sizeof(long)); }

Quando declaramos uma varivel, informamos ao compilador duas coisas, o nome da varivel e o tipo da mesma. Por exemplo, declaramos uma varivel do tipo integer com o nome k escrevendo:
int k;

Ao ver a parte "int" nesta declarao o compilador reserva 4 bytes de memria (em um PC) para guardar o valor do integer. Ele tambm monta uma tabela de smbolos. Nesta tabela ele acrescenta o smbolo k e o endereo relativo na memria em que estes 4 bytes foram separados. Desta forma, se mais tarde escrevemos:
k = 2;

esperamos que, na execuo, quando esta declarao for executada, o valor 2 seja colocado na poro de memria reservada para guardar o valor de k. Em C nos referimos a uma varivel como o integer k como sendo um "objeto". Em um certo sentido existem dois "valores" associados ao objeto k. Um o valor do inteiro armazenado ali (2 no exemplo acima) e o outro o "valor" da localizao de memria, isto , o endereo de k. Alguns textos se referem a estes dois valoers com a nomenclatura rvalue (right value, valor direita, pronunciado "are value"), e lvalue (left value, valor esquerda, pronunciado "el value") respectivamente. Em algumas linguagens, o lvalue o valor que pode aparecer do lado esquerdo de um operador de atribuio '=' (isto , o endero para onde o resultado da expresso do lado direito vai). O rvalue o que est no lado direito da declarao de atribuio, o 2 acima. Rvalues no podem ser usados no lado esquerdo da declarao de atribuio. Desta forma, 2 = k; ilegal. Na verdade, a definio acima de "lvalue" foi um pouco modificada para o C. De acordo com K&R II (pgina 197):[1] "An object is a named region of storage; an lvalue is an expression referring to an object."

("Um objeto uma regio de armazenamento que tem nome; um lvalue uma expresso que refere um objeto.") Entretanto, neste ponto, a definio originalmente citada acima suficiente. Conforme nos tornamos mais familiares com ponteiros iremos entrar em mais detalhes sobre isto. Vamos considerar agora:
int j, k; k = 2; j = 7; k = j; <-- linha 1 <-- linha 2

No exemplo acima, o compilador interpreta o j na linha 1 com sendo o endereo da varivel j (seu lvalue) e cria o cdigo para copiar o valor 7 para aquele endereo. Na linha 2, entretanto, o j interpretado como o seu rvalue (j que est no lado direito do operador de atribuio '='). Ou seja, a o j se refere ao valor armazenado na posio de memria reservada para o j, neste caso, o 7. Assim, o 7 copiado para o endereo designado pelo lvalue k. Em todos estes exemplos, estamos uando inetgers de 4 bytes de forma que todas as cpias de rvalues de um local de armazenamento para outro feito pela cpia de 4 bytes. Se estivssemos usando inteiros de 2 bytes, estaramos copiando 2 bytes. Agora, digamos que temos uma razo para querer que uma varivel armazene um lvalue (um endereo). O tamanho necessrio para armazenar este tipo de valor depende do sistema. Em computadores desktop antigos com 64K de memria total, o endereo de qualquer ponto na memria pode estar contido em 2 bytes. Computadores com mais memria vo necessitar mais bytes para conter um endereo. O tamanho real necessrio no to importanto, j que temos uma forma de informar ao compilador que o que queremos armazenar um endereo. Este tipo de varivel chamado de varivel ponteiro (por razes que esperamos que fiquem claras um pouco mais adiante). Em C quando definimos uma varivel ponteiro ns o fazemos precedendo o seu nome com um asterisco. Em C tambm damos a nosso ponteiro um tipo que, neste caso, refere-se ao tipo de dado armazenado no endereo que estaremos armazenando em nosso ponteiro. Por exemplo, considere a declarao de varivel:
int *ptr;

ptr o nome de nossa varivel (como k era o nome de nossa varivel integer). O '*' informa ao compilador que queremos uma varivel ponteiro, isto , ele deve separar tantos bytes quantos forem necessrios para armazenar um endereo na memria. O int diz que pretendemos uasr nossa varivel ponteiro para armazenar o endereo de um inteiro. Deste tipo de ponteiro se diz que ele "aponta para" um inteiro. Entretanto, note que quando

escrevemos int k;, ns no damos a k um valor. Se esta definio for feita fora de qualquer funo, compiladores ANSI iro colocar nelas o valor inicial zero. De forma semelhante, ptr no tem um valor, j que no armazenamos um endereo na declarao acima. Neste caso, novamente se a declarao estiver fora de qualquer funo, ela inicializada para um valor que garantidamente no aponta para qualquer objeto C ou funo. Um ponteiro iniciado desta forma chamado de ponteiro "null". O padro de bits usado para um ponteiro null pode ou no resultar em zero j que depende do sistema especfico no qual o cdigo est sendo desenvolvido. Para fazer com que o cdigo fonte seja compatvel entre vrios compiladores em vrios sistemas, uma macro usada para representar um ponteiro null. A macro recebe o nome NULL. Assim, colocar o valor NULL em um ponteiro, como em uma declarao de atribuio, como ptr = NULL;, garante que o ponteiro um ponteiro null. De forma similar, da mesma forma que algum testa um valor inteiro para ver se zero, como em if(k == 0), podemos testar se um ponteiro null usando if(ptr == NULL). Voltemos ao uso de nossa nova varivel ptr. Suponha agora que queremos armazenar em ptr o endereo de nossa varivel inteira k. Para isto, usamos o operador unrio & e escrevemos:
ptr = &k;

O que o operador & faz recuperar o lvalue (endereo) de k, mesmo que k esteja no lado direito do operador '=' de atribuio, e copia o mesmo para o contedo de nosso ponteiro ptr. Agora, diz-se que ptr "aponta para" k. Continue conosco agora, h somente mais um operador que precisamos discutir. O "operador de de referenciamento" o asterisco, e usado conforme o exemplo abaixo:
*ptr = 7;

Esta linha ir copiar o 7 para o endereo apontado por ptr. Assim, se ptr "aponta para" (contm o endereo de) k, a declarao acima ir colocar em k o valor 7. Ou seja, quando usamos o '*' desta forma, estamos nos referindo ao valor para o qual ptr est apontando, no ao valor do ponteiro em si. De forma semelhante, podemos escrever:
printf("%d\n", *ptr);

para escrever na tela o valor inteiro armazenado no endereo apontado por ptr. Uma forma de ver tudo isto junto executar o seguinte programa e ento ervisar o cdigo e a sada cuidadosamente.

Programa 1.1
/* Program 1.1 from PTRTUT10.TXT #include <stdio.h> int j, k; int *ptr; int main(void) { j = 1; k = 2; ptr = &k; printf("\n"); printf("j has the value %d and is stored at %p\n", j, (void *)&j); printf("k has the value %d and is stored at %p\n", k, (void *)&k); printf("ptr has the value %p and is stored at %p\n", ptr, (void *)&ptr); printf("The value of the integer pointed to by ptr is %d\n", *ptr); return 0; } 6/10/97 */

Nota: Ainda temos que discutir os aspectos de C que requerem o uso da expresso (void *) usada acima. Por enquanto, inclua a mesma no seu cdigo de teste. Iremos explicar as razes por trs desta expresso mais tarde.

Para revisar:

Uma varivel declarada dando a ela um tipo e um nome (p.ex., int k;) Uma varivel ponteiro declarada dando a ela um tipo e um nome (p.ex., int *ptr) onde o asterisco informa ao compilador que a varivel de nome ptr uma varivel ponteiro e o tipo informa ao compilador que tipo o ponteiro aponta (inteiro neste caso). Uma vez que uma varivel seja declarada, podemos obter seu endereo precedendo o seu nome com o operador unrio &, como em &k. Podemos "dereferenciar" um ponteiro, isto , referirmos o valor para o qual ele aponta, usando o operador unrio '*' como em *ptr. O "lvalue" de uma varivel o valor de seu endereo, isto , onde ela est armazenada na memria. O "rvalue" de uma varivel o valor armazenado naquela varivel (naquele endereo).

Referncias
[1] "The C Programming Language" 2nd Edition B. Kernighan and D. Ritchie Prentice Hall ISBN 0-13-110362-8

2. Tipos Ponteiros e Arrays


Podemos avanar agora. Consideremos que precisamos identificar o tipo da varivel para o qual o ponteiro aponta, como em:
int *ptr;

Uma razo para fazer isto que mais tarde, uma vez que ptr "aponte para" alguma coisa, se escrevermos:
*ptr = 2;

o compilador saber quantos bytes copiar para aquela localizao de memria apontada por ptr. Se ptr foi declarado como apontando para um inteiro, 4 bytes sero copiados. De forma semelhante para floats e doubles o nmero apropriado ser copiado. Mas, definir o tipo para o qual o ponteiro aponta permite um grande nmero de outras formas que o compilador pode interpretar o cdigo. Por exemplo, considere um bloco de memria consistindo em dez inteiros em linha. Ou seja, 40 bytes de memria so separados para armazenar 10 inteiros. Agora, digamos que apontamos nosso ponteiro ptr para o primeiro destes inteiros. Mais, vamos dizer que aquele inteiro est localizado na posio de memria 100 (decimal). O que acontece quando escrevemos:
ptr + 1;

Como o compilador "sabe" que se trata de um ponteiro (isto , seu valor um endereo), e que est apontando para um inteiro (seu endereo atual, 100, o endereo de um inteiro), ele acrescenta 4 a ptr, em vez de 1, assim o ponteiro "aponta" para o prximo inteiro, na posio de memria 104. De forma semelhante, onde o ptr declarado como um ponteiro para um short, ele ser acrescido de 2 em vez de 1. O mesmo vale para outros tipos de dados, como floats, doubles, ou mesmo tipos de dados definidos pelo usurio, como estruturas. Isto obviamente no o mesmo tipo de "adio" que normalmente pensamos. Em C, ela referida como adio usando "aritmtica de ponteiros", um termo que iremos retornar mais tarde.

De forma similar, j que ++ptr e ptr++ so equivalentes a ptr + 1 (apesar que o ponto no programa em que ptr seja incrementado pode ser diferente), incrementar um ponteiro usando o operador unrio ++, tanto pr ou ps, incrementa o endereo que ele armazena pela quantidade sizeof(type) onde "type" o tipo de objeto apontado (isto , 4 para um inteiro). Como um bloco de 10 inteiros localizado de forma contgua na memria , por definio, um array de inteiros, temos a um relacionamento interessante entre arrays e ponteiros. Considere o seguinte:
int my_array[] = {1, 23, 17, 4, -5, 100};

Temos a um array contendo 6 inteiros. Nos referimos a cada um destes inteiros via um subscrito a my_array, isto , usando my_array[0] a my_array[5]. Mas podemos alternativamente acess-los usando um ponteiro, como no exemplo abaixo:
int *ptr; ptr = &my_array[0]; /* aponta nosso ponteiro para o primeiro inteiro em nosso array */

E assim podemos escrever nosso array usando ou a notao de array, ou dereferenciando nosso ponteiro. O seguinte cdigo ilustra isto: Programa 2.1
/* Program 2.1 from PTRTUT10.HTM #include <stdio.h> int my_array[] = {1,23,17,4,-5,100}; int *ptr; int main(void) { int i; ptr = &my_array[0]; 6/13/97 */

printf("\n\n"); for (i = 0; i < 6; i++) { printf("my_array[%d] = %d ",i,my_array[i]); printf("ptr + %d = %d\n",i, *(ptr + i)); } return 0; }

/* point our pointer to the first element of the array */

/*<-- A */ /*<-- B */

Compile e execute o programa acima e note com cuidado as linhas A e B e que o programa escreve os mesmos valores em cada caso. Tambm note como dereferenciamos nosso ponteiro na linha B, isto , primeiro incrementamos ele e ento dereferenciamos o novo ponteiro. Mude a linha B para: 10

printf("ptr + %d = %d\n", i, *ptr++);

e execute o programa novamente... ento mude para:


printf("ptr + %d = %d\n", i, *(++ptr));

e tente novamente. Cada vez tente prever o resultado e examine com cuidado o resultado real. Em C, o padro declara que onde precisamos usar &var_name[0], podemos trocar por var_name, assim, no nosso cdigo, onde escrevemos:
ptr = &my_array[0];

podemos escrever:
ptr = my_array;

para obter o mesmo resultado. Por causa disto que muitos textos se referem ao nome de um array como um ponteiro. Eu prefiro pensar que "o nome do array o endereo do primeiro elemento do array". Muitos iniciantes (inclusive eu quando estava aprendendo) tem a tendncia de ficar confuso ao pensar nisto como um ponteiro. Por exemplo, enquanto podemos escrever
ptr = my_array;

no podemos escrever
my_array = ptr;

A razo que enquanto ptr uma varivel, my_array uma constante. Ou seja, o local onde o primeiro elemento de my_array ser armazenado no pode ser alterado uma vez que my_array[] tenha sido declarado. Um pouco antes, quando discutimos o termo "lvalue", eu citei K&R-2 onde est declarado: "An object is a named region of storage; an lvalue is an expression referring to an object". ("Um objeto uma regio de armazenamento com nome; um lvalue uma expresso que se refere a um objeto"). Isto leva a um problema interessante. Como my_array uma regio de armazenamento nomeada, por qu my_array na declarao acima no um lvalue? Para resolver este problema, alguns se referem a my_array como sendo um "lvalue no modificvel".

11

Modifique o programa exemplo acima trocando


ptr = &my_array[0];

para
ptr = my_array;

e execute novamente para ver se os resultados so idnticos. Agora, vamos nos aprofundar um pouco mais na diferena entre os nomes ptr e my_array como foram usados acima. Alguns autores se referem ao nome de um array como sendo um ponteiro constante. O que isto quer dizer? Bem, para entender o termo "constante" neste sentido, vamos retornar nossa definio do termo "varivel". Quando declaramos uma varivel ns separamos um ponto na memria para armazenar o valor do tipo apropriado. Uma vez que isto foi feito, o nome da varivel pode ser interpretado em uma de duas formas. Quando usado no lado esquerdo do operador de atribuio, o compilador interpreta como o local de memria para o qual mover o valor resultante da avaliao do termo do lado direito do operador de atribuio. Mas, quando usado no lado direito do operador de atribuio, o nome da varivel interpretado como significando o contedo armazenado naquele endereo de memria, separado para armazenar o valor daquela varivel. Com isto em mente, vamos agora considerar a mais simples das constantes, como em:
int i, k; i = 2;

Aqui, enquanto i uma varivel e ento ocupa espao na poro dedicada aos dados na memria, 2 uma constante e, como tal, em vez de receber memria no segmento de dados, ela est inserida diretamente no segmento de cdigo da memria. Ou seja, enquanto escrever algo como k = i; diz ao compilador para criar cdigo que na execuo ir procurar a localizao de memria &i para determinar o valor a ser movido para k, o cdigo criado por i = 2; simplemente coloca 2 no cdigo e no h referncias ao segmento de dados. Ou seja, tanto k quanto i so objetos, mas 2 no um objeto. De forma similar, no exemplo acima, como my_array uma constante, uma vez que o compilador estabelece onde o array ser armazenado, ele "sabe" o endereo de my_array[0] e ao ver:
ptr = my_array;

ele simplesmente usa este endereo como uma constante no segmento de cdigo e, portanto, no est fazendo referncia ao segmento de dados. Este pode ser um bom momento para explicar um pouco mais o uso da expresso (void *) usada no Programa 1.1 no Captulo 1. Como j vimos, podemos ter ponteiros de vrios tipos. At agora discutimos ponteiros para inteiros e ponteiros para caracteres. Nos

12

captulos seguintes aprenderemos sobre ponteiros a estruturas e mesmo ponteiros a ponteiros. Tambm aprendemos que em diferentes sistemas o tamanho de um ponteiro pode variar. Da mesma forma, possvel que o tamanho de um ponteiro possa variar dependendo do tipo de objeto apontado. Assim, da mesma forma que com os inteiros voc pode ter problemas se tentar atribuir um inteiro long a uma varivel do tipo short, voc pode ter problemas se tentar atribuir valores de ponetiros de vrios tipos para variveis ponteiro de outros tipos. Para minimizar este problema, o C permite ponteiros do tipo void. Podemos declarar este tipo de ponteiro escrevendo:
void *vptr;

Um ponteiro void um tipo de ponteiro genrico. Por exemplo, enquanto o C no permite a comparao de um ponteiro do tipo inteiro com um ponteiro do tipo caracter, por exemplo, qualquer um deles pode ser comparado a um ponteiro void. Obviamente, como com outras variveis, casts podem ser usados para converter de um tipo de ponteiro para outro, nas circunstncias apropriadas. No Programa 1.1 do Captulo 1 eu fiz um cast dos ponteiros para inteiros para ponteiros void para que ficassem compatveis com a especificaod e converso de %p. Nos captulos seguintes, outros casts sero feitos por razes a serem apresentadas. Bem, foi bastante coisa tcnica para digerir, e eu no espero que um iniciante entenda tudo isto na primeira leitura. Com o tempo e experincia voc vai voltar e reler os dois primeiros captulos. Por ora, vamos avanar para o relacionamento entre os ponteiros, arrays de caracter, e strings.

3. Ponteiros e Strings
O estudo de strings til para entender mais o relacionamento entre ponteiros e arrays. Ele tambm torna fcil ilustrar como algumas das funes string padro do C podem ser implementadas. Finalmente, ele ilustra como e quando os ponteiros podem e devem ser passados funes. Em C, strings so arrays de caracteres. Isto no necessariamente verdadeiro em outras linguagens. Em BASIC, Pascal, FORTRAN e vrias outras linguagens, uma string tem seu prprio tipo de dados. Mas em C, no. Em C uma string qualquer array de caracteres terminado com um caracter binrio zero (tambm chamado de nul ou nulo, escrito como '\ 0'). Para comear nossa discusso vamos escrever algum cdigo que, enquanto serve como ilustrao, voc provavelmente nunca escrever algo parecido em um programa real. Considere, por exemplo:

13

char my_string[40]; my_string[0] my_string[1] my_string[2] my_string[3] = = = = 'T'; 'e'; 'd': '\0';

Apesar de ningum nunca escrever uma string desta forma, o resultado final um array de caracteres terminado com um caracter nul. Por definio, em C, uma string um array de caracteres terminada com o caracter nul. Perceba, entretanto, que "nul" **no * o mesmo que "NULL". 'nul' refere-se ao zero como definido pela seqncia de escape '\0'. Ela ocupa um byte de memria. NULL, por outro lado, o nome da macro usada para iniciar ponteiros nulos. NULL definido por #define em um arquivo de cabealho em seu compilador C, nul pode no ser definido em lugar nenhum. Como escrever o cdigo acima pode demorar bastante, o C permite duas formas alternativas de chegar ao mesmo fim. Primeiro, pode-se escrever:
char my_string[40] = ('T', 'e', 'd', '\0'};

Mas isto tambm usa mais digitao do que conveniente. Assim, o C permite:
char my_string[40] = "Ted";

Quando as aspas duplas so usadas, em vez das aspas simples (ou plicas), como nos exemplos anteriores, o caracter nul ('\0') automaticamente acrescentado no fim da string. Em todos os casos acima, a mesma coisa acontece. O compilador separa um bloco contguo de memria de tamanho 40 bytes para guardar os caracteres e inicia o mesmo de forma que os 4 primeiros caracteres so Ted \0. Agora, considere o seguinte programa: Programa 3.1
/* Program 3.1 from PTRTUT10.HTM #include <stdio.h> char strA[80] = "A string to be used for demonstration purposes"; char strB[80]; int main(void) { char *pA; char *pB; puts(strA); pA = strA; /* /* /* /* a pointer to type character */ another pointer to type character */ show string A */ point pA at string A */ 6/13/97 */

14

puts(pA); /* show what pA is pointing to */ pB = strB; /* point pB at string B */ putchar('\n'); /* move down one line on the screen */ while(*pA != '\0') /* line A (see text) */ { *pB++ = *pA++; /* line B (see text) */ } *pB = '\0'; /* line C (see text) */ puts(strB); /* show strB on screen */ return 0;

No exemplo acima comeamos definindo dois arrays de caracteres de 80 caracteres cada. Como eles so definidos globalmente, eles so inicializados com '\0's primeiro. Ento strA tem os 42 primeiros caracteres inicializados para a string entre aspas. Agora, avanando no cdigo, declaramos dois ponteiros de caracter e mostramos a string na tela. Ento "apontamos" o ponteiro pA para strA. Isto , via a declarao de atribuio ns copiamos o endereo de strA[0] em nossa varivel pA. Agora usamos puts() para mostrar o que est apontado por pA na tela. Considere aqui que o prottipo da funo puts() :
int puts(const char *s);

Por enquato, ignore o const. O parmetro passado a puts() um ponteiro, ou seja, o valor de um ponteiro (j que todos os parmetros em C so passados por valor), e o valor de um ponteiro o endereo para o qual ele est apontando, ou, simplesmente, um endereo. Assim, quando escrevemos puts(strA); como vimos antes, estamos passando o endereo de strA[0]. De forma similar, quando escrevemos puts(pA); estamos passando o mesmo endereo, j que fizemos pA = strA; Isto posto, seguimos o cdigo at a declarao while( ) na linha A. A linha A declara: Enquanto o caracter apontado por pA (isto , *pA) no for um caracter nul (isto , o terminador '\0'), fazemos o seguinte: A linha B declara: copie o caracter apontado por pA para o espao apontado por pB, ento incremente pA, de forma que aponte para o prximo caracter e pB aponte para o prximo espao. Quando copiamos o ltimo caracter, pA aponta agora para o caracter de terminao nul e o lao termina. Entretanto, no copiamos o caracter nul. E, por definio uma string em C deve ser terminada por um nul. Assim, ns acrescentamos o caracter nul com a linha C. bastante educacional executar este programa com seu depurador ao mesmo tempo que examina strA, strB, pA e pB e andando passo a passo pelo programa. E at mais

15

educacional se em vez de simplesmente definir strB[] como foi feito acima, colocarmos um valor inicial como:
strB[80] = "1234567890123456789012345678901234567890";

onde o nmero de dgitos usado seja maior que o comprimento de strA e ento repetir o procedimento de seguir passo a passo o programa ao mesmo tempo que examina as variveis acima. Faa esta experincia! Voltando ao prottipo de puts() por um momento, o "const" usado como um modificador de parmetros que informa ao usurio que a funo no ir modificar a string apontada por s, isto , a string ser tratada como uma constante. Obviamente, o que o programa acima ilustra uma forma simples de copiar uma string. Aps brincar com o cdigo acima at ter um bom entendimento do que est acontecendo, podemos seguir com a criao de nosso prprio substituto para o strcpy() que vem com o C. Ele pode ficar assim:
char *my_strcpy(char *destination, char *source) { char *p = destination; while (*source != '\0') { *p++ = *source++; } *p = '\0'; return destination; }

Neste caso, eu segui a prtica usada na rotina padro que retornar um ponteiro para o destino. Novamente, a funo projetada para aceitar os valores de dois ponteiros de caracteres, isto , endereos, e assim no programa anterior podemos escrever:
int main(void) { my_strcpy(strB, strA); puts(strB); }

Eu mudei um pouco da forma usada no C padro que teria o prottipo:


char *my_strcpy(char *destination, const char *source);

Aqui, o modificador "const" usado para garantir ao usurio que a funo no modificar o contedo apontado pelo ponteiro origem. Voc pode provar isto modificando a funo acima, e seu prottipo, para incluir o modificador "const" como mostrado. Ento, dentor da

16

funo voc pode acerscentar uma declarao que tente mudar o contedo do que apontado pela fonte, como em:
*source = 'X';

que normalmente mudaria o primeiro caracter da string para um X. O modificador const deve fazer que seu compilador aponte isto como um erro. Tente e veja. Agora, vamos considerar algumas das coisas que os exemplos acima nos mostraram. Primeiro, considere o fato que *ptr++ deve ser interpretado como retornando o valor apontado por ptr e em seguida incrementando o valor do ponteiro. Isto tem a ver com a precedncia de operadores. Onde escrevemos (*ptr)++ ns incrementaremos, no o ponteiro, mas o que o ponteiro aponta! Isto , se usado no primeiro caracter da string exemplo o 'T' seria incrementado para um 'U'. Voc pode escrever um cdigo exemplo simples para ilustrar isto. Lembre novamente que uma string nada mais que um array de caracteres, com o ltimo caracter sendo um '\0'. O que fizemos acima tratar com a cpia de um array. Aconteceu ser um array de caracteres, mas a tcnica pode ser aplicada a um array de inteiros, doubles, etc. Naqueles casos, entretanto, no estaremos tratando com strings e portanto o fim do array no ser marcado com um valor especial como o caracter nulo. Podemos implementar uma verso que dependa de um valor especial para identificar o fim. Por exemplo, podemos copiar um aray de inteiros positivos marcando o fim com um inteiro negativo. Por outro lado, mais usual que quando escrevermos uma funo para copiar um array de itens que no strings, passemos para a funo o nmero de itens a serem copiados bem como o endereo do array, por exemplo, algo como o indicado pelo prottipo abaixo:
void int_copy(int *ptrA, int *ptrB, int nbr);

onde nbr o nmero de inteiros a serem copiados. Voc pode querer brincar com esta idia e criar um array de inteiros e ver se consegue escrever a funo int_copy() e faz-la funcionar. Isto permite usar funes para tratar arrays grandes. Por exemplo, se temos um array de 5000 inteiros que queremos manipular com uma funo, precisamos passar para a funo s o endereo do array (e qualquer informao auxiliar como o nbr acima, dependendo do que estamos fazendo). O array em si no passado, isto , o array inteiro no copiado e colocado na pilha antes do chamado funo, s seu endereo enviado. Isto diferente de passar, por exemplo, um inteiro a uma funo. Quando passamos um inteiro fazemos uma cpia do inteiro, isto , o seu valor obtido e colocado na pilha. Dentro da funo qualquer manipulao do valor passado no pode afetar de forma alguma o valor original. Mas, com arrays e ponteiros podemos passar o endereo da varivel e, portanto, manipular os valores das variveis originais.

17

4. Mais Sobre Strings


Bem, progredimos um bom tanto em to pouco tempo! Vamos olhar para trs um pouco e ver o que foi feito no Captulo 3, sobre cpias de Strings, mas em uma luz diferente. Considere a seguinte funo:
char *my_strcpy(char dest[], char source[]) { int i = 0; while (source[i] != '\0') { dest[i] = source[i]; i++; } dest[i] = '\0'; return dest; }

Lembre que strings so arrays de caracteres. Aqui escolhemos usar a notao de array em vez de notao de ponteiros para fazer a cpia. O resultado o mesmo, isto , a string copiada usando esta notao to correto quanto antes. Isto levanta alguns pontos interessantes para discusso. Como os parmetros so passados por valor, tanto no passar o ponteiro caracter ou o nome do array, como no exemplo acima, o que realmente passado o endereo do primeiro elemento de cada array. Assim, o valor numrico do parmetro o mesmo, caso usemos um ponteiro de caracter ou um nome de array como parmetro. Isto parece implicar que de alguma forma source[i] o mesmo que *(p+i). De fato, isto verdadeiro, isto , onde est escrito a[i] pode-se substituir com *(a+i) sem qualquer problema. De fato, o compilador ir criar o mesmo cdigo em qualquer dos casos. Assim nos vemos que a aritmtica de ponteiro a mesma coisa que a indexao de array. Qualquer uma das duas sintaxes produz o mesmo resultado. Isto no quer dizer que ponteiros e arrays so a mesma coisa, eles no so. Estamos apenas dizendo que para identificar um dado elemento em um array temos a escolha de duas sintaxes, uma usando indexao de array e outra usando aritmtica de ponteiros, o que d resultados idnticos. Agora, olhando para a ltima expresso, parte dela, (a+i), uma simples adio usando o operador + e as regras do C dizem que esta expresso comutativa. Ou seja, (a+i) idntico a (i+a). Assim, podemos escrever *(i+a) com a mesma facilidade que escrevemos *(a+i). Mas *(i+a) poderia vir de i[a]! Disto decorre a curiosa verdade que, se:
char a[20]; int i;

18

escrever
a[3] = 'x';

o mesmo que escrever


3[a] = 'x';

Tente! Crie um array de caracteres, inteiros ou longs, etc. e atribua ao terceiro ou quarto elemento um valor usando a abordagem convencional e ento escreva o valor para ter certeza que est funcionando. Ento reverta a notao de array como foi feito acima. Um bom compilador no vai reclamar e os resultados sero idnticos. Uma curiosidade... nada mais! Agora, olhando para nossa funo acima, quando escrevemos:
dest[i] = source[i];

defido ao fato que a indexao de array e a aritmtica de ponteiro d os mesmos resultados, podemos escrever isto como:
*(dest + i) = *(source + i);

Mas isto toma 2 adies para cada valor de i. Adies, falando em termos gerais, tomam mais tempo que incrementos (como os feitos usando o operador ++, como em i++). Isto pode no ser verdade em compiladores modernos com otimizao, mas no se pode ter certeza. Assim, a verso de ponteiro pode ser um pouco mais rpida que a verso de array. Outra forma de tornar mais rpida a verso de ponteiro seria mudar:
while (*source != '\0')

para simplesmente
while (*source)

j que o valor entre parntesis ir resultar em zero (FALSE) no mesmo momento em cada um dos casos. Neste ponto voc pode querer experimentar um pouco escrevendo seus prprios programas usando ponteiros. A manipulao de strings um bom lugar para experimentar. Voc pode querer escrever sua prpria verso de funes padro como:
strlen(); strcat(); strchr();

e quaisquer outras que voc possa ter em seu sistema. 19

Voltaremos as strings e sua manipulao via ponteiros em um outro captulo. Por enquanto, vamos mudar e discutir um pouco de estruturas.

5. Ponteiros e Estruturas
Como voc sabe, podemos declarar a forma de um bloco de dados contendo diferentes tipos de dados via uma declarao de estrutura. Por exemplo, um arquivo de pessoal pode conter estruturas que se paream com algo assim:
struct tag { char lname[20]; char fname[20]; int age; float rate; }; /* /* /* /* ltimo nome */ primeiro nome */ idade */ p. ex. 12.75 por hora */

Digamos que temos um punhado destas estruturas em um arquivo em disco e queremos ler cada uma e escrever o primeiro e ltimo nomes de cada um de forma que tenhamos uma lista das pessoas em nossos arquivos. As informaes restantes no devem ser escritas. Faremos isto escrevendo com uma chamada de funo e passando para aquela umo um ponteiro para a estrutura mo. Para demonstrao eu irei usar somente uma estrutura, mas perceba que o objetivo aqui escrever a funo, no a leitura do arquivo que, presumivelmente, ns sabemos como fazer. Para reviso, lembre que podemos acessar membros de estruturas com o operador ponto, como em: Programa 5.1
/* Program 5.1 from PTRTUT10.HTM #include <stdio.h> #include <string.h> struct tag { char lname[20]; char fname[20]; int age; float rate; }; struct tag my_struct; /* /* /* /* last name */ first name */ age */ e.g. 12.75 per hour */ /* declare the structure my_struct */ 6/13/97 */

20

int main(void) { strcpy(my_struct.lname,"Jensen"); strcpy(my_struct.fname,"Ted"); printf("\n%s ",my_struct.fname); printf("%s\n",my_struct.lname); return 0; }

Agora, esta estrutura em particular bem pequena comparada com muitas usadas em programas C. Aos dados acima queremos acrescentar:
date_of_hire; date_of_last_raise; last_percent_increase; emergency_phone; medical_plan; Social_S_Nbr; etc..... (data types not shown)

Se temos um grande nmero de empregados, o que querermos manipular os dados nestas estruturas atravs de funes. Por exemplo, podemos querer que uma funo escreva o nome do empregado listado em qualquer estrutura passada ao mesmo. Entretanto, no C original (Kernighan & Ritchie, 1st Edition) no era possvel passar uma estrutura, somente um ponteiro para uma estrutura podia ser passado. Em ANSI C, permitido passar a estrutura completa. Mas, como nosso objetivo aqui aprender mais sobre ponteiros, no iremos fazer isto. De qualquer forma, se passamos a estrutura toda isto significa que teremos de copiar todo o contedo da estrutura da funo que est fazendo a chamada para a funo chamada. Em sistemas que usam pilha, isto feito empurrando o contedo da estrutura para a pilha. Com estruturas enormes isto pode ser um problema. Entretanto, passar um ponteiro usa um mnimo do espao da pilha. De qualquer forma, como esta uma discusso sobre ponteiros, iremos discutir como passar um ponteiro de de estrutura e como us-lo dentro de uma funo. Considere o caso descrito, ou seja, queremos uma funo que ir aceitar como parmetro um ponteiro para uma estrutura e que dentro daquela funo queremos acessar os membros da estrutura. Por exemplo, queremos escrever o nome do empregado em nossa estrutura exemplo. Certo, agora sabemos que nosso ponteiro ir apontar para uma estrutura declarada usando struct tag. Declaramos um ponteiro destes com a declarao:
struct tag *str_ptr;

e apontamos para a nossa estrutura exemplo com:

21

st_ptr = &my_struct;

Agora, podemos acessar um dado membro dereferenciando o ponteiro. Mas, como ns dereferenciamos o ponteiro a uma estrutura? Bem, considere o fato que podemos querer usar o ponteiro para mudar a idade do empregado. Poderamos escrever:
(*str_ptr).age = 63;

Olhe esta linha com cuidado. Ela diz, substitua o que est entre parntesis pelo que st_ptr est apontando, que a estrutura my_struct. Assim, isto faz o mesmo que my_struct.age. Entretanto, esta expresso usada com uma certa freqncia, e os projetistas de C criaram uma sintaxe alternativa com o mesmo significado que :
st_ptr->age = 63;

Com isto em mente, olhe para o seguinte programa: Programa 5.2


/* Program 5.2 from PTRTUT10.HTM #include <stdio.h> #include <string.h> struct tag{ char lname[20]; char fname[20]; int age; float rate; }; struct tag my_struct; void show_name(struct tag *p); /* /* /* /* /* the structure type */ last name */ first name */ age */ e.g. 12.75 per hour */ 6/13/97 */

/* define the structure */ /* function prototype */

int main(void) { struct tag *st_ptr; /* a pointer to a structure */ st_ptr = &my_struct; /* point the pointer to my_struct */ strcpy(my_struct.lname,"Jensen"); strcpy(my_struct.fname,"Ted"); printf("\n%s ",my_struct.fname); printf("%s\n",my_struct.lname); my_struct.age = 63; show_name(st_ptr); /* pass the pointer */ return 0; } void show_name(struct tag *p) { printf("\n%s ", p->fname); printf("%s ", p->lname); printf("%d\n", p->age);

/* p points to a structure */

22

Novamente, bastante informao para ser absorvida de uma s vez. O leitor deve compilar e executar os vrios trechos de cdigo e usando um depurador monitar coisas como my_struct e p enquanto executa passo a passo main e seguindo o cdigo at para dentro do cdigo da funo para ver o que est acontecendo.

6. Um Pouco Mais Sobre Strings, e Arrays de Strings


Bem, vamos voltar um pouco s strings. Na discusso que se segue, todas as atribuies devem ser entendidas como sendo globais, ou seja, feitas fora de qualquer funo, incluindo main(). Ns apontamos em um captulo anterior que podemos escrever:
char my_string[40] = "Ted";

o que ir alocar espao para um array de 40 bytes e colocar a string nos primeiros 4 bytes (trs para os caracteres entre aspas e o quarto para guardar o terminador '\0'). Na verdade, se tudo o que quisssemos era armazenar o nome "Ted" poderamos ter escrito:
char my_name[] = "Ted";

e o compilador contaria os caracteres, deixaria espao para o caracter nul e armazenar o todal de quatro caracteres na posio de memria que seria retornada pelo nome do array, neste caso, my_name; Em alguns cdigos, em vez do acima, voc pode ver:
char *my_name = "Ted";

que a abordagem alternativa. Existe uma diferena entre elas? A resposta ... sim. Usando a notao de array 4 bytes de armazenamento no bloco de memria esttica separado, um para cada caracter e um para o caracter nul terminador. Mas na notao de ponteiro os mesmos 4 bytes so requeridos, mais N bytes para armazenar a varivel ponteiro my_name (onde N depende do sistema, mas usualmente um mnimo de 2 bytes e pode ser 4 ou mais). Na notao arary, my_name uma abreviao para &my_name[0] que o endereo do primeiro elemento do array. Como a localizao do array fixo durante o tempo de execuo, esta uma constante (e no uma varivel). Na notao de ponteiro, my_name uma varivel. Sobre qual o melhor mtodo, isto depende do que voc vai fazer no resto do programa.

23

Avancemos um passo adiante e consideremos o que acontece se cada uma destas declaraes feita dentro de uma funo em vez de ser feita globalmente, fora dos limites de qualquer funo.
void my_function_A(char *ptr) { char a[] = "ABCDE" . . } void my_function_B(char *ptr) { char *cp = "FGHIJ" . . }

No caso de my_function_A, o contedo, ou valor(es) do array a[] considerado como sendo os dados. O array dito como tendo inicialmente os valores ABCDE. NO caso de my_function_B o valor do ponteiro cp considerado como sendo o dado. O ponteiro inicialmente aponta para a string FGHIJ. Tanto em my_function_A e my_function_B as definies so variveis locais e assim a string ABCDE armazenada na pilha, bem como o valor do ponteiro cp. A string FGHIJ pode ser armazenada em qualquer lugar. Em meu sistema ela armazenada no segmento de dados. A propsito, a inicializao de variveis automticas de array como eu fiz em my_function_A seria ilegal no velho K&R C e somente "amadureceu" no mais recente ANSI C. Um fato que pode ser importante quando se considera portabilidade e compatibilidade retroativa. Como estamos discutindo o relacionamento/diferenas entre ponteiros e arrays, vamos dar uma olhada em arrays multi-dimensionais. Considere, por exemplo, o array:
char multi[5][10];

O que isto significa? Bem, vamos considerar da seguinte forma.


char multi[5][10];

Vamos pegar a parte sublinhada como se fosse o "nome" de um array. Ento colocando antes a parte char e aps acrescentarmos a parte [10], ns etmos um array de 10 caracteres. Mas o nome multi[5] por si s um array indicando que existem 5 elementos, cada um deles sendo um array de 10 caracteres. Assim ns temos um array de 5 arrays de 10 caracteres cada.

24

Assumindo que tenhamos preenchido este array bidimensional com dados de algum tipo. Na memria, ele pode parecer como tendo sido formado pela inicializao de 5 arrays separados, usando algo como:
multi[0] multi[1] multi[2] multi[3] multi[4] = = = = = {'0','1','2','3','4','5','6','7','8','9'} {'a','b','c','d','e','f','g','h','i','j'} {'A','B','C','D','E','F','G','H','I','J'} {'9','8','7','6','5','4','3','2','1','0'} {'J','I','H','G','F','E','D','C','B','A'}

Ao mesmo tempo, elementos individuais podem ser endereados usando a sintaxe:


multi[0][3] = '3'; multi[1][7] = 'h'; multi[4][0] = 'J';

Como os arrays so contguos na memria, nosso bloco de memria para a matriz acima pode ser algo do tipo:
0123456789abcdefghijABCDEFGHIJ9876543210JIHGFEDCBA ^ |_____ starting at the address &multi[0][0]

Note que eu no escrevi multi[0]="0123456789". Se eu tivesse feito isto, um terminador '\0' seria acrescentado aos caracters contidos entre aspas. Seria este o caso se eu tivesse separado espao para 11 caracteres por linha em vez de 10. Meu objetivo acima ilustrar como a memria arranjada para arrays de duas dimenses. Ou seja, este um array bidimensional de caracteres, NO um array de "strings". Agora, o compilador sabe quantas colunas esto presentes no array de forma que ele pode interpretar multi + 1 como o endereo do 'a' na segunda linha acima. Ou seja, ele acrescenta 10, o nmero de colunas, para obter sua posio. Se estivermos lidando com inteiros e um array com a mesma dimenso o compilador acrescentaria 10*sizeof(int) que, em minha mquina, seria 20. Assim, o endereo de 9 na quarta linha acima seria &multi[3] [0] ou (multi + 3) em notao de ponteiro. Para obter o contedo do segundo elemento na quarta linha ns acrescentaramos 1 a seu endereo e dereferenciaramos o resultado como em
*(*(multi + 3) + 1)

Com um pouco de raciocnio, podemos ver que:


*(*(multi + row) + col) multi[row][col] e do o mesmo resultado.

O programa seguinte ilustra isto usando arrays de inteiros em vez de arrays de caracteres. 25

Programa 6.1
/* Program 6.1 from PTRTUT10.HTM #include <stdio.h> #define ROWS 5 #define COLS 10 int multi[ROWS][COLS]; int main(void) { int row, col; for (row = 0; row < ROWS; row++) { for (col = 0; col < COLS; col++) { multi[row][col] = row*col; } } for (row = 0; row < ROWS; row++) { for (col = 0; col < COLS; col++) { printf("\n%d ",multi[row][col]); printf("%d ",*(*(multi + row) + col)); } } return 0; } 6/13/97*/

Por causa da dupla de-referncia exigida na verso com ponteiros, o nome de um array bidimensional geralmente dito como sendo o equivalente a um ponteiro para ponteiro. Com um array tridimensional ns estaramos lidando com um array de arrays de arrays e alguns diriam que seu nome seria o equivaletne a um ponteiro para ponteiro para ponteiro. Entretanto, ns aqui separamos um bloco de memria inicialmente para o array usando a notao de array. Portanto, estamos lidando com uma constante, no uma varivel. Ou seja, estamos falando sobre um endereo fixo, e no uma varivel ponteiro. A funo de dereferncia usada acima permite que acessemos qualquer elemento no array de arrays sem precisar mudar o valor daquele endereo (o endereo de multi[0][0] como dado pelo smbolo multi).

26

7. Mais Sobre Arrays Multi-Dimensionais


No captulo anterior ns notamos que, dado
#define ROWS 5 #define COLS 10 int multi[ROWS][COLS];

ns podemos acessar os elementos individuais do array multi usando ou


multi[row][col]

ou
*(*(multi + row) + col)

Para entender melhor o que est acontecendo, vamos trocar


*(multi + row)

por X como em
*(X + col)

Agora, a partir disto ns vemos que o X funciona como um ponteiro, j que a expresso de-referenciada e sabemos que col um inteiro. Aqui a aritmtica usada um tipo especial, chamada "aritmtica de ponteiros". Isto significa que, como estamos falando de um array de inteiros, o endereo apontado por (ou seja, pelo valor de) X + col + 1 deve ser maior que o endereo X + col por uma quantia igual a sizeof(int). Como conhecemos o leiaute da memria para arrays de duas dimenses, podemos determinar que na expresso multi + row como a usada acima, multi + row + 1 deve incrementar de valor uma quantia igual necessria para "apontar para" a prxima linha, que neste caso deve ser uma quantia igual a COLS * sizeof(int). Isto diz que se a expresso *(*(multi + row) + col) deve ser avaliada corretamente na execuo, o compilador deve gerar o cdigo que leve em considerao o valor de COLS, isto , a segunda dimenso. Devido equivalncia das duas formas de expresso, isto verdadeiro quer estejamos usando a expresso com ponteiros como aqui, ou a expresso de array multi[row][col]. Assim, para calcular qualquer das expresses, um total de 5 valores devem ser conhecidos: 1. O endereo do primeiro elemento do array, que retornado pela expresso multi, isto , o nome do array. 2. O tamanho do tipo de elementos do array, neste caso, sizeof(int). 27

3. A segunda dimenso do array. 4. O valor do ndice da primeira dimenso, row neste caso. 5. O valor do ndice da segunda dimenso, col neste caso. Dado tudo isto, considere o problema de designar uma funo para manipular os valores de elementos de um array previamente declarado. Por exemplo, pode-se colocar em todos os elementos do array multi o valor 1.
void set_value(int m_array[][COLS]) { int row, col; for (row = 0; row < ROWS; row++) { for (col = 0; col < COLS; col++) { m_array[row][col] = 1; }
} }

E para chamar esta funo usaramos:


set_value(multi);

Agora, a partir da funo ns usamos os valores que foram definidos via #define como ROWS e COLS que estabelecem os limites para os laos. Mas estes #define so apenas constantes no que tange ao compilador, isto , no h nada que conecte estes nmeros ao tamanho do array dentro da funo. row e col so variveis locais, obviamente. A definio formal de parmetros permite que o compilador determine as caractersticas associadas com o valor do ponteiro que ser passado em tempo de execuo. Ns realmente no precisamos a primeira dimenso e, como veremos mais tarde, existem ocasies onde iremos perferir no defin-los dentro da definio de parmetro, fora o hbito ou consistncia, eu no usei aqui. Mas a segunda dimenso deve ser usada e foi vista na expresso para o parmetro. A razo por que precisamos dela para resolver m_array[row][col] j foi descrita. Enquanto o parmetro define o tipo de dados (int neste caso) e as variveis automticas para linha e coluna so definidas nos laos, somente um valor podoe ser passado usando um nico parmetro. Neste caso, o valor de multi como notado na declarao de chamada, isto , o endereo do primeiro elemento, geralmente referido como sendo um ponteiro para o array. Assim, a nica forma que temos de informar o compilador da segunda dimenso incluindo-a explicitamente na definio do parmetro. De fato, em geral todas as dimenses de ordem maior que um so necessrias quando se trata de arrays multi-dimensionais. Ou seja, se estamos falando de arrays de 3 dimenses, a segunda e terceira dimenses devem ser especificadas na definio do parmetro.

28

8. Ponteiros Para Arrays


Ponteiros, obviamente, podem ser "apontados" para qualquer tipo de objeto de dado, incluindo arrays. Enquanto era evidente quando discutamos o programa 3.1, importante expandir sobre como fazer isto quando se trata de arrays multi-dimensionais. Para revisar, no Captulo 2 ns declaramos que dado um array de inteiros ns podemos apontar a um ponteiro de inteiro daquele array usando:
int *ptr; ptr = &my_array[0]; /* aponta nosso ponteiro para o primeiro inteiro em nosso array */

Como declaramos l, o tipo da varivel ponteiro deve ser o mesmo tipo do primeiro elemento do array. Alm disto, podemos usar um ponteiro como parmetro formal de uma funo que projetada para manipular um array, por exemplo. Dado:
int array[3] = {'1', '5', '7'}/ void a_func(int *p);

Alguns programadores podem preferir escrever o prottipo da funo como:


void a_func(int p[]);

o que informa a outros que podero usar esta funo que a mesma feita para tratar os elementos de um array. Obviamente, em qualquer dos casos, o que realmente passado o valor de um ponteiro para o primeiro elemento do array, independente de que notao usada no prottipo da funo ou definio. Note que se a notao de array usada, no h necessidade de passar a dimenso do array j que no estamos passando o array inteiro, apenas o endereo do primeiro elemento. Voltemos agora ao problema do array de 2 dimenses. Como foi dito no captulo anterior, o C interpreta um array bidimensional como um array de arrays de uma s dimenso. Sendo este o caso, o primeiro elemento de um array bidimensional de inteiros um array de uma dimenso de inteiros. E um ponteiro para um array bidimensional de inteiros deve ser um ponteiro para aquele tipo de dados. Uma forma de conseguir isto atravs do uso da palavra-chave "typedef". O typedef atribui um novo nome a um tipo de dados especificado. Por exemplo:
typedef unsigned char byte;

faz com que o nome byte signifique o tipo unsigned char. Portanto

29

byte b[10];

ser um array de unsigned char. Note que na declarao typedef, a palavra byte foi substituda pelo que normalmente seria o nome de nosso unsigned char. Ou seja, a regra para usar typedef que o novo nome para o tipo de dados o nome usado na definio do tipo de dado. Assim, em:
typedef int Array[10];

Array torna-se um tipo de dados para um array de 10 inteiros, isto , Array my_arr; declara my_arr como sendo um array de 10 inteiros e Array arr2d[5]; cria arr2d como um array de 5 arrays de 10 inteiros cada. Note que Array *pld; faz de pld um ponteiro para um array de 10 inteiros. Como *pld aponta para o mesmo tipo de arr2d, atribuir o endereo do array bidimensional arr2d para **pld*, o ponteiro ao array de uma dimenso de 10 inteiros, aceitvel. Isto , pld = &arr2d[0]; ou pld = arr2d; esto corretos. Como o tipo de dados que usamos para nosso ponteiro um array de 10 inteiros, deveremos esperar que incrementar pld por 1 ir mudar seu valor de 10*sizeof(int), que o que acontece. Ou seja, sizeof(*pld) 20. Voc pode provar isto para voc mesmo escrevendo e rodando um pequeno e simples programa. Agora, enquanto usar typedef torna as coisas mais claras para o leitor e mais fceis para o programador, isto realmente no necessrio. O que precisamos uma forma de declarar um ponteiro como pld sem a necessidade da palavra-chave typedef. Isto pode ser feito, e
int (*p1d)[10];

a declarao correta, isto , pld aqui um ponteiro para um array de 10 inteiros da mesma forma que ele seria se fosse declarado usando o tipo Array. Note que isto diferente de
int *p1d[10];

que faria que pld fosse o nome de um array de 10 ponteiros para o tipo int.

30

9. Ponteiros e Alocao Dinmica de Memria


Existem vezes em que conveniente alocar memria durante a execuo do programa usando malloc(), calloc(), ou outras funes de alocao. Usar esta abordagem permite protelar a deciso sobre o tamanho do bloco de memria necessrio para armazenar um array, por exemplo, para o momento de execuo do programa. Ou permite usar uma seo da memria para armazenar um array de inteiros em certo momento no tempo, e quando aquela memria no mais necessria, ela pode ser liberada para outros usos, como o armazenamento de um array de estruturas. Quando a memria alocada, a funo de alocao (como malloc(), calloc(), etc.) retorna um ponteiro. O tipo deste ponteiro depende do tipo do compilador usado, se um velho compilador K&R ou um compilador ANSI novo. Com o compilador antigo o tipo do ponteiro retornado char, com o compilador ANSI void. Se voc est usando um compilador mais antigo, e quer alocar memria para um array de inteiros, voc ter que fazer um cast do ponteiro char retornado para um ponteiro inteiro. Por exemplo, para alocal espao para 10 inteiros, poderamos escrever:
inf *iptr; iptr = (int *)malloc(10 * sizeof(int)); if (iptr == NULL) { ... Rotina de erro vem aqui ... }

Se voc est usando um compilador ANSI, malloc() retorna um ponteiro void e como um ponteiro void pode ser atribudo a uma varivel ponteiro de qualquer tipo, o cast (int *) mostrado acima no necessrio. A dimenso do array pode ser determinada durante a execuo e no necessria durante a compilao. Ou seja, o 10 acima poderia ser uma varivel lida de um arquivo de dados ou teclado, ou calculada baseada em alguma necessidade, durante a execuo. Devido equivalncia entre a notao de array e ponteiros, uma vez que iptr tenha sido criado como mostrado acima, pode-se usar a notao de array. Por exemplo, pode-se escrever:
int k; for (k = 0; k < 10; k++) iptr[k] = 2;

para colocar em todos os elementos o valor 2. Mesmo com um entendimento razoavelmente bom de ponteiros e arrays, se existe um lugar em que o novato em C provavelmente ir tropear na primeira vez que trabalhar, a alocao dinmica de arrays multidimensionais. Em geral, gostaramos de poder acessar elementos destes arrays usando notao de array, e no notao de ponteiro, sempre que

31

possvel. Dependendo da aplicao podemos ou no conhecer as duas dimenses durante a compilao. Isto nos leva a vrias formas de executar a tarefa. Como j vimos, quando alocando dinamicamente um array de uma dimenso, sua dimenso pode ser determinada durante a execuo. Agora, quando usando alocao dinmica de arrays de ordem maior, ns no precisamos saber a primeira dimenso durante a compilao. Se precisamos ou no conhecer as dimenses maiores depende de como iremos escrever o cdigo. Aqui eu irei discutir vrios mtodos para alocar dinamicamente espao para arrays bidimensionais de inteiros. Primeiro, iremos considerar casos em que a segunda dimenso conhecida durante a compilao.

Mtodo 1
Uma forma de lidar com o problema atravs do uso da palavra chave typedef. Para alocar um array bidimensional de inteiros, lembre que as duas notaes seguintes resultam na gerao do mesmo cdigo objeto:
multi[row][col] = 1; *(*(multi + row) + col) = 1;

Tambm verdade que as duas notaes a seguir geram o mesmo cdigo:


multi[row] *(multi + row)

Como o da direita deve resultar em um ponteiro, a notao de array na esquerda tambm deve resultar em um ponteiro. De fato, multi[0] ir retornar um ponteiro para o primeiro inteiro na primeira linha, multi[1] um ponteiro para o primeiro inteiro na segunda linha, etc. Na verdade, multi[n] retorna um ponteiro para o n-simo array deste array de arrays. Aqui a palavra ponteiro est sendo usada para representar um valor de endereo. Apesar deste uso ser comum na literatura, quando estiver lendo este tipo de declarao deve-se ser cuidados para distinguir entre o endereo constante de um array e uma varivel ponteiro que um objeto de dados em si. Considere agora: Programa 9.1
/* Program 9.1 from PTRTUT10.HTM 6/13/97 */

#include <stdio.h> #include <stdlib.h> #define COLS 5 typedef int RowArray[COLS]; RowArray *rptr;

32

int main(void) { int nrows = 10; int row, col; rptr = malloc(nrows * COLS * sizeof(int)); for (row = 0; row < nrows; row++) { for (col = 0; col < COLS; col++) { rptr[row][col] = 17; } } return 0;
}

Neste exemplo eu assumi que se estava usando um compilador ANSI, assim um cast no ponteiro void retornado por malloc() no necessrio. Se voc estiver usando um compilador K&R antigo voc ter de fazer o cast usando:
rptr = (RowArray *)malloc(... etc.

Usando esta abordagem, rptr tem todas as caractersticas de um nome de array (exceto que rptr modificvel), e a notao de array pode ser usada pelo resto do programa. Isto tambm significa que se voc pretende escrever uma funo para modificar o contedo do array, deve usar COLS como parte dos parmetros formais da funo, como ns fizemos quando discutimos a passagem de arrays bidimensionais para uma funo.

Mtodo 2
No Mtodo 1 acima, rptr acabou sendo um ponteiro para o tipo "um array de uma dimenso de COLS inteiros". Acontece que existe uma sintaxe que pode ser usada para este tipo sem a necessidade do typedef. Se escrevermos:
int (*xptr)[COLS];

a varivel xptr ter todas as mesmas caractersticas da varivel rptr no Mtodo 1 acima, e no precisamos usar a palavra-chave typedef. Aqui, xptr um ponteiro para um array de inteiros e o tamanho do array dado pelo #define COLS. A colocao dos parntesis torna a notao de ponteiros predominante, mesmo que a notao de array tenha uma maior precedncia. Isto , se tivssemos escrito
int *xptr[COLS];

teramos definido xptr como um array de ponteiros mantendo um nmero de ponteiros igual ao definido por COLS. No a mesma coisa, de forma nenhuma. Entretanto, arrays

33

de ponteiros tem seu uso na alocao dinmica de arrays bidimensionais, como veremos nos dois mtodos a seguir.

Mtodo 3
Considere o caso onde no conhecemos o nmero de elementos de cada linha durante a compilao, isto , tanto o nmero de linhas e o nmero de colunas devem ser determinados durante a execuo. Uma forma de fazer isto seria criar um array de ponteiros do tipo int e ento alocar espao para cada linha e apontar cada um destes ponteiros a cada uma destas linhas. Considere o exemplo:

Programa 9.2
/* Program 9.2 from PTRTUT10.HTM 6/13/97 */

#include <stdio.h> #include <stdlib.h> int main(void) { int nrows = 5; /* Both nrows and ncols could be evaluated */ int ncols = 10; /* or read in at run time */ int row; int **rowptr; rowptr = malloc(nrows * sizeof(int *)); if (rowptr == NULL) { puts("\nFailure to allocate room for row pointers.\n"); exit(0); } printf("\n\n\nIndex Pointer(hex) Pointer(dec) Diff.(dec)");

for (row = 0; row < nrows; row++) { rowptr[row] = malloc(ncols * sizeof(int)); if (rowptr[row] == NULL) { printf("\nFailure to allocate for row[%d]\n",row); exit(0); } printf("\n %d %p %d", row, rowptr[row],rowptr[row]); if (row > 0) printf("%d",(int)(rowptr[row] - rowptr[row-1])); } } return 0;

34

No cdigo acima, rowptr um ponteiro a um ponteiro do tipo int. Neste caso ele aponta para o primeiro elemento de um array de ponteiros para o tipo int. Considere o nmero de chamadas a malloc():
Para obter espao para o array de ponteiros Para obter espao para as linhas Total 1 chamada 5 chamadas ----6 chamadas

Se voc escolher usar esta abordagem, note que mesmo que voc possa usar a notao de array para acessar elementos individuais do array, como por exemplo, rowptr[row][col] = 17;, isto no significa que os dados no "array bidimensional" estejam contguos na memria. Voc~e pode, entretanto, usar notao de array como se ele fosse um bloco contnuo de memria. Por exemplo, voc pode escrever:
rowptr[row][col] = 176;

exatametne como se rowptr fosse o nome de um array bidimensional criado durante a compilao. Obviamente row e col devem estar nos limites do array que foi criado, exatamente como em um array criado durante a compilao. Se voc quer ter um bloco contguo de memria dedicado ao armazenamento de elementos no array voc pode fazer como no exemplo abaixo:

Mtodo 4
Neste mtodo ns alocamos um bloco de memria para guardar o array inteiro primeiro. Ento criamos um array de ponteiros para apontar para cada linha. Assim, mesmo que o array de ponteiros seja usado, o array em si est contguo na memria. O cdigo como segue: Programa 9.3
/* Program 9.3 from PTRTUT10.HTM #include <stdio.h> #include <stdlib.h> int main(void) { int **rptr; int *aptr; int *testptr; int k; int nrows = 5; evaluated */ 6/13/97 */

/* Both nrows and ncols could be

35

int ncols = 8; int row, col;

/* or read in at run time */

/* we now allocate the memory for the array */ aptr = malloc(nrows * ncols * sizeof(int)); if (aptr == NULL) { puts("\nFailure to allocate room for the array"); exit(0); } /* next we allocate room for the pointers to the rows */ rptr = malloc(nrows * sizeof(int *)); if (rptr == NULL) { puts("\nFailure to allocate room for pointers"); exit(0); } /* and now we 'point' the pointers */ for (k = 0; k < nrows; k++) { rptr[k] = aptr + (k * ncols); } /* Now we illustrate how the row pointers are incremented */ printf("\n\nIllustrating how row pointers are incremented"); printf("\n\nIndex Pointer(hex) Diff.(dec)"); for (row = 0; row < nrows; row++) { printf("\n%d %p", row, rptr[row]); if (row > 0) printf(" %d",(rptr[row] - rptr[row-1])); } printf("\n\nAnd now we print out the array\n"); for (row = 0; row < nrows; row++) { for (col = 0; col < ncols; col++) { rptr[row][col] = row + col; printf("%d ", rptr[row][col]); } putchar('\n'); } puts("\n"); /* and here we illustrate that we are, in fact, dealing with

36

a 2 dimensional array in a contiguous block of memory. */ printf("And now we demonstrate that they are contiguous in memory\n"); testptr = aptr; for (row = 0; row < nrows; row++) { for (col = 0; col < ncols; col++) { printf("%d ", *(testptr++)); } putchar('\n'); } return 0; }

Considere, novamente, o nmero de chamadas a malloc( )


Para obter espao para o array de ponteiros Para obter espao para o array de ptrs Total 1 chamada 1 chamada ----2 chamadas

Agora, cada chamada a malloc() cria um overhead adicional de espao j que malloc() geralmente implementado pelo sistema operacional formando uma lista ligada que contm dados sobre o tamanho do bloco. Mas, mais importante, com grandes arrays (vrias centenas de linhas) manter o controle do que precisa ser liberado quando chega a hora pode ser problemtico. Isto, combinado com a contiguidade do bloco de dados que permite a inicializao de tudo para zero usando o memset(), parece que torna a segunda alternativa a preferida. Como um exemplo final de arrays multidimensionais iremos ilustrar a alocao dinmica de um array de trs dimenses. Este exemplo ir ilustrar uma coisa mais a ser verificada quando se faz este tipo de alocao. Por razes citadas acima, iremos utilizar a abordagem apresentada na segunda alternativa. Considere o seguinte cdigo:

Programa 9.4
/* Program 9.4 from PTRTUT10.HTM #include <stdio.h> #include <stdlib.h> #include <stddef.h> int X_DIM=16; int Y_DIM=5; 6/13/97 */

37

int Z_DIM=3; int main(void) { char *space; char ***Arr3D; int y, z; ptrdiff_t diff; /* first we set aside space for the array itself */ space = malloc(X_DIM * Y_DIM * Z_DIM * sizeof(char)); /* next we allocate space of an array of pointers, each to eventually point to the first element of a 2 dimensional array of pointers to pointers */ Arr3D = malloc(Z_DIM * sizeof(char **)); /* and for each of these we assign a pointer to a newly allocated array of pointers to a row */ for (z = 0; z < Z_DIM; z++) { Arr3D[z] = malloc(Y_DIM * sizeof(char *)); /* and for each space in this array we put a pointer to the first element of each row in the array space originally allocated */ for (y = 0; y < Y_DIM; y++) { Arr3D[z][y] = space + (z*(X_DIM * Y_DIM) + y*X_DIM); }

/* And, now we check each address in our 3D array to see if the indexing of the Arr3d pointer leads through in a continuous manner */ for (z = 0; z < Z_DIM; z++) { printf("Location of array %d is %p\n", z, *Arr3D[z]); for ( y = 0; y < Y_DIM; y++) { printf(" Array %d and Row %d starts at %p", z, y, Arr3D[z][y]); diff = Arr3D[z][y] - space; printf(" diff = %d ",diff); printf(" z = %d y = %d\n", z, y); } }

38

return 0; }

Se voc seguiu este tutorial at este ponto, no deve ter problemas decifrando o cdigo acima com base apenas nos comentrios. H alguns pontos que eu gostaria de destacar. Comecemos com a linha que tem:
Arr3D[z][y] = space + (z*(X_DIM * Y_DIM) + y*X_DIM);

Note que aqui space um ponteiro para caracter, que tem o mesmo tipo de Arr3D[z][y]. importante que quando se est somando um inteiro, como o resultante da expresso (z*(X_DIM * Y_DIM) + y*X_DIM) a um ponteiro, o resultado um novo valor de ponteiro. E quando se est atribuindo valoers de ponteiro a variveis de ponteiro os tipos dos dados do valor e da varivel devem combinar.

10. Ponteiros Para Funes


At agora discutimos ponteiros para objetos de dados. C tambm permite a declarao de ponteiros para funes. Ponteiros para funes tem uma variedade de usos e alguns deles sero discutidos aqui. Considere o seguinte problema real. Voc quer escrever uma funo que capaz de ordenar virtualmente qualquer coleo de dados que podem ser armazenados em um array. Este pode ser um array de strings, ou inteiros, ou floats, ou mesmo estruturas. O algoritmo de ordenamento pode ser o mesmo para todos. Por exemplo, ele pode ser um simples algoritmo bubble sort, ou o algoritmo mais complexo shell sort ou quick sort. Iremos utilizar um simples bubble sort para nossa demonstrao. Sedgewick[1] descreveu o bubble sort usando cdigo C configurando uma funo que quando recebe um ponteiro para um array ir ordenar o mesmo. Se chamarmos esta funo bubble(), um programa de ordenamento descrito por bubble_1.c, que segue:

Programa bubble_1.c
/*-------------------- bubble_1.c --------------------*/

/* Program bubble_1.c from PTRTUT10.HTM #include <stdio.h> int arr[10] = { 3,6,1,2,3,8,4,1,7,2}; void bubble(int a[], int N);

6/13/97 */

39

int main(void) { int i; putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } bubble(arr,10); putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0;

void bubble(int a[], int N) { int i, j, t; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { if (a[j-1] > a[j]) { t = a[j-1]; a[j-1] = a[j]; a[j] = t; } } } }
/*---------------------- end bubble_1.c -----------------------*/

O bubble sort um dos ordenamentos mais simples. O algoritmo examina o array do segundo at o ltimo elemento, comparando cada elemento com o que o precede. Se um dos elementos que precede maior que o elemento atual, os dois so trocados de forma que o maior vai ficando prximo do fim do array. No primeiro passo, isto resulta no maior elemento indo para o fim do array. O array agora limitado a todos os elementos exceto o ltimo e o processo repetido. Isto coloca o prximo maior elemento na posio anterior do maior elemento. O processo repetido por um nmero de vezes igual ao nmero de elementos menos 1. O resultado final um array ordenado. Aqui nossas funes foram feitas para ordenar um array de inteiros. Assim, na linha 1 estamos comparando inteiros e nas linhas 2 a 4 estamos usando um inteiro temporrio para guardar inteiros. O que queremos fazer agora ver se podemos converter este cdigo de forma que possamos usar qualquer tipo de dado, isto , no ficarmos restritos a inteiros.

40

Ao mesmo tempo no queremos ter que analisar nosso algoritmo e o cdigo associado com ele toda vez que usarmos o mesmo. Comeamos removendo a comparao de dentro da funo bubble() de forma a tornar relativamente fcil modificar a funo de comparao sem ter que reescrever pores relacionadas ao algoritmo em sim. O resultado est em bubble_2.c:

Programa bubble_2.c
/*---------------------- bubble_2.c -------------------------*/

/* Program bubble_2.c from PTRTUT10.HTM

6/13/97 */

/* Separating the comparison function */ #include <stdio.h> int arr[10] = { 3,6,1,2,3,8,4,1,7,2}; void bubble(int a[], int N); int compare(int m, int n); int main(void) { int i; putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } bubble(arr,10); putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; } void bubble(int a[], int N) { int i, j, t; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { if (compare(a[j-1], a[j])) {

41

t = a[j-1]; a[j-1] = a[j]; a[j] = t; } } } }

int compare(int m, int n) { return (m > n); }

/*--------------------- end of bubble_2.c -----------------------*/

Se nosso objetivo fazer nossa rotina de ordenamento independente do tipo de dados, uma forma de fazer isto usar ponteiros para o tipo void para apontar para os dados em vez de usar o tipo inteiro. Como partida nesta direo, vamos modificar algumas coisas no programa acima de forma que ponteiros possam ser utilizados. Para comear, vamos ficar com ponteiros para o tipo inteiro. Programa bubble_3.c
/*----------------------- bubble_3.c -------------------------*/

/* Program bubble_3.c from PTRTUT10.HTM #include <stdio.h> int arr[10] = { 3,6,1,2,3,8,4,1,7,2}; void bubble(int *p, int N); int compare(int *m, int *n); int main(void) { int i; putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } bubble(arr,10); putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }

6/13/97 */

42

void bubble(int *p, int N) { int i, j, t; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { if (compare(&p[j-1], &p[j])) { t = p[j-1]; p[j-1] = p[j]; p[j] = t; } } } } int compare(int *m, int *n) { return (*m > *n);
} /*------------------ end of bubble3.c -------------------------*/

Note as alteraes. Estamos agora passando um ponteiro para um inteiro (ou array de inteiros) para bubble(). E de dentro de bubble estamos passando ponteiros para os elementos do array que queremos comparar para a nossa funo de comparao. E, obviamente, estamos dereferenciando estes ponteiros em nossa funo compare() para fazer a comparao em si. Nosso prximo passo ser converter os ponteiros em bubble() para ponteiros do tipo void de forma que a funo fique mais insensvel ao tipo. Isto est mostrado no bubble_4. Programa bubble_4.c
/*------------------ bubble_4.c ----------------------------*/ /* Program bubble_4.c from PTRTUT10,HTM 6/13/97 */

#include <stdio.h> int arr[10] = { 3,6,1,2,3,8,4,1,7,2}; void bubble(int *p, int N); int compare(void *m, void *n); int main(void) { int i; putchar('\n');

43

for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } bubble(arr,10); putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0;

void bubble(int *p, int N) { int i, j, t; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { if (compare((void *)&p[j-1], (void *)&p[j])) { t = p[j-1]; p[j-1] = p[j]; p[j] = t; } } } } int compare(void *m, void *n) { int *m1, *n1; m1 = (int *)m; n1 = (int *)n; return (*m1 > *n1); }
/*------------------ end of bubble_4.c ---------------------*/

Note que, ao fazer isto, em compare() temos que introduzir o casting do ponteiro tipo void para o tipo real sendo ordenado. Mas, como veremos mais tarde, isto no problema. E como o que est sendo passado para bubble() ainda um ponteiro para um array de inteiros, temos que fazer o cast destes ponteiros para ponteiros para inteiros quando passamos os mesmos como parmetros em nossa chamada a compare(). Agora vamos tratar do prolema do que passamos para bubble(). Queremos que o primeiro parmetro para aquela funo seja tambm um ponteiro para void. Mas, isto significa que dentro de bubble() precisamos fazer algo sober a varivel t, que atualmente um inteiro.

44

Alm disso, onde usamos t = p[j-1]; o tipo de p[j-1] precisa ser conhecido para saber quantos bytes copiar para a varivel t (ou o que quer que coloquemos no lugar de t). Atualmente, no bubble_4.c, o conhecimento dentro de bubble() sobre o tipo do dado sendo ordenado (e, portanto, o tamanho de cada elemento individual) obtido do fato que o primeiro parmetro um ponteiro para o tipo inteiro. Se queremos que bubble() ordente qualquer tipo de dado, precisamos fazer que aquele ponteiro aponte para o tipo void. Mas, ao fazer isto, iremos perder a informao sobre o tamanho dos elementos individuais dentro do array. Assim, em bubble_5.c, iremos acrescentar um parmetro separado para tratar esta informao de tamanho. Estas alteraes, de bubble_4.c para bubble_5.c so, talvez, um pouco mais extensivas que as que fizemos nos exemplos anteriores. Assim, compare cuidadosamente os dois mdulos para ver as diferenas. Programa bubble_5.c
/*---------------------- bubble5.c ---------------------------*/ /* Program bubble_5.c from PTRTUT10.HTM 6/13/97 */

#include <stdio.h> #include <string.h> long arr[10] = { 3,6,1,2,3,8,4,1,7,2}; void bubble(void *p, size_t width, int N); int compare(void *m, void *n); int main(void) { int i; putchar('\n'); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } bubble(arr, sizeof(long), 10); putchar('\n'); for (i = 0; i < 10; i++) { printf("%ld ", arr[i]); } } return 0;

45

void bubble(void *p, size_t width, int N) { int i, j; unsigned char buf[4]; unsigned char *bp = p; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { if (compare((void *)(bp + width*(j-1)), (void *)(bp + j*width))) /* 1 */ { t = p[j-1]; */ memcpy(buf, bp + width*(j-1), width); p[j-1] = p[j]; */ memcpy(bp + width*(j-1), bp + j*width , width); p[j] = t; */ memcpy(bp + j*width, buf, width); } } }

/* /* /*

} int compare(void *m, void *n) { long *m1, *n1; m1 = (long *)m; n1 = (long *)n; return (*m1 > *n1); }
/*--------------------- end of bubble5.c ---------------------*/

Note que eu mudei o tipo dos dados do array de int para long para ilustrar as mudanas necessrias na funo compare(). Dentro de bubble() eu me descartei da varivel t (que teramos de ter mudado do tipo int para o tipo long). Eu acrescentei um buffer de tamanho 4 de unsigned char, que o tamanho necessrio para armazenar um long (isto ser novamente alterado em futuras modificaes a este cdigo). O ponteiro unsigned char *bp usado para apontar para a base do array a ser ordenado, isto , o primeiro elemento daquele array. Temos tambm que modificar o que passado a compare(), e como faremos a troca dos elementos que a comparao indica precisarem ser trocados. O uso de memcpy() e a notao de ponteiros em vez da notao de array ajuda na reduo da sensibilidade ao tipo. Novamente, fazer uma comparao cuidadosa de bubble_5.c com bubble_4.c pode resultar em um entendimento maior sobre o que est acontecendo e por qu.

46

Agora vamos para bubble_6.v, onde iremos usar a mesma funo bubble() que usamos em bubble_5 para ordenar strings em vez de inteiros longos. Obviamente temos que alterar a funo de comparao j que a forma que strings so comparadas diferente da forma que inteiros longos so comparados. E em bubble_6.c ns exclumos as linhas de bubble() que estavam comentadas em bubble_5.c

Programa bubble_6.c
/*--------------------- bubble6.c ---------------------*/ /* Program bubble_6.c from PTRTUT10.HTM 6/13/97 */

#include <stdio.h> #include <string.h> #define MAX_BUF 256 char arr2[5][20] = { "Mickey Mouse", "Donald Duck", "Minnie Mouse", "Goofy", "Ted Jensen" }; void bubble(void *p, int width, int N); int compare(void *m, void *n); int main(void) { int i; putchar('\n'); for (i = 0; i < 5; i++) { printf("%s\n", arr2[i]); } bubble(arr2, 20, 5); putchar('\n\n'); for (i = 0; i < 5; i++) { printf("%s\n", arr2[i]); } return 0;

void bubble(void *p, int width, int N)

47

{ int i, j, k; unsigned char buf[MAX_BUF]; unsigned char *bp = p; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { k = compare((void *)(bp + width*(j-1)), (void *)(bp + j*width)); if (k > 0) { memcpy(buf, bp + width*(j-1), width); memcpy(bp + width*(j-1), bp + j*width , width); memcpy(bp + j*width, buf, width); } } } } int compare(void *m, void *n) { char *m1 = m; char *n1 = n; return (strcmp(m1,n1)); } /*------------------- end of bubble6.c ---------------------*/

Mas, o fato que bubble() tenha ficado inalterado daquilo que foi usado em bubble_5.c indica qeu a funo capaz de ordenar uma grande variedade de tipos de dados. O que falta fazer passar para bubble() o nome da funo de comparao que queremos utilizar, de forma que a funo seja realmente universal. Da mesma forma que um array o endereo do primeiro elemento do array no segmento de dados, o nome de uma funo traduz-se para o endereo daquela funo no segmento de cdigo. Assim, precisamos usar um ponteiro para uma funo. Neste caso, a funo de comparao. Ponteiros para funes devem combinar com as funes apontadas no nmero e tipo dos parmetros e o tipo do valor de retorno. Em nosso caso, declaramos nosso ponteiro para funo como:
int (*fptr)(const void *p1, const void *p2); Noet que se tivssemos escrito: int *fptr(const void *p1, const void *p2);

teramos feito o prottipo para uma funo que retorna um ponteiro do tipo int. Isto por que em C o operador parntesis () tem uma precedncia maior que o operador * ponteiro. 48

Colocando o parntesis em torno da string (*fptr) ns indicamos que estamos declarando um ponteiro para uma funo. Abora ns modificamos nossa declarao de bubble() pelo acrscimo, como seu quarto parmetro, um ponteiro de funo do tipo apropriado. assim que o prottipo da funo fica:
void bubble(void *p, int width, int N, int(*fptr)(const void *, const void *));

Quando chamarmos bubble(), ns inserimos o nome da funo de comparao que queremos utilizar. O programa bubble_7.c ilustra como esta abordagem permite o uso da mesma funo bubble() para ordenar diferentes tipos de dados.
Programa bubble_7.c /*------------------- bubble7.c ------------------*/ /* Program bubble_7.c from PTRTUT10.HTM #include <stdio.h> #include <string.h> #define MAX_BUF 256 long arr[10] = { 3,6,1,2,3,8,4,1,7,2}; char arr2[5][20] = { "Mickey Mouse", "Donald Duck", "Minnie Mouse", "Goofy", "Ted Jensen" }; void bubble(void *p, int width, int N, int(*fptr)(const void *, const void *)); int compare_string(const void *m, const void *n); int compare_long(const void *m, const void *n); int main(void) { int i; puts("\nBefore Sorting:\n"); for (i = 0; i < 10; i++) { printf("%ld ",arr[i]); } puts("\n"); /* show the long ints */ 6/10/97 */

49

for (i = 0; i < 5; i++) /* show the strings */ { printf("%s\n", arr2[i]); } bubble(arr, 4, 10, compare_long); /* sort the longs */ bubble(arr2, 20, 5, compare_string); /* sort the strings */ puts("\n\nAfter Sorting:\n"); for (i = 0; i < 10; i++) { printf("%d ",arr[i]); } puts("\n"); for (i = 0; i < 5; i++) { printf("%s\n", arr2[i]); } return 0; } void bubble(void *p, int width, int N, int(*fptr)(const void *, const void *)) { int i, j, k; unsigned char buf[MAX_BUF]; unsigned char *bp = p; for (i = N-1; i >= 0; i--) { for (j = 1; j <= i; j++) { k = fptr((void *)(bp + width*(j-1)), (void *)(bp + j*width)); if (k > 0) { memcpy(buf, bp + width*(j-1), width); memcpy(bp + width*(j-1), bp + j*width , width); memcpy(bp + j*width, buf, width); } } } } int compare_string(const void *m, const void *n) { char *m1 = (char *)m; char *n1 = (char *)n; return (strcmp(m1,n1)); } int compare_long(const void *m, const void *n) { /* show the sorted longs */

/* show the sorted strings */

50

long *m1, *n1; m1 = (long *)m; n1 = (long *)n; return (*m1 > *n1);

/*----------------- end of bubble7.c -----------------*/

Referncias
[1] "Algorithms in C" Robert Sedgewick Addison-Wesley ISBN 0-201-51425-7

Eplogo
Eu escrevi o material que o leitor tem em mos agora para dar uma introduo a ponteiros para novatos no C. Em C, quanto mais se entende sobre ponteiros, maior a flexibilidade que se tem no cdigo escrito. O texto acima foi expandido do meu primeiro trabalho neste tpico que recebeu o nome de ptr_help.txt e era encontrado nas primeiras verses da coleo de cdigo C SNIPPETS de Bob Stout. O contedo nesta verso foi atualizado a partir do cdigo em PTRTUTOT.ZIP includo em SNIP9510.ZIP. Eu estou sempre pronto a aceitar crticas construtivas sobre este material, ou solicitaes de reviso para a adio de outro material relevante. Assim sendo, se voc tiver questes, comentrios, crticas, etc. sobre esta apresentao, eu apreciaria imensamente seu contato via email para minha conta em tjensen@ix.netcom.com.

51

You might also like