Buscar

apontadores e estruturas dinâmicas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 139 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 139 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 139 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Apontadores e Estruturas de Dados Dinaˆmicas em C
Fernando Mira da Silva
Departamento de Engenharia Electrote´cnica e de Computadores
Instituto Superior Te´cnico
Novembro de 2002
Resumo
O C e´ provavelmente a mais flexı´vel das linguagens de programac¸a˜o de alto-nı´vel, mas ap-
resenta uma relativa complexidade sinta´ctica. Uma das maiores dificuldades na abordagem do
C numa disciplina de introduto´ria de programac¸a˜o e´ a necessidade de introduzir os conceitos de
enderec¸o de memo´ria, apontador e memo´ria dinaˆmica.
Este texto foi preparado para apoio a` disciplina de Introduc¸a˜o a` Programac¸a˜o da Licenciatura
em Engenharia Electrote´cnica e Computadores do Instituto Superior Te´cnico. Este texto tenta
focar de modo sistema´tico alguns dos to´picos que maiores du´vidas suscita nas abordagens iniciais
da linguagem: apontadores e estruturas de dados dinaˆmicas. Assim, embora se pressuponha o
conhecimentos dos elementos ba´sicas da linguagem C por parte do leitor – nomeadamente, os
tipos de dados elementares e as estruturas de controlo – o texto e´ mantido ao nı´vel elementar de
uma disciplina introduto´ria de informa´tica.
Na apresentac¸a˜o das estruturas de dados consideradas, que incluem pilhas, filas, listas e
ane´is, introduz-se de forma natural a noc¸a˜o de abstracc¸a˜o de dados, e os princı´pios essenciais
de estruturac¸a˜o e modularidade baseados neste paradigma de programac¸a˜o.
Para o programador experiente em C, alguns dos exemplos de co´digo podera˜o parecer pouco
optimizados. Trata-se de uma opc¸a˜o premeditada que tenta beneficiar a clareza e a simplicidade
algorı´tmica, ainda que em alguns casos esta opc¸a˜o possa sacrificar ligeiramente a eficieˆncia do
co´digo apresentado. Pensamos, no entanto, que esta e´ a opc¸a˜o correcta numa abordagem intro-
duto´ria da programac¸a˜o.
´Indice
1 Introduc¸a˜o 1
2 Apontadores 5
2.1 Motivac¸a˜o para os apontadores em C . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Modelos de memo´ria em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 Apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Func¸o˜es e passagem por refereˆncia . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4.1 Passagem por refereˆncia . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4.2 Erros frequentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5 Vectores e apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5.1 Declarac¸a˜o de vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5.2 Aritme´tica de apontadores . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5.3 ´Indices e apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5.4 Vectores como argumentos de func¸o˜es . . . . . . . . . . . . . . . . . . . 26
2.6 Matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.6.1 Declarac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
IV ´INDICE
2.6.2 Matrizes como argumento de func¸o˜es . . . . . . . . . . . . . . . . . . . 29
2.6.3 Matrizes e vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.6.4 Matrizes e apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.7 Generalizac¸a˜o para mais do que duas dimenso˜es . . . . . . . . . . . . . . . . . . 38
3 Vectores e memo´ria dinaˆmica 41
3.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.3 “Vectores” dinaˆmicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.4 Gesta˜o da memo´ria dinaˆmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.4.1 Verificac¸a˜o da reserva de memo´ria . . . . . . . . . . . . . . . . . . . . . 48
3.4.2 Outras func¸o˜es de gesta˜o de memo´ria dinaˆmica . . . . . . . . . . . . . . 50
3.4.3 Garbbage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.5 Criac¸a˜o dinaˆmica de matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5.2 Matrizes esta´ticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5.3 Matrizes dinaˆmicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.5.4 Vectores de apontadores e matrizes . . . . . . . . . . . . . . . . . . . . 58
4 Listas dinaˆmicas 61
4.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.2 Abstracc¸a˜o de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
´INDICE V
4.3 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4 Listas dinaˆmicas: listar elementos . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.5 Listas: pilhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.5.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.5.2 Declarac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.5.3 Inicializac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.5.4 Sobreposic¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.5.5 Remoc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.5.6 Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.5.7 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6 Listas: filas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.6.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.6.2 Declarac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.6.3 Inicializac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.6.4 Inserc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.6.5 Remoc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.6.6 Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.6.7 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.7 Listas ordenadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.7.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.7.2 Declarac¸a˜o e inicializac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . 88
VI ´INDICE
4.7.3 Listagem ordenada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.7.4 Procura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.7.5 Abstracc¸a˜o de dados e me´todos de teste . . . . . . . . . . . . . . . . . . 91
4.7.6 Inserc¸a˜o ordenada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.7.7 Remoc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
4.7.8 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.8 Variantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4.8.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4.8.2 Listas com registo separado para a base . . . . . . . . . . . . . . . . . . 107
4.8.3 Listas duplamente ligadas . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.8.4 Aneis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
4.9 Anel duplo com registo separado para a base . . . . . . . . . . . . . . . . . . . . 109
4.9.1 Introduc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
4.9.2 Declarac¸a˜o. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.9.3 Inicializac¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.9.4 Listagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.9.5 Procura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.9.6 Inserc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.9.7 Remoc¸a˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.9.8 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4.10 Listas de listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
´INDICE VII
5 Concluso˜es 125
Bibliografia 127
Capı´tulo 1
Introduc¸a˜o
Ate´ ao aparecimento da linguagem C, as linguagens de alto-nı´vel tinham por objectivo dis-
tanciar o programador do hardware especı´fico de trabalho. Pretendia-se, deste modo, que o pro-
gramador focasse a sua actividade na soluc¸a˜o conceptual e algorı´tmica do problema e, simultane-
amente, que o co´digo final fosse independente do hardware e, como tal, facilmente porta´vel entre
diferentes plataformas.
Este princı´pio teo´rico fundamental, ainda hoje correcto em muitas a´reas de aplicac¸a˜o de soft-
ware, conduzia no entanto a problemas diversos sempre que o programador, por qualquer motivo,
necessitava de explorar determinadas regularidades das estruturas de dados de modo a optimizar
zonas crı´ticas do co´digo ou, em outros casos, por ser conveniente ou deseja´vel explorar deter-
minadas facilidades oferecidas pelas instruc¸o˜es do processador que na˜o estavam directamente
disponı´veis na linguagem de alto-nı´vel. Nestas situac¸o˜es, a u´nica alternativa era a programac¸a˜o
directa em linguagem ma´quina destes blocos de co´digo, opc¸a˜o que implicava a revisa˜o do software
em cada alterac¸a˜o ou evoluc¸a˜o de hardware.
Por razo˜es semelhantes, quer o sistema operativo quer ferramentas de sistema como com-
piladores, gestores de ficheiros ou monitores de sistema eram consideradas aplicac¸o˜es que, por
requisitos de eficieˆncia do co´digo, eram incompatı´veis com linguagens de alto-nı´vel. O mesmo
sucedia com todos os programas que necessitavam, de alguma forma, de controlar directamente
dispositivos de hardware. Como corola´rio, estas aplicac¸o˜es eram tradicionalmente escritas total-
mente em linguagem ma´quina, implicando um enorme esforc¸o de desenvolvimento e manutenc¸a˜o
em cada evoluc¸a˜o do hardware.
Quando Ken Thompson iniciou a escrita do sistema operativo Unix (Ritchie e Thmompson,
1974), desenvolveu uma linguagem, designada B, que funcionava no hardware de um computador
2 INTRODUC¸A˜O
Digital PDP-10. O B era uma linguagem pro´xima da linguagem ma´quina, mas que facilitava ex-
traordinariamente a programac¸a˜o de baixo-nı´vel. O B adoptava algumas estruturas decisionais e
ciclos comuns a linguagens de alto-nı´vel e, simultaneamente, disponibilizava um conjunto de fa-
cilidades simples, geralmente so´ acessı´veis em linguagem ma´quina, como o acesso a enderec¸os de
memo´ria e a me´todos de enderec¸amento indirecto. De facto, os mecanismos de acesso a varia´veis
e estruturas de dados previstos no B cobriam a esmagadora maioria das necessidades dos progra-
madores quando anteriormente eram obrigados a recorrer a` linguagem ma´quina. Ora estes mecan-
ismos, embora obviamente dependentes do hardware, obedeciam na sua generalidade ao modelo
de memo´ria previsto na arquitectura de Von Neuman, o qual, nos seus princı´pios essenciais, esta´ na
base da maioria das plataformas computacionais desde os anos 50 ate´ aos nossos dias. Foi assim
surgiu a ideia da possibilidade de desenvolvimento de uma linguagem de alto nı´vel, independente
do hardware, que permitisse simultaneamente um acesso flexı´vel aos modos de enderec¸amento
e facilidades disponı´veis ao nı´vel da linguagem ma´quina da maioria dos processadores. ´E assim
que, em 1983, e´ inventada a linguagem C (Kernighan e Ritchie, 1978), a qual permitiu a re-escrita
de 90% do nu´cleo do sistema operativo Unix em alto-nı´vel.
Para ale´m da manipulac¸a˜o directa de enderec¸os de memo´ria, uma das facilidades introduzida
pela linguagem C foi a incorporac¸a˜o de mecanismos para gesta˜o de memo´ria dinaˆmica. O conceito
de memo´ria dinaˆmica permite que um programa ajuste de modo flexı´vel a dimensa˜o da memo´ria
que utiliza de acordo com as suas necessidades efectivas. Por exemplo, um mesmo programa de
processamento de texto pode ocupar pouca memo´ria se estiver a tratar um documento de pequena
dimensa˜o, ou ocupar um volume mais significativo no caso de um documento de maior nu´mero de
pa´ginas.
A invenc¸a˜o da linguagem C (Kernighan e Ritchie, 1978) permitiu a re-escrita de 90% do
nu´cleo do sistema operativo Unix em linguagem de alto-nı´vel. A possibilidade de manipular
enderec¸os de memo´ria permitiu ainda a implementac¸a˜o eficiente em linguagem de alto nı´vel de
muitos algoritmos e estruturas de dados ate´ enta˜o geralmente escritos em linguagem Assembler
(Knuth, 1973).
Neste texto apresenta-se uma introduc¸a˜o aos apontadores e memo´ria dinaˆmica na linguagem
de programac¸a˜o C. Para exemplificar estes conceitos, sa˜o introduzidas algumas estruturas de dados
dinaˆmicas simples, como pilhas, filas, listas e aneis. Durante a exposic¸a˜o, introduz-se de forma
natural a noc¸a˜o de abstracc¸a˜o de dados, e os princı´pios essenciais de estruturac¸a˜o e modularidade
baseados neste paradigma de programac¸a˜o. Deste modo, a noc¸a˜o de abstracc¸a˜o de dados na˜o e´ in-
troduzida formalmente num capı´tulo auto´nomo, mas sim quando se instroduzem listas dinaˆmicas,
altura em que o conceito e´ fundamental para justificar a estrutura adoptada. Esta abordagem, em-
bora pouco convencional, deriva da nossa experieˆncia na doceˆncia da disciplina de Introduc¸a˜o a`
Programac¸a˜o durante va´rios anos no IST, tendo beneficiado das crı´ticas e sugesto˜es de diversas
3
gerac¸o˜es de alunos.
Tenta-se neste texto dar-se primazia a` clareza algorı´tmica e legibilidade do co´digo, ainda
que em alguns casos esta opc¸a˜o possa sacrificar pontualmente a eficieˆncia do co´digo produzido.
Considera-se, no entanto, que e´ esta a abordagem mais adequada numa disciplina de Introduc¸a˜o
a` Programac¸a˜o. Por outro lado, a maioria dos compiladores recentes incluem processos de
optimizac¸a˜o que dispensam a utilizac¸a˜o explı´cita dos mecanismos de optimizac¸a˜o previstos origi-
nalmente na linguagem C.
Capı´tulo 2
Apontadores
2.1 Motivac¸a˜o para os apontadores em C
2.2 Modelos de memo´ria em C
Embora os mecanismos de enderec¸amento e acesso a` memo´ria tenham sofrido va´rias
evoluc¸o˜es nas u´ltimas de´cadas e sejam evidentemente dependentes do hardware, o C requer apenas
hipo´teses muito simples relativamente ao modelo de memo´ria do processador.
A memo´ria de um computador encontra-se organizada em ce´lulas ou palavras de memo´ria
individuais. Cada palavra de memo´ria e´ referenciada por um enderec¸o e armazena um dado
conteu´do. Designa-se por “nu´mero de bits da arquitectura” o nu´mero ma´ximo de bits que um
processador e´ capaz de ler num u´nico acesso a` memo´ria. Cada palavra de memo´ria de refereˆncia
de um processador tem em geral um nu´mero de bits ideˆntico ao nu´mero de bits da arquitectura1.
Admita-se que num dado programa em C declara, entre outras as varia´veis i,j,k com o tipo
int, a varia´vel f com o tipo float e uma varia´vel d do tipo double, na˜o necessariamente
por esta ordem. Admita-se que apo´s a declarac¸a˜o destas varia´veis sa˜o realizadas as seguintes
atribuic¸o˜es:
i = 2450; j = 11331; k = 113; f = 225.345; d = 22.5E+145;
1A complexidade dos modos de enderec¸amento dos processadores actuais conduz a que a definic¸a˜o e os modelos
aqui apresentados pequem por uma simplicidade excessiva, mas sa˜o suficientespara os objectivos propostos.
6 APONTADORES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
113
11331
225.345
22.5E145 
(double, 64bits)
2450 i
f
d
k
j
???
???
???
Conteúdo Variável
Figura 2.1: Modelo de memo´ria em C (exemplo).
Um exemplo esquema´tico de um modelo memo´ria correspondente a esta configurac¸a˜o encontra-se
representado na figura 2.1. Admite-se aqui que a palavra de memo´ria e o tipo inteiro sa˜o repre-
sentados por 32 bits. De acordo com a figura, durante a compilac¸a˜o foram atribuı´dos a`s varia´veis
i,j,k,f os enderec¸os 1002,1005, 1006 e 1004, respectivamente, enquanto que a` varia´vel d foi
atribuı´do o enderec¸o 10072 . Note-se que, com excepc¸a˜o do tipo double, cada varia´vel tem uma
representac¸a˜o interna de 32 bits, ocupando por isso apenas um enderec¸o de memo´ria. A varia´vel
d, de tipo double, tem uma representac¸a˜o interna de 64 bits, exigindo por isso duas palavras de
memo´ria: os enderec¸os 1007 e 1008. Na figura 2.1 encontram-se representados outras posic¸o˜es
de memo´ria, provavelmente ocupadas por outras varia´veis, e cujo conteu´do e´ desconhecido do
programador.
2Estritamente falando, a cada varia´vel local, e´ apenas atribuı´do um enderec¸o relativo durante a compilac¸a˜o, sendo o
enderec¸o final fixado durante a execuc¸a˜o da func¸a˜o ou activac¸a˜o do bloco de instruc¸o˜es em que a declarac¸a˜o e´ realizada.
Este tema voltara´ a ser abordado na secc¸a˜o 3.2.
APONTADORES 7
2.3 Apontadores
Um apontador em C e´ uma varia´vel cujo conteu´do e´ um enderec¸o de outra posic¸a˜o de
memo´ria. A declarac¸a˜o de varia´veis do tipo apontador pode ser construı´da a partir de qualquer
tipo definido anteriormente, e deve especificar o tipo de varia´vel referenciada pelo apontador.
A declarac¸a˜o de uma varia´vel do tipo apontador realiza-se colocando um “*” antes do nome
da varia´vel. Assim, na declarac¸a˜o
double *pd;
int i;
int *pi;
float f;
int j,k;
double d;
int m,*pi2;
double *pd2,d2;
as varia´veis i,j,k,m sa˜o do tipo int, f e´ do tipo float, e d,d2 sa˜o do tipo double. Ale´m
destas varia´veis de tipos elementares, sa˜o declaradas as varia´veis pi, pi2 do tipo apontador
para inteiro, enquanto que pd e pd2 sa˜o do tipo apontador para double.
Admita-se agora se realiza a seguinte sequeˆncia de atribuic¸o˜es:
i = 2450; f = 225.345; k = 113; d = 22.5E145; m = 9800;
Apo´s estas instruc¸o˜es, o mapa de memo´ria poderia ser o representado na figura 2.2, situac¸a˜o
A. Sublinhe-se que as varia´veis de tipo apontador apenas conteˆm um enderec¸o de memo´ria, in-
dependentemente do tipo referenciado. Desta forma, todas as varia´veis de tipo apontador teˆm
uma representac¸a˜o igual em memo´ria, normalmente de dimensa˜o ideˆntica ao do tipo inteiro (uma
palavra de memo´ria). Note-se que o conteu´do dos apontadores, tal como o de algumas varia´veis
elementares, ainda na˜o foi inicializado, pelo que surgem representados com ???. ´E importante
compreender que cada uma destas varia´veis tem de facto um conteu´do arbitra´rio, resultante da
operac¸a˜o anterior do computador. Claro que estes valores, na˜o tido sido ainda inicializados pelo
programa, sa˜o desconhecidos do programador, mas esta situac¸a˜o na˜o deve ser confundida com a
“auseˆncia de conteu´do”, erro de raciocı´nio frequentemente cometido por programadores principi-
antes.
As varia´veis de tipo elementar podem ser inicializadas pela atribuic¸a˜o directa de valores con-
stantes. A mesma te´cnica pode ser utilizada para a inicializac¸a˜o de apontadores, embora este
8 APONTADORES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
113
9800
225.345
22.5E145 
(double, 64bits)
2450 i
f
d
k
j
pd
pi
1010
1011
1012
1013
pi2
pd2
d2
m
???
???
???
???
(double, 64bits)
???
???
Conteúdo Variável
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
9800
225.345
22.5E145 
1007
1006
2450 i
f
d
k
j
pi
1010
1011
1012
1013
pi2
pd2
d2
m
???
???
???
(double, 64bits)
(double, 64bits)
113
???
Conteúdo Variável
pd
B
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
225.345
22.5E145 
1007
1006
2450 i
f
d
k
j
pi
1010
1011
1012
1013
pi2
pd2
d2
m
(double, 64bits)
(double, 64bits)
113
???
9800
1006
1007
Conteúdo Variável
pd
???
C
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
225.345
22.5E145 
1007
1006
2450 i
f
d
k
j
pi
1010
1011
1012
1013
pi2
pd2
d2
m
(double, 64bits)
(double, 64bits)
113
9800
1006
1007
Conteúdo Variável
pd
22.5E145 
113
D
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
225.345
22.5E145 
1007
1006
2450 i
f
d
k
j
pi
1010
1011
1012
1013
pi2
pd2
d2
m
(double, 64bits)
(double, 64bits)
113
Conteúdo Variável
pd
113
1012
22.5E144
1002
24500
E
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Endereço Conteúdo Variável
i1
i2
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
pi1
pi2
pi3
k1
k2
pk1
pk2
5001
3
4
5003
5004
5006
5003
10
46
F
Figura 2.2: Mapa de memo´ria apo´s diferentes sequeˆncias de atribuic¸a˜o (explicac¸a˜o no texto).
APONTADORES 9
me´todo raramente seja utilizado: em geral, o programador na˜o sabe quais os enderec¸os de memo´ria
disponı´veis no sistema, e a manipulac¸a˜o directa de enderec¸os absolutos tem reduzidas aplicac¸o˜es
pra´ticas.3 Assim, A inicializac¸a˜o de apontadores e´ geralmente realizada por outros me´todos, entre
os quais a utilizac¸a˜o do enderec¸o de outras varia´veis ja´ declaradas. Este enderec¸o pode ser obtido
em C aplicando o operador & a uma varia´vel. Na continuac¸a˜o do exemplo anterior, admita-se que
era realizada agora a seguinte sequeˆncia de atribuic¸o˜es:
pd = &d; pi = &k;
Apo´s esta fase, o mapa de memo´ria seria o representado na figura 2.2, situac¸a˜o B, onde as
varia´veis pd e pi surgem agora preenchidas com os enderec¸os de d e k. Como seria de es-
perar, a consisteˆncia da atribuic¸a˜o exige que a varia´vel referenciada e o tipo do apontador sejam
compatı´veis. Por exemplo, a atribuic¸a˜o pd=&k e´ incorrecta, atendendo a que k e´ do tipo int e
pd esta´ declarada como um apontador para double.
A partir do momento em que os apontadores sa˜o inicializados, o seu conteu´do pode ser
copiado e atribuı´do a outras varia´veis, desde que os tipos ainda sejam compatı´veis. Assim, as
atribuic¸o˜es
pd2 = pd; pi2 = pi;
conduziriam apenas a` co´pia dos enderec¸os guardados em pd e pi para pd2 e pi2. Apo´s esta
sequeˆncia, a situac¸a˜o seria a representada na figura 2.2, caso C.
Uma vez estabelecido um mecanismo para preecher o conteu´do de um apontador, coloca-se a
questa˜o de como utilizar este apontador de modo a aceder aos dados apontados. Este mecanismo e´
suportado no chamado sistema de enderec¸amento indirecto, o qual e´ realizado em C pelo operador
*. Assim, a atribuic¸a˜o j = *pi; tem o significado “consultar o enderec¸oguardado em pi (1006)
e, seguidamente, ler o inteiro cujo enderec¸o e´ 1006 e colocar o resultado (113) na varia´vel j. Deste
modo, apo´s as atribuic¸o˜es
j = *pi; d2 = *pd;
a situac¸a˜o seria a representada na figura 2.2, caso D.
Referiu-se anteriomente que um apontador e´ apenas um enderec¸o de memo´ria. Assim, podera´
questionar-se a necessidade de distinguir um apontador para um inteiro de um apontador para um
3Embora possa ser pontualmente utilizada em programas que lidam directamente com o hardware.
10 APONTADORES
real, por exemplo. De facto, a dependeˆncia do apontador do tipo apontado na˜o tem a ver com
a estrutura do apontador em si, mas sim com o facto de esta informac¸a˜o ser indispensa´vel para
desreferenciar (aceder) correctamente o valor enderec¸ado. Por exemplo, na expressa˜o d2 = *pd,
e´ o facto de pd ser um apontador para double que permite ao compilador saber que o valor
referenciado ocupa na˜o apenas uma, mas duas palavras de memo´ria e qual a sua representac¸a˜o. So´
na posse desta informac¸a˜o e´ possı´vel efectuar a atribuic¸a˜o correcta a d2.
Outras sequeˆncias de atribuic¸a˜o seriam possı´veis. Por exemplo, apo´s a sequeˆncia
pd2 = &d2; *pd2 = *pd1 / 10.0; pi2 = &i; m= *pi2 * 10;
a situac¸a˜o seria a representada no caso E. Note-se que aqui o operador * foi utilizado no lado
esquerdo da atribuic¸a˜o. Assim, por exemplo, a atribuic¸a˜o *pd2 = *pd1 e´ interpretada como
ler o real cujo enderec¸o e´ especificado pelo conteu´do de pd1 e colocar o resultado no enderec¸o
especificado por pd2.
Embora ate´ aqui tenham sido considerados apontadores para tipos elementares, um apontador
pode tambe´m enderec¸ar um outro apontador. Assim, na declarac¸a˜o
int i1,i2,*pi1,**pi2,***pi3;
int k1,k2,*pk1,**pk2;
enquanto que p1 e´ um apontador para um inteiro, p2 e´ um apontador para um apontador para um
inteiro e p3 e´ um apontador para um apontador para um apontador para um inteiro. Como seria de
esperar, a inicializac¸a˜o destes apontadores realiza-se segundo as mesmas regras seguidas para os
apontadores simples, sendo apenas necessa´rio ter em atenc¸a˜o, o nı´vel correcto de enderec¸amento
indirecto mu´ltiplo. Assim, se apo´s a declarac¸a˜o anterior, fosse executada a sequeˆncia de instruc¸o˜es
i1 = 3; i2 = 4;
pi1 = &i1; pi2 = &pi1; pi3 = &pi2;
k1 = 10;
pk1 = &k1; pk2 = pi2;
k2 = i1 * ***pi3 + *pi1 + i2 + **pk2 * *pk1;
a situac¸a˜o final poderia ser a representada na figura 2.2, caso F.
Em apontadores mu´tilpos a possibilidade de desreferenciar um apontador continua a ser de-
pendente do tipo apontado. Deste modo, um apontador para um inteiro e um apontador para um
apontador para um inteiro sa˜o tipos claramente distintos e cujos conteu´dos na˜o podem ser mu-
tuamente trocados ou atribuı´dos. Por outro lado, os nı´veis de indirecc¸a˜o devem ser claramente
FUNC¸O˜ES E PASSAGEM POR REFEREˆNCIA 11
respeitados. Assim, a atribuic¸a˜o i1 = *pk2; no exemplo precedente seria incorrecta, ja´ que i1
e´ do tipo inteiro e, *pk2 e´ um apontador para inteiro (recorde-se que pk2 e´ um apontador para um
apontador para um inteiro).
Como e´ sabido, o valor de uma varia´vel nunca deve ser utilizado antes de esta ser inicializada
explicitamente pelo programa. Com efeito, no inı´cio de um programa, as varia´veis teˆm um valor
arbitra´rio desconhecido. Por maioria de raza˜o, o mesmo princı´pio deve ser escrupulosamente
seguido na manipulac¸a˜o de apontadores. Suponha-se, por exemplo que, no inı´cio de um programa,
sa˜o incluı´das a declarac¸a˜o e as instruc¸o˜es seguintes:
int *p,k;
k = 4; *p = k*2;
Aqui, a segunda atribuic¸a˜o especifica que o dobro de k (valor 8) deve ser colocado no enderec¸o
especificado pelo conteu´do da varia´vel p. No entanto, dado que p na˜o foi inicializada, o seu
conteu´do e´ arbitra´rio. Com elevada probabilidade, o seu valor corresponde a um enderec¸o de
memo´ria inexistente ou inva´lido. No entanto, o C na˜o realiza qualquer juı´zo de valor sobre esta
situac¸a˜o e, tendo sido instruı´do para “colocar 8 no enderec¸o indicado por p” tentara´ executar esta
operac¸a˜o. A tentativa de escrita numa posic¸a˜o de memo´ria inva´lida ou protegida conduzira´ ou ao
compromisso da integridade do sistema operativo se o espac¸o de memo´ria na˜o for conveniente-
mente protegido, como e´ o caso do DOS. Se o sistema tiver um modo protegido, como o Unix ou o
Windows NT, esta situac¸a˜o pode originar um erro de execuc¸a˜o, devido a uma violac¸a˜o de memo´ria
detectada pelo sistema operativo. Os erros de execuc¸a˜o conduzem a` interrupc¸a˜o imediata, em erro,
do programa.
Sublinhe-se que nas figuras desta secc¸a˜o foram utilizados enderec¸os especı´ficos nos aponta-
dores de modo a melhor demonstrar e explicar o mecanismo de funcionamento dos mecanismos de
apontadores e indirecc¸a˜o em C. No entanto, na pra´tica, o programador na˜o necessita de conhecer o
valor absoluto dos apontadores, sendo suficiente a manipulac¸a˜o indirecta do seu conteu´do atrave´s
dos mecanismos de referenciac¸a˜o e desreferenciac¸a˜o descritos.
2.4 Func¸o˜es e passagem por refereˆncia
2.4.1 Passagem por refereˆncia
Em C, na chamada de uma func¸a˜o, os paraˆmetros formais da func¸a˜o recebem sempre uma
co´pia dos valores dos paraˆmetros actuais. Este mecanismo de passagem de argumentos e´ desig-
12 APONTADORES
nado passagem por valor (Martins, 1989) e esta´ subjacente a todas as chamadas de func¸o˜es em
C. Esta situac¸a˜o e´ adequada se se pretender apenas que os argumentos transmitam informac¸a˜o do
mo´dulo que chama para dentro da func¸a˜o. Dado que uma func¸a˜o pode tambe´m retornar um valor,
este mecanismo ba´sico e´ tambe´m adequado quando a func¸a˜o recebe va´rios valores de entrada e
tem apenas um valor de saı´da. Por exemplo, uma func¸a˜o que determina o maior de treˆs inteiros
pode ser escrita como
int maior_3(int i1,int i2, int i3){
/*
* Func¸a˜o que determina o maior de treˆs inteiros
*
*/
if((i1 > i2) && (i1 > i3))
return i1;
else
return (i2 > i3 ? i2 : i3);
}
Existem no entanto situac¸o˜es em que se pretende que uma func¸a˜o devolva mais do que um
valor. Uma situac¸a˜o possı´vel seria uma variante da func¸a˜o maior_3 em que se pretendesse
determinar os dois maiores valores, e na˜o apenas o maior. Outro caso tı´pico e´ o de uma func¸a˜o
para trocar o valor de duas varia´veis entre si. Numa primeira tentativa, poderia haver a tentac¸a˜o de
escrever esta func¸a˜o como
#include <stdio.h>
#include <stdlib.h>
void trocaMal(int x,int y){
/*
* ERRADO
*/
int aux;
aux = x;
x = y;
y = aux;
}
int main(){
int a,b;
printf("Indique dois nu´meros: ");
scanf("%d %d",&a,&b);
trocaMal(&a,&b);
printf("a = %d, b= %d\n",a,b);
FUNC¸O˜ES E PASSAGEM POR REFEREˆNCIA 13
exit(0);
}
No entanto, este programa na˜o funciona: o mecanismo de passagem por valor implica que a
func¸a˜o troca opera correctamente sobre as varia´veis locais x e y, trocando o seu valor, mas estas
varia´veis na˜o sa˜o mais do que co´pias das varia´veis a e b que, como tal, se manteˆm inalteradas.
Na figura 2.3 representa-se a evoluc¸a˜o do mapa de memo´ria e do conteu´do das varia´veis durante a
chamada a` func¸a˜o trocaMal.
Este aparente problema pode ser resolvido pela utilizac¸a˜o criteriosa de apontadores. A func¸a˜o
troca especificada anteriormente pode ser correctamente escrita como se segue:
#include <stdio.h>
#include <stdlib.h>
void troca(int *x,int *y){
/*
* Func¸a˜o que troca dois argumentos
*/
int aux;
aux = *x;
*x = *y;
*y = aux;
}
int main(){
int a,b;
printf("Indique dois nu´meros: ");
scanf("%d %d",&a,&b);
troca(&a,&b);
printf("a = %d, b= %d\n",a,b);
exit(0);
}
Tal como pode ser observado, a soluc¸a˜o adoptada consiste em passar a` func¸a˜o na˜o o valor das
varia´veis a e b, mas sim os seus enderec¸os. Embora estes enderec¸os sejam passados por valor (ou
seja, a func¸a˜o recebe uma co´pia destes valores), o enderec¸opermite a` func¸a˜o o conhecimento da
posic¸a˜o das varia´veis a b em memo´ria e, deste modo, permite a manipulac¸a˜o do seu conteu´do por
meio de um enderec¸amento indirecto.
Um hipote´tico exemplo de um mapa memo´ria associado ao funcionamento do programa
troca encontra-se representado na figura 2.4. Admita-se que a declarac¸a˜o de varia´veis no pro-
14 APONTADORES
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
.
.
.
.
.
.
.
.
.
a8
b2
2135
2136
A
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
Variáveis
troca()
da funçao
a8
b2
2135
x
y
aux
2136
2133
2132
2131 8
2
B
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
Variáveis
troca()
da funçao
a
b
2135
x
y
aux
2136
2133
2132
2131
8
8
2 8
2
8
2
C
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
.
.
.
.
.
.
.
.
.
a8
b2
2135
2136
D
Figura 2.3: Mapa de memo´ria durante as diferentes fase de execuc¸a˜o de um programa que utiliza
a func¸a˜o trocaMal. A - antes da chamada a` func¸a˜o, B - no inı´cio de troca, C - no final de
troca, D - apo´s o regresso ao programa principal. O mecanismo de passagem por valor conduz
a que os valores do programa principal na˜o sejam alterados.
FUNC¸O˜ES E PASSAGEM POR REFEREˆNCIA 15
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
.
.
.
.
.
.
.
.
.
a8
b2
2135
2136
A
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
Variáveis
troca()
da funçao
a8
b2
2135
x
y
aux
2136
2133
2132
2131 2135
2136
B
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
Variáveis
troca()
da funçao
a
b
2135
x
y
aux
2136
2133
2132
2131 2135
2136
8
8
2 8
2
C
Endereço Conteúdo Variável
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Variáveis
main()
do bloco
.
.
.
.
.
.
.
.
.
a
b
2135
2136
2
3
D
Figura 2.4: Mapa de memo´ria durante as diferentes fase de execuc¸a˜o do programa que utiliza a
func¸a˜o troca. A - antes da chamada a` func¸a˜o, B - no inı´cio de troca C - no final de troca, D -
apo´s o regresso ao programa principal. A passagem de apontadores para as varia´veis do programa
principal (passagem por refereˆncia) permite que a func¸a˜o altere as varia´veis do programa principal.
16 APONTADORES
grama principal atribuiu a`s varia´veis A e B os enderec¸os 2135 e 2136, e que estas foram inicial-
izadas pelo utilizador com os valores 8 e 2, respectivamente. A situac¸a˜o imediatamente antes da
chamada da func¸a˜o troca encontra-se representada em A. Durante a chamada da func¸a˜o, realiza-
se a activac¸a˜o das varia´veis x, y e aux, locais a` func¸a˜o, eventualmente numa zona de memo´ria
afastada daquela onde se encontram as varia´veis a e b, sendo as duas primeiras destas varia´veis
inicializadas com os enderec¸os de a e b (situac¸a˜o B). Atrave´s do enderec¸amento indirecto atrave´s
das varia´veis x e y, sa˜o alterados os valores das varia´veis a e b do programa principal, atingido-se
a situac¸a˜o C. Apo´s o regresso ao programa principal, as varia´veis da func¸a˜o troca sa˜o libertadas,
atingindo-se a situac¸a˜o representada em D, com as
Note-se que, estritamente falando, a passagem de argumento se deu por valor, atendendo a que
x e y sa˜o varia´veis locais a` func¸a˜o, tendo recebido apenas valores correspondentes ao enderec¸o de
varia´veis declaradas no programa principal. No entanto, neste tipo de mecanismo, diz-se tambe´m
que as varia´veis a e b foram passadas por refereˆncia(Martins, 1989), atendendo a que o seu
enderec¸o (e na˜o o seu conteu´do) que foi passadas a` func¸a˜o.
Mais genericamente, sempre que e´ necessa´rio que uma func¸a˜o altere o valor de um ou mais
dos seus argumentos, este ou estes devera˜o ser passados por refereˆncia, de forma a ser possı´vel a`
func¸a˜o modificar o valor das varia´veis por um mecanismo de indirecc¸a˜o. ´E por este motivo que,
na chamada da func¸a˜o scanf(), todas varia´veis a ler sa˜o passados por refereˆncia, de modo a ser
possı´vel a esta func¸a˜o poder ler e alterar os valores das varia´veis do programa principal.
2.4.2 Erros frequentes
A tentativa de desdobramento do mecanismo de referenciac¸a˜o de uma varia´vel quando uma
dado programa envolve va´rios nı´veis de func¸o˜es e´ um erro frequente de programadores principi-
antes ou com reduzida experieˆncia de C. Considere-se o seguinte troc¸o de programa, em que se
pretende que a varia´vel x do programa principal seja modificada na func¸a˜o func2. Neste exem-
plo, o programador adoptou desnecessariamente uma referenciac¸a˜o (ca´lculo do seu enderec¸o) da
varia´vel a modificar em cada um dos nı´veis de chamada da func¸a˜o:
/*
Utilizac¸a˜o incorrecta (desnecessa´ria)
de refereˆncias mu´ltiplas.
*/
void func2(int **p2,int b2){
**p2 = -b2 * b2;
}
void func1(int *p1,int b1){
b1 = b1 + 1;
FUNC¸O˜ES E PASSAGEM POR REFEREˆNCIA 17
func2(&p1,b1);
}
int main(){
int x;
func1(&x,5);
return 0;
}
Como pode ser facilmente entendido, a referenciac¸a˜o de uma varia´vel uma u´nica vez e´ suficiente
para que este mesmo enderec¸o possa ser sucessivamente passado entre os va´rios nı´veis de func¸o˜es
e ainda permitir a alterac¸a˜o da varia´vel seja sempre possı´vel. Assim, embora o programa anterior
funcione, a referenciac¸a˜o de p1 na passagem de func1 para func2 e´ inu´til: o mecanismo ali
adoptado so´ faria sentido caso se pretendesse que func2 alterasse o conteu´do da varia´vel p1.
Como e´ evidente na˜o e´ esse o caso, e o programa anterior correctamente escrito tomaria a seguinte
forma:
/*
Utilizac¸a˜o correcta de uma passagem
por refereˆncia entre va´rios nı´veis
de func¸o˜es.
*/
void func2(int *p2,int b2){
*p2 = -b2 * b2;
}
void func1(int *p1,int b1){
b1 = b1 + 1;
func2(p1,b1);
}
int main(){
int x;
func1(&x,5);
return 0;
}
Um outro erro frequente em programadores principiantes e´ passagem ao programa principal
ou func¸a˜o que chama a refereˆncia de uma varia´vel local a` func¸a˜o evocada. Este tipo de erro pode
ser esquematizado pelo seguinte programa:
int *func(int a){
int b,*c;
b = 2 * a;
c = &b;
return c;
18 APONTADORES
}
int main(){
int x,*y;
y = func1(1);
x = 2 * *y;
return 0;
}
Aqui, a varia´vel b e´ local a` func¸a˜o func e, como tal, e´ criada quando func e´ activada e a
sua zona de memo´ria libertada quando a func¸a˜o termina. Ora o resultado da func¸a˜o func e´
passada ao programa principal sob a forma do enderec¸ode b. Quando o valor desta varia´vel e´
lido no programa principal por meio de um enderec¸amento indirecto na expressa˜o x = 2 * *y,
a varia´vel b ja´ na˜o esta´ activa, realizando-se por isso um acesso inva´lido a` posic¸a˜o de memo´ria
especificada pelo enderec¸o em y. Com elevada probabilidade, o resultado final daquela expressa˜o
sera´ incorrecto.
2.5 Vectores e apontadores
2.5.1 Declarac¸a˜o de vectores
Um vector em C permite a criac¸a˜o de uma estrutura com ocorreˆncias mu´ltiplas de uma varia´vel
de um mesmo tipo. Assim, a declarac¸a˜o
int x[5] = {123,234,345,456,567};
double y[3] = {200.0,200.1,200.2};
declara um vector x de 5 inteiros, indexado entre 0 e 4, e um vector y de 3 reais de dupla pre-
cisa˜o, indexado entre 0 e 2, inicializados na pro´pria declarac¸a˜o. Um possı´vel mapa de memo´ria
correspondente a esta declarac¸a˜o encontra-se representado na figura 2.5.1.
Cada elemento individual de um vector pode ser referenciado acrescentando a` frente do nome
da varia´vel o ı´ndice, ou posic¸a˜o que se pretende aceder, representado por um inteiro entre [].
Para um vector com N posic¸o˜es, o ı´ndice de acesso varia entre 0 (primeira posic¸a˜o) e N − 1
(u´ltima posic¸a˜o). Cada elemento de x e y corresponde a uma varia´vel de tipo inteiro ou double,
respectivamente. Deste modo, se se escrever,
int x[5] = {123,234,345,456,567};
VECTORES E APONTADORES 19
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
1010
1011
Conteúdo Variável
x[0]
x[1]
x[2]
x[3]
x[4]
y[0]
y[1]
y[2]
123
234
345
456
789
200.0
200.1
200.2
Figura 2.5: Mapa de memo´ria correspondente a` declarac¸a˜o de dois vectores
20 APONTADORES
double y[3] = {200.0,200.1,200.2};
double a;
a = y[1] + x[3];
o conteu´do final de a sera´ o resultado da soma da segunda posic¸a˜o de y com a quarta posic¸a˜o de
x, ou seja 656.1.
Dado que cada elemento de um vector e´ uma varia´vel simples, e´ possı´vel determinar o seu
enderec¸o. Assim, e´ possı´vel fazer
int x[5] = {123,234,345,456,567};
double y[3] = {200.0,200.1,200.2};
int *pi; double *pd;
pi = &(x[2]);
pd = &(y[1]);
conduzindo-se assim a` situac¸a˜o representada na figura 2.5.1. Note-se que nas atribuic¸o˜es
pi = &(x[2]) e pd = &(y[1]) os parenteses poderiam ser omitidos, dado que o oper-
ador [] (ı´ndice) tem precedeˆncia sobre o operador & (enderec¸o de). Assim, aquelas atribuic¸o˜es
poderiam ser escritas como pi = &x[2] e pd = &y[1].
Uma das vantagens da utilizac¸a˜o de vectores e´ o ı´ndice de acesso poder ser uma varia´vel.
Deste modo, inicializac¸a˜o de um vector de 10 inteiros a 0 pode ser feita pela sequeˆncia
#define NMAX 10
int main(){
int x[NMAX];
int k;
for(k = 0; k < NMAX ; k++)
x[k] = 0;
/* ...*/
Uma utilizac¸a˜o comum dos vectores e´ a utilizac¸a˜o de vectores de caracteres para guardar
texto. Por exemplo a declarac¸a˜o
char texto[18]="Duas palavras";
VECTORES E APONTADORES 21
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
1010
1011
Conteúdo Variável
x[0]
x[1]
x[2]
x[3]
x[4]
y[0]
y[1]
y[2]
123
234
345
456
789
200.0
200.1
200.2
1012
1013
pi
pd
1003
1008
Figura 2.6: Apontadores e vectores (explicac¸a˜o no texto).
22 APONTADORES
’u’ ’a’ ’s’ ’ ’ ’p’’a’ ’l’’a’ ’v’ ’r’ ’a’ ’s’’\0’’D’
 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
texto[18]
Figura 2.7: Utilizac¸a˜o de vectores de caracteres para armazenar texto.
cria um vector de 18 caracteres, e inicia as suas primeiras posic¸o˜es com o texto Duas palavras.
Uma representac¸a˜o gra´fica deste vector depois de inicializado e´ apresentada na figura 2.7.
Dado que o texto pode ocupar menos espac¸o que a totalidade do vector, como sucede neste
caso, a u´ltima posic¸a˜o ocupada deste vector e´ assinalada pela colocac¸a˜o na posic¸a˜o seguinte
a` u´ltima do caracter com o co´digo ASCII 0, geralmente representado pela constante inteira 0
ou pelo caracter ’\0’. Note-se que esta u´ltima representac¸a˜o na˜o deve ser confundida com a
representac¸a˜o do algarismo zero ’0’, internamente representado por 48, o co´digo ASCII de 0.
2.5.2 Aritme´tica de apontadores
´E possı´vel adicionar ou subtrair uma constante inteira de uma varia´vel de tipo apontador. Este
tipo de operac¸a˜o so´ faz sentido em vectores ou estruturas de dados regulares, em que o avanc¸o ou
recuo de um apontador conduz a um outro elemento do vector (ou estrutura de dados regulares).
´E da u´nica e exclusiva responsabilidade do programador garantir que tais operac¸o˜es aritme´ticas
se manteˆm dentro dos enderec¸os va´lidos da estrutura de dados apontada. Assim, por exemplo, se
na sequeˆncia do exemplo e da situac¸a˜o representada na figura 2.5.1 (repetida por convenieˆncia na
figura 2.5.2, A) fossem executadas as instruc¸o˜es
pi = pi + 2;
pd = pd - 1;
a situac¸a˜o resultante seria a representada na figura 2.5.2, B.
No caso do modelo de memo´ria adoptado como exemplo, a operac¸a˜o aritme´tica sobre o apon-
tador inteiro tem uma correspondeˆncia directa com a operac¸a˜o realizada sobre o enderec¸o. No
entanto, o mesmo na˜o sucede com o apontador para double, onde subtracc¸a˜o de uma unidade
ao apontador corresponde na realidade a uma reduc¸a˜o de duas unidades no enderec¸o fı´sico da
memo´ria.
De facto, a aritme´tica de apontadores em C e´ realizada de forma a que o incremento ou decre-
mento unita´rio corresponda a um avanc¸o ou recuo de uma unidade num vector, independentemente
do tipo dos elementos do vector. O compilador de C garante o escalamento da constante adicionada
VECTORES E APONTADORES 23
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
1010
1011
Conteúdo Variável
x[0]
x[1]
x[2]
x[3]
x[4]
y[0]
y[1]
y[2]
123
234
345
456
789
200.0
200.1
200.2
1012
1013
pi
pd
1003
1008
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço
1010
1011
Conteúdo Variável
x[0]
x[1]
x[2]
x[3]
x[4]
y[0]
y[1]
y[2]
123
234
345
456
789
200.0
200.1
200.2
1012
1013
pi
pd
1005
1006
B
Figura 2.8: Aritme´tica de apontadores (explicac¸a˜o no texto).
24 APONTADORES
ou subtraı´da de acordo com a dimensa˜o do tipo apontado. Este modo de operac¸a˜o garante que,
na pra´tica, o programador possa realizar operac¸o˜es sobre apontadores abstraindo-se do nu´mero
efectivo de bytes do elemento apontado.
Ja´ anteriormente se mencionou que em C o tipo do apontador depende do objecto apontado, de
modo a ser possı´vel determinar, num enderec¸amento indirecto, qual o tipo da varia´vel referenciada.
A aritme´tica de apontadores reforc¸a esta necessidade, dado que o seu incremento ou decremento
exige o conhecimento da dimensa˜o do objecto apontado.
A aritme´tica de apontadores define apenas operac¸o˜es relativas de enderec¸os. Assim, embora
seja possı´vel adicionar ou subtrair constantes de apontadores, na˜o faz sentido somar apontadores.
Numa analogia simples, considere-se que os enderec¸os sa˜o os nu´meros da porta dos edifı´cios de
uma dada rua: faz sentido referir “dois nu´meros depois do pre´dio 174” ou “treˆs nu´meros antes do
180”, mas na˜o existe nenuma aplicac¸a˜o em que fac¸a sentido adicionar dois nu´meros de porta (174
e 180, por exemplo). No entanto, faz sentido referir “o nu´mero de pre´dios entre o 174 e o 180”:
de modo equivalente, tambe´m em Ca subtracc¸a˜o de apontadores e´ possı´vel, desde que sejam do
mesmo tipo. Como e´ evidente, tal como na adic¸a˜o, a subtracc¸a˜o de operadores e´ convenientemenet
escalada pela dimensa˜o do objecto apontado:
int x[5] = {123,234,345,456,567};
double y[3] = {200.0,200.1,200.2};
int *pi1,*pi2; double *pd1,*pd2;
int di,dd;
pi1 = &x[2]; pi2 = &x[0]; di = pi1 - pi2; /* di <- 2 */
pd1 = &y[1]; pd2 = &y[3]; dd = pd1 - pd2; /* dd <- -1 */
.
Uma u´ltima nota relativamente aos apontadores de tipo void. O C-ANSI permite a definic¸a˜o
de apontadores gene´ricos, cujo tipo e´ independente do objecto apontado. Um apontador pv deste
tipo manipula um enderec¸o gene´rico e pode ser simplesmente declarado por
void *pv;
e encontra utilizac¸a˜o em situac¸o˜es em que se pretende que uma mesma estrutura possa enderec¸ar
entidades ou objectos de tipos diferentes. Sempre que possı´vel, este tipo de situac¸o˜es deve ser
preferencialmente abordado pela utilizac¸a˜o de estruturas do tipo union. No entanto, existem
casos em que tal na˜o e´ possı´vel, obrigando a` utilizac¸a˜o deste tipo de apontadores. ´E, por exemplo,
o caso das func¸o˜es de gesta˜o de memo´ria, que trabalham com apontadores gene´ricos (V. secc¸a˜o
3.3). Note-se, no entanto, que o C na˜o consegue desreferenciar automaticamente um apontador
VECTORES E APONTADORES 25
para void (ou seja, aceder ao conteu´do apontado por). De igual modo, o desconhecimento da
dimensa˜o do objecto apontado impede que a aritme´tica de apontadores seja aplica´vel a apontadores
deste tipo.
2.5.3 ´Indices e apontadores
Dos princı´pios gerais referidos anteriormente, resulta que atribuic¸a˜o do 3o elemento de um
vector x a uma varia´vel a pode ser realizada directamente por
a = x[2];
ou, de forma equivalente, por
a = *(&x[0] + 2);
sendo o resultado ideˆntico. Enquanto que no primeiro caso se adopta um mecanismo de
indexac¸a˜o directa, no segundo caso determina-se um apontador para o primeiro elemento do
vector, incrementa-se o apontador de duas unidades para enderec¸ar o 3o elemento e, finalmente,
aplica-se o operador “*” para realizar o enderec¸amento indirecto.
De facto, a segunda expressa˜o pode ser simplificada. O C define que o enderec¸o do primeiro
elemento de um vector pode ser obtido usando simplesmente o nome do vector, sem o operador
de indexac¸a˜o (“[0]”) a` frente. Ou seja, no contexto de uma expressa˜o em C, “ &x[0]” e´ equiv-
alente a usar simplesmente “ x”. Por outras palavras, se x[] e´ um vector do tipo xpto, x e´ um
apontador para o tipo xpto. Assim, a expressa˜o “ x[k]” e´ sempre equivalente a “ *(xk)+”. Este
facto conduz ao que designamos por regra geral de indexac¸a˜o em C, que pode ser enunciada pela
equivaleˆncia
x[k] <-> *(x + k)
Registe-se, como curiosidade, que a regra geral de indexac¸a˜o conduz a que, por exemplo,
x[3] seja equivalente a 3[x]. Com efeito,
x[3] <-> *(x+3) <-> *(3+x) <-> 3[x]
Claro que a utilizac¸a˜o desta propriedade e´ geralmente proibido, na˜o pelo C, mas pelas normas de
boa programac¸a˜o!
26 APONTADORES
2.5.4 Vectores como argumentos de func¸o˜es
Para usar um vector como argumento de uma func¸a˜o func() basta, no bloco que chama
func(), especifiar o nome da varia´vel que se pretende como argumento. Assim, pode fazer-se,
por exemplo,
int x[10];
/* ... */
func(x);
/* ... */
No entanto, e´ necessa´rio ter em conta que x, quando usado sem ı´ndice, representa apenas o
enderec¸o da primeira posic¸a˜o do vector. Como deve enta˜o ser declarado o argumento formal
correspondente no cabec¸alho de func?
Diversas opc¸o˜es existem para realizar esta declarac¸a˜o. No exemplo seguinte, a func¸a˜o set e´
utilizada para inicializar os NMAX elementos do vector a do programa principal com os inteiros
entre 1 e NMAX. Numa primeira variante, o argumento formal da func¸a˜o e´ apenas a repetic¸a˜o da
declarac¸a˜o efectuada no programa principal. Assim,
#define NMAX 100
void set(int x[NMAX],int n){
int k;
for(k = 0; k < n; k++) x[k] = k+1;
}
int main(){
int a[NMAX];
set(a,NMAX);
/* ... */
}
Note-se que para uma func¸a˜o manipular um vector e´ suficiente conhecer o enderec¸o do
primeiro elemento. Dentro da func¸a˜o, o modo de aceder ao k-e´simo elemento do vector e´ sempre
adicionar k ao enderec¸o da base, independentemente da dimensa˜o do vector. Ou seja, o paraˆmetro
formal int x[NMAX] apenas indica que x e´ um apontador para o primeiro elemento de um vec-
tor de inteiros. Deste modo, o C na˜o usa a informac¸a˜o “[NMAX]” no paraˆmetro formal para aceder
a` func¸a˜o. Assim, a indicac¸a˜o explı´cita da dimensa˜o pode ser omitida, sendo va´lido escrever
void set(int x[],int n){
VECTORES E APONTADORES 27
int k;
for(k = 0; k < n; k++) x[k] = k+1;
}
Note-se que a possibilidade de omitir a dimensa˜o resulta tambe´m da func¸a˜o na˜o necessitar de
reservar o espac¸o para o vector: a func¸a˜o limita-se a referenciar as ce´lulas de memo´ria reservadas
no programa principal.
Na passagem de vectores como argumento o C utiliza sempre o enderec¸o da primeira posic¸a˜o,
na˜o existindo nenhum mecanismo previsto que permita passar a totalidade dos elementos do vec-
tor por valor. Esta limitac¸a˜o, inerente a` pro´pria origem do C, tinha por base a justificac¸a˜o de
que a passagem por valor de estruturas de dados de dimensa˜o elevada e´ pouco eficiente, dada a
necessidade de copiar todo o seu conteu´do4.
Atendendo a que a, sem inclusa˜o do ı´ndice, especifica um apontador para primeiro elemento
do vector, uma terceira forma de declarar o argumento formal e´
void set(int *x,int n){
int k;
for(k = 0; k < n; k++) x[k] = k+1;
}
Esta u´ltima forma sugere uma forma alternativa de escrever o corpo da func¸a˜o set. Com
efeito, para percorrer um vector basta criar um apontador, inicializa´-lo com o enderec¸o da primeira
posic¸a˜o do vector e seguidamente incrementa´-lo sucessivamente para aceder a`s posic¸o˜es seguintes.
Esta te´cnica pode ser ilustrada escrevendo a func¸a˜o
void set(int *x,int n){
int k;
for(k = 0; k < n; k++) *x++ = k+1;
}
Note-se que sendo x um ponteiro cujo valor resulta de uma passagem co´pia do enderec¸o de a no
programa principal, e´ possı´vel proceder ao seu incremento na func¸a˜o de modo a percorrer todos
os elementos do vector. Aqui, a expressa˜o *x++ merece um comenta´rio adicional. Em primeiro
lugar, o operador ++ tem precedeˆncia sobre o operador * e, deste modo, o incremento opera sobre
o enderec¸o e na˜o sobre a posic¸a˜o de memo´ria em si. ´E este apenas o significado da precedeˆncia,
4Na versa˜o original do C, esta limitac¸a˜o estendia-se ao tratamento de estruturas de dados criadas com a directiva
struct, mas esta limitac¸a˜o foi levantada pelo C-Ansi
28 APONTADORES
o qual na˜o deve ser confundido com a forma de funcionamento do operador incremento enquanto
sufixo: o sufixo estabelece apenas que o conteu´do de x e´ utilizado antes da operac¸a˜o de incremento
se realizar. Ou seja, *x+=k;+ e´ equivalente a *x=k;x+;+.
Um outro exemplo de utilizac¸a˜o desta te´cnica pode ser dado escrevendo uma func¸a˜o para
contabilizar o nu´mero de caracteres usados de uma string. Atendendo a que se sabe que o u´ltimo
caracter esta´ seguido do co´digo ASCII 0, esta func¸a˜o pode escrever-se
int conta(char *s){
int n = 0;
while(*s++ != ’\0’) n++;
return n;
}
De facto, esta func¸a˜o e´ equivalente a´ func¸a˜o strlen, disponı´vel na biblioteca string.h.
2.6 Matrizes
2.6.1 Declarac¸a˜o
Na sua forma mais simples, a utilizac¸a˜o de estruturas multidimensionais em C apenas envolve
a declarac¸a˜o de uma varia´vel com va´rios ı´ndices, especificando cada um da dimensa˜o pretendida
da estrutura. Por exemplo
float x[3][2];
declara uma estrutura bidimensional de treˆs por dois reais, ocupando um total de seis palavras
de memo´ria no modelo de memo´ria que temos vindo a usar como refereˆncia. ´E frequente uma
estrutura bidimensionalser interpretada como uma matriz, neste exemplo de treˆs linhas por duas
colunas.
Nos modos de utilizac¸a˜o mais simples deste tipo de estruturas, o programadador pode abstrair-
se dos detalhes de implementac¸a˜o e usar a varia´vel bidimensional como uma matriz. Assim, a
inicializac¸a˜o a zeros da estrutura x pode ser efectuada por
float x[3][2];
int k,j;
MATRIZES 29
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1002
1003
1004
1005
1006
Endereço Conteúdo Variável
1.0
2.0
3.0
4.0
5.0
6.0
1001 x[0][0]
x[0][1]
x[1][0]
x[1][1]
x[2][0]
x[2][1]
Figura 2.9: Mapa de memo´ria correspondente a` declarac¸a˜o de uma estrutura de treˆs por dois reais
for(k = 0 ; k < 3 ; k++)
for(j = 0 ; j < 2 ; j++)
x[k][j] = 0.0;
Alternativamente, a inicializac¸a˜o pode ser feita listando os valores iniciais, sendo apenas
necessa´rio agrupar hierarquicamente as constantes de inicializac¸a˜o de acordo com as dimenso˜es
da estrutura:
float x[3][2] = {{1.0,2.0},{3.0,4.0},
{5.0,6.0}};
A disposic¸a˜o em memo´ria desta estrutura e´ representada esquematicamente na figura 2.9.
2.6.2 Matrizes como argumento de func¸o˜es
O facto de ser possı´vel omitir a dimensa˜o de um vector na declarac¸a˜o do paraˆmetro formal de
uma func¸a˜o leva por vezes a pensar que o mesmo pode ser feito no caso de uma matriz. De facto,
a situac¸a˜o na˜o e´ totalmente equivalente.
Comece-se por regressar a` declarac¸a˜o int x[3][2] e observar a forma como e´ determi-
nado o enderec¸o de x[k][j]. Analisando a figura 2.9, e´ evidente que o enderec¸o deste elemento e´
obtido adicionando ao enderec¸o do primeiro elemento k*2+j. Ou seja,
30 APONTADORES
x[k][j] <-> *(&(x[0][0]) + k * 2 +j)
No caso mais gene´rico da declarac¸a˜o ter a forma <tipo> x[N][M] ter-se-a´ ainda
x[k][j] <-> *(&(x[0][0]) + k * M +j)
Ou seja, para aceder a um elemento gene´rico de uma matriz na˜o basta conhecer o enderec¸o da
primeira posic¸a˜o e o os dois ı´ndices de acesso: e´ necessa´rio saber tambe´m o nu´mero de colunas da
matriz. Deste modo, quando uma matriz e´ passada como argumento de uma func¸a˜o, e´ necessa´rio
que esta saiba a dimensa˜o das colunas da matriz. Por este motivo, dada a chamada a` func¸a˜o
/* ... */
#define NLIN 3
#define NCOL 2
/* ... */
int m[NLIN][NCOL];
int a;
/* ... */
a = soma(m);
/* ... */
o paraˆmetro formal da func¸a˜o pode repetir na totalidade a declarac¸a˜o da matriz, como em
float norma(float x[NLIN][NCOL]){
int s = 0,k,j;
for(k = 0; k < NLIN ; k++)
for(j = 0; j < NCOL ; j++)
s += x[k][j];
return s;
}
ou pode omitir o nu´mero de linhas (dado que, como se mostrou, este nu´mero na˜o e´ indispensa´vel
para localizar o enderec¸o de um elemento gene´rico da matriz), como em
float norma(float x[][NCOL]){
int s = 0,k,j;
for(k = 0; k < NLIN ; k++)
for(j = 0; j < NCOL ; j++)
s += x[k][j];
return s;
}
MATRIZES 31
No entanto, a omissa˜o simultaˆnea de ambos os ı´ndices como em
float norma(float x[][]){ /* ERRADO */
int s = 0,k,j;
for(k = 0; k < NLIN ; k++)
for(j = 0; j < NCOL ; j++)
s += x[k][j];
return s;
}
na˜o e´ possı´vel, gerando um erro de compilac¸a˜o.
Viu-se anteriormente que se o vector int x[NMAX] for utilizado na chamada a uma func¸a˜o,
a declarac¸a˜o a adoptar nos paraˆmetros formais da func¸a˜o podia ser ou int a[]) ou int *a.
´E frequente surgir a du´vida se e´ possı´vel adoptar uma notac¸a˜o de apontador equivalente no caso
de uma matriz. De facto sim, embora esta notac¸a˜o raramente seja utilizada na pra´tica. No caso do
exemplo que tem vindo a ser utilizado a declarac¸a˜o possı´vel seria
float norma(float (*x)[NCOL]){
int s = 0,k,j;
for(k = 0; k < NLIN ; k++)
for(j = 0; j < NCOL ; j++)
s += x[k][j];
return s;
}
Nesta declarac¸a˜o, x e´ um apontador para um vector de NCOL floats. Uma explicac¸a˜o mais
detalhada do significado desta invulgar declarac¸a˜o pode ser encontrado na secc¸a˜o 2.6.4.
2.6.3 Matrizes e vectores
Em C, uma matriz na˜o e´ mais do que um vector de vectores. Ou seja, a declarac¸a˜o
int x[3][2] pode ser interpretada como “ x e´ um vector de 3 posic¸o˜es, em que cada posic¸a˜o e´
um vector de 2 posic¸o˜es”. Esta interpretac¸a˜o e´ tambe´m sugerida pela representac¸a˜o que se adoptou
na figura 2.9. Por outras palavras, x[0], x[1] e x[2] representam vectores de dois elementos
constituı´dos respectivamente por {1.0 , 2.0}, {2.0 , 3.0}, e {4.0 , 5.0}.
Na pra´tica, este facto significa que cada uma das linhas de uma matriz pode ser tratada indi-
vidualmente como um vector.
32 APONTADORES
Suponha-se, por exemplo, que dada uma matriz a de dimensa˜o NLIN × NCOL, se pretende
escrever uma func¸a˜o que determine o ma´ximo de cada uma das linhas da matriz e coloque o
resultado num vector y. Esta func¸a˜o pode ser escrita como
void maxMat(float y[],float a[][NCOL]){
int k;
for(k = 0; k < NLIN ; k++)
y[k] = maxVec(a[k],NCOL);
}
onde cada linha foi tratada individualmente como um vector, cujo ma´ximo e´ determinado pela
func¸a˜o vectorial
float maxVec(float v[],int n){
float m;
int k;
m = v[0];
for(k = 1; k < n; k++)
m = (v[k] > m ? v[k] : m);
return m;
}
De igual forma, considere-se que se pretende calcular o produto matricial
y = Ax
onde A e´ uma matriz de dimensa˜o N ×M e x e y sa˜o vectores de dimensa˜o M e N , respectiva-
mente.
Admita-se que o programa principal era escrito como
#define N 3
#define M 2
int main(){
float A[N][M] = {{1,2},{3,4},{5,6}};
float x[M] = {10,20};
float y[N];
int k;
matVecProd(y,A,x);
for(k = 0; k < N ; k++)
printf("%f\n",y[k]);
exit(0);
}
MATRIZES 33
Atendendo a que o produto de uma matriz por um vector e´ um vector em que cada elemento
na˜o e´ mais do que o produto interno de cada linha da matriz com o vector operando, a func¸a˜o
prodMatVec poderia ser escrita como
void matVecProd(float y[],float A[][M],float x[]){
int k;
for(k = 0; k < N ; k++)
y[k] = prodInt(A[k],x,M);
}
com o produto interno definido por
float prodInt(float a[],float b[],int n){
float s = 0.0;
int k;
for(k = 0; k < n; k++)
s += a[k] * b[k];
return s;
}
Uma u´ltima situac¸a˜o em que e´ possı´vel exemplificar a utilizac¸a˜o de linhas como vectores e´
o do armazenamento de va´rias linhas de texto. Admita-se, por exemplo, que se pretende ler uma
sequeˆncia de linhas de texto e imprimir as mesmas linhas por ordem inversas. Tal e´ possı´vel atrave´s
da utilizac¸a˜o de uma matriz de caracteres, utilizando cada linha como uma string convencional:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NUM_LINHAS 5
#define DIM_LINHA 40
int main(){
char texto[NUM_LINHAS][DIM_LINHA];
int k;
/* Leitura */
printf("Escreva uma sequeˆncia de %d linhas:\n",NUM_LINHAS);
for(k = 0;k < NUM_LINHAS; k++){
fgets(texto[k],DIM_LINHA,stdin);
if(texto[k][strlen(texto[k])-1] != ’\n’){
printf("Erro: linha demasiado comprida.\n");
exit(1);
34 APONTADORES
}
}
/* Escrita */
printf("\nLinhas por ordem inversa:\n");
for(k = NUM_LINHAS-1;k >= 0; k--)
printf("%s",texto[k]);
exit(0);
}
2.6.4 Matrizes e apontadores
Se a utilizac¸a˜o de estruturas multidimensionais e´ relativamente simples e intuitiva, ja´ o mesmo
nem sempre sucede quando e´ necessa´rio manipular apontadores relacionados com este tipo. 5 O
tratamento de matrizes no C e´ frequentemente fonte de alvo de du´vidas. Se se perguntar a qualquer
programador de C “dada a declarac¸a˜o int x[3], qual e´ o tipo de x quando usado isoladamente”,
nenhum tera´ du´vidas em afirmar que a resposta e´ “apontador para int”. Experimente-se, no
entanto, a colocar a pergunta semelhante “dada a declarac¸a˜o int x[3][2], qual e´ o tipo de x
quando usado isoladamente” e, com elevada probabilidade, na˜o sera´ obtida a mesma unanimidade
nas respostas.
Dado que, por consisteˆncia sinta´ctica, se temsempre
x[k] <-> *(x + k)
enta˜o, no caso de uma estrutura bidimensional, tera´ que ser
x[k][j] <-> *(x[k] + j)
e, portanto, se x[k][j] e´ por exemplo do tipo float, x[k] e´ um apontador para float. Con-
sultando novamente o exemplo da figura 2.9, significa isto que x[0] corresponde a um apontador
para float com o valor 1001 (e portanto o enderec¸o do primeiro elemento do vector de floats
formado pelos reais 1.0 e 2.0, enquanto que x[2] corresponde tambe´m a um apontador, cujo
valor e´ 1003 (primeiro elemento do vector de floats formado pelos reais 3.0 e 4.0).
Considere-se agora, novamente, a questa˜o de qual o tipo de x, quando considerado isolada-
mente. Como ja´ se disse anteriormente em C, uma estrutura multidimensional representa sempre
5A leitura desta secc¸a˜o pode ser omitida numa abordagem introduto´ria da linguagem C.
MATRIZES 35
uma hierarquia de vectores simples. Ou seja, x[3][2] representa um vector de treˆs elementos,
em que cada um e´ por sua vez um vector de dois elementos. Assim, x[k] representa sempre um
vector de dois floats. Com esta formulac¸a˜o, resulta claro que x representa um apontador para
um vector de dois floats. Isto na˜o e´ mais do que a generalizac¸a˜o da situac¸a˜o dos vectores, em que
dada a declarac¸a˜o int a[N], se sabe que a isoladamente e´ um apontador para inteiro.
Assim, e´ natural que x sem ı´ndices especifique o enderec¸o do primeiro elemento de um vector
de treˆs elementos, em que cada um e´ um vector de dois floats. Ou seja, no nosso exemplo, x
corresponde ao valor 1001.
Mas, afinal, qual a diferenc¸a entre um apontador para float e um apontador para um vec-
tor de floats? Por um lado, a forma de usar este tipo varia´vel num enderec¸amento indirecto
e´ claramente diferente. Por exemplo, x e &x[0][0] correspondem ao mesmo valor (1001 no
nosso exemplo), mas sa˜o tipos diferentes: o primeiro e´ um apontador para um vector, pelo que *x
e´ um vector (a primeira linha de x, enquanto que *(&x[0][0]) e´ um float (o conteu´do de
x[0][0]. No entanto, e´ provavelmente mais importante reter que a diferenc¸a fundamental reside
na dimensa˜o do objecto apontado. Considere-se novamente o exemplo que temos vindo a con-
siderar. Uma varia´vel do tipo apontador para float, cujo valor seja 1001, quando incrementada
passa a ter o valor 1002. Mas uma varia´vel do tipo apontador para vector de dois float, cujo
valor seja 1001, quando incrementada passa a ter o valor 1003 (ja´ que o incremento e´ escalado
pela dimensa˜o do objecto apontado).
´E interessante verificar que este modelo e´ o u´nico que permite manter a consisteˆncia sinta´ctica
do C na equivaleˆncia entre vectores e apontadores. Ja´ se disse que sendo sempre
x[k] <-> *(x + k)
enta˜o,
x[k][j] <-> *(x[k] + j)
O que na˜o se disse antes, mas que tambe´m se verifica, e´ que a primeira destas regras tambe´m deve
ser aplica´vel a` entidade x[k] que surge na u´ltima expressa˜o. Ou seja, tem-se
x[k][j] <-> *(*(x+k) + j)
o que exige por outro lado estabelecer que
36 APONTADORES
Expressa˜o Tipo Valor Elemento da
estrutura
x Apontador para vector 1001 -
de dois floats
x+1 Apontador para vector 1003 -
de dois floats
*(x+1) Apontador para float 1003 -
*(x+2)+1 Apontador para float 1006 -
*(*(x+2)+1) float 6.0 x[2][1]
*x Apontador para float 1001 -
**x float 1.0 x[0][0]
*(*x+1) float 2.0 x[0][1]
x[1] Apontador para float 1003 -
Tabela 2.1: Exemplos de tipos e valores derivados do exemplo da figura 2.9.
• Se a e´ um apontador para um escalar, *a e´ desse tipo escalar, e o valor de *a e´ o conteu´do
do enderec¸o especificado por a.
• Se a e´ um apontador para um vector de elementos de um dado tipo, *a e´ um apontador para
um elemento do tipo constituinte, e o seu valor e´ ideˆntico ao de a.
Um factor que contribui frequentemente para alguma confusa˜o deriva do facto de que, ainda
que x na˜o seja sintacticamente um duplo apontador para float, sendo
x[0][0] <-> *(*(x+0) + 0) <-> **x
verifica-se que **x e´ um float.
A consisteˆncia destas equivaleˆncias podem ser verificada considerando casos particulares do
exemplo que tem vindo a ser utilizado como refereˆncia, tal como listados na tabela 2.1. O co´digo
de um pequeno programa que permite validar esta tabela esta´ listado no apeˆndice A.
Como e´ natural, e´ possı´vel declarar um apontador para um vector de dois floats, sem ser
da forma implı´cita que resulta da declarac¸a˜o da matriz. A declarac¸a˜o de uma varia´vel y deste tipo
pode ser feita por
float (*y)[2];
Por este motivo, que quando a matriz x e´ passada por argumento para uma func¸a˜o func, a
MATRIZES 37
declarac¸a˜o do paraˆmetro formal poder ser feita repetindo a declarac¸a˜o total, omitindo a dimensa˜o
do ı´ndice interior, ou enta˜o por
void func(float (*y)[2]);
tal como se referiu na secc¸a˜o 2.6.2.
Dado que este tipo de declarac¸o˜es e´ alvo frequente de confusa˜o, e´ conveniente saber que existe
uma regra de leitura que ajuda a clarificar a semaˆntica da declarac¸a˜o. Com efeito, e´ suficiente
“seguir” as regras de precedeˆncia, procedendo a` leitura na seguinte sequeˆncia:
float (*y)[3]
um apontador
floats
y é
para um vector de três 
Sublinhe-se que, face a tudo o que ficou dito anteriormente, na˜o e´ possı´vel declarar um apon-
tador para um vector sem especificar a dimensa˜o do vector: como ja´ foi dito por diversas vezes,
um apontador tem que conhecer a dimensa˜o do objecto apontado. Isto na˜o e´ possı´vel sem especi-
ficar a dimensa˜o do vector. Como corola´rio, um apontador para um vector de treˆs inteiros e´ de
tipo distinto de um apontador para um vector para seis inteiros, na˜o podendo os seus conteu´dos
ser mutuamente atribuı´dos.
Note-se que a declarac¸a˜o que se acabou de referir e´ claramente distinta de
float *y[2];
onde, devido a` auseˆncia de parenteses, e´ necessa´rio ter em atenc¸a˜o a precedeˆncia de “[]” sobre o
“*”. Neste caso, y e´ um vector de dois apontadores para float, podendo a leitura da declarac¸a˜o
ser realizada pela sequeˆncia:
y é
float *y[3]
um vector de três 
apontadores
para float
A utilizac¸a˜o de vectores de apontadores e´ abordada em maior detalhe na secc¸a˜o 3.5.4.
Finalmente, refira-se que os apontadores para vectores podem ainda surgir noutros contextos:
dada a declarac¸a˜o int x[10], x e´ um apontador para inteiro, mas a expressa˜o &x e´ do tipo
38 APONTADORES
apontador para um vector de 10 inteiros.
2.7 Generalizac¸a˜o para mais do que duas dimenso˜es
A generalizac¸a˜o do que ficou dito para mais do que duas dimenso˜es e´ directa. Considere-se,
como refereˆncia, a declarac¸a˜o da estrutura
int x[M][N][L];
1. No ca´lculo do enderec¸o de qualquer elemento da estrutura tem-se a igualdade:
&(x[m][n][l]) == (&x[0][0][0]) + m * (N*L) + n * L + l
2. x[k][j] e´ um apontador para inteiro.
3. x[k] e´ um apontador para um vector de L inteiros.
4. x e´ um apontador para uma matriz de N×L inteiros.
5. Em geral,
x[m][n][l] == *(*(*(x+m)+n)+l)
A passagem de uma estrutura multidimensional como argumento pode ser feita pela repetic¸a˜o
da declarac¸a˜o completa do tipo. Assim, uma declarac¸a˜o possı´vel e´
#define M ...
#define N ...
#define L ...
void func(int x[M][N][L]){
/* ... */
}
int main(){
int x[M][N][L];
/*...*/
func(x);
/*...*/
return 0;
}
GENERALIZAC¸A˜O PARA MAIS DO QUE DUAS DIMENSO˜ES 39
Como se mostrou anteriormente, o ca´lculo do enderec¸o de um elemento gene´rico de uma es-
trutura tridimensional exige o conhecimento das duas dimenso˜es exteriores da estrutura (N e L no
exemplo). A generalizac¸a˜o desta regra mostra que para calcular o enderec¸o de um elemento de
uma estrutura n-dimensional e´ necessa´rio conhecer com rigor os n − 1 ı´ndices exteriores da es-
trutura. Deste modo, nos argumentos formais de uma func¸a˜o e´ sempre possı´vel omitir a dimensa˜o
do primeiro ı´ndicede uma estrutura multidimensional, mas na˜o mais do que esse. No exemplo
anterior, pode enta˜o escrever-se
void func(int x[][N][L]){
/* ... */
}
Claro que todas as outras variantes em que exista consisteˆncia sinta´ctica entre os argumentos
formais e actuais do procedimento sa˜o va´lidas. Assim, pelas mesmas razo˜es ja´ detalhadas na
secc¸a˜o 2.6.4,
void func(int (*x)[N][L]){
/* ... */
}
e´ uma alternativa sintacticamente correcta neste caso.
Capı´tulo 3
Vectores e memo´ria dinaˆmica
3.1 Introduc¸a˜o
Ate´ ao aparecimento da linguagem C, a maioria das linguagens de alto nı´vel exigia um dimen-
sionamento rı´gido das varia´veis envolvidas. Por outras palavras, a quantidade ma´xima de memo´ria
necessa´ria durante a execuc¸a˜o do programa deveria ser definida na altura de compilac¸a˜o do pro-
grama. Sempre que os requisitos de memo´ria ultrapassavam o limite fixado durante a execuc¸a˜o
do programa, este deveria gerar um erro. A soluc¸a˜o nestes casos era recompilar o programa au-
mentando a dimensa˜o das varia´veis, soluc¸a˜o so´ possı´vel se o utilizador tivesse acesso e dominasse
os detalhes do co´digo fonte. Por exemplo, um programa destinado a` simulac¸a˜o de um circuito
electro´nico poderia ser obrigado a definir no co´digo fonte o nu´mero ma´ximo de componentes
do circuito. Caso este nu´mero fosse ultrapassado, o programa deveria gerar um erro, porque a
memo´ria reservada durante a compilac¸a˜o tinha sido ultrapassada. A alternativa nestas situac¸o˜es
era a reserva a` partida de uma dimensa˜o de memo´ria suficiente para acomodar circuitos de di-
mensa˜o elevada, mas tal traduzia-se obviamente num consumo excessivo de memo´ria sempre que
o programa fosse utilizado para simular circuitos de dimensa˜o reduzida. Em alguns casos, os pro-
gramas recorriam a` utilizac¸a˜o de ficheiros para guardar informac¸a˜o tempora´ria, mas esta soluc¸a˜o
implicava geralmente uma complexidade algorı´tmica acrescida.
Tal como o nome indica, os sistemas de memo´ria dinaˆmica permitem gerir de forma dinaˆmica
os requisitos de memo´ria de um dado programa durante a sua execuc¸a˜o. Por exemplo, no caso
do sistema de simulac¸a˜o referido anteriormente, o programa pode, no inı´cio da execuc¸a˜o, deter-
minar a dimensa˜o do circuito a simular e so´ nessa altura reservar a memo´ria necessa´ria. Com
esta metodologia, o programa pode minimizar a quantidade de memo´ria reservada e, deste modo,
permitir que o sistema operativo optimize a distribuic¸a˜o de memo´ria pelos va´rios programas que
42 VECTORES E MEMO´RIA DINAˆMICA
se encontram simultaneamente em execuc¸a˜o. No entanto, ate´ ao aparecimento do C este tipo
de mecanismos, caso estivessem disponı´veis no sistema operativo, eram apenas acessı´veis em
linguagem ma´quina ou Assembler, mais uma vez pela necessidade de manipular directamente
enderec¸os de memo´ria.
Apesar da flexibilidade oferecida, a gesta˜o directa da memo´ria dinaˆmica exige algumas
precauc¸o˜es suplementares durante o desenvolvimento do co´digo. Por este motivo, muitas lingua-
gens de programac¸a˜o de alto-nı´vel mais recentes (Lisp, Java, Scheme, Python, Perl, Mathematica,
entre outras) efectuam a gesta˜o automa´tica da memo´ria dinaˆmica, permitindo assim que o pro-
gramador se concentre na implementac¸a˜o algorı´tmica e nos modelos de dados associados, sem se
preocupar explicitamente com os problemas de dimensionamento de varia´veis ou os volumes de
memo´ria necessa´rios para armazenamento de dados.
Neste contexto, podera´ perguntar-se quais as vantagens de programar em C ou porque e´ que
o C ainda mante´m a popularidade em tantas a´reas de aplicac¸a˜o. Ha´ diversas respostas para esta
questa˜o:
• A gesta˜o directa da memo´ria dinaˆmica permite normalmente a construc¸a˜o de programas
com maior eficieˆncia e com menores recursos computacionais.
• Dada a evidente complexidade dos compiladores de linguagens mais elaboradas, e´ frequente
microprocessadores e controladores especializados (processadores digitais de sinal, micro-
controladores, etc) apenas disporem de compiladores para a linguagem C, a qual se encontra
mais pro´ximo da linguagem ma´quina do que linguagens conceptualmente mais elaboradas.
Por este motivo, o C ainda se reveste hoje de uma importaˆncia crucial em diversas a´reas da
Engenharia Electrote´cnica, nomeadamente em aplicac¸o˜es que implicam o recurso a micro-
controladores especializados.
• Dada a sua proximidade com o hardware1, a maioria dos sistemas operativos actualmente
existentes sa˜o ainda hoje programados na linguagem C.
• A maioria dos compiladores de linguagens de alto nı´vel, incluindo o pro´prio C, sa˜o actual-
mente escritos e desenvolvidos em C. Ou seja, nesta perspectiva, o C e´ hoje uma linguagem
indispensa´vel a` gerac¸a˜o da maioria das outras linguagens, constituindo, neste sentido, a
“linguagem das linguagens”.
1O C e´ frequentemente designado na gı´ria como um Assembler de alto nı´vel, apesar do evidente paradoxo contido
nesta designac¸a˜o.
VECTORES 43
3.2 Vectores
Em C, um vector e´ uma colecc¸a˜o com um nu´mero bem definido de elementos do mesmo tipo.
Ao encontrar a declarac¸a˜o de um vector num programa, o compilador reserva automaticamente
espac¸o em memo´ria para todos os seus elementos. Por razo˜es de clareza e de boa pra´tica de
programac¸a˜o, estas constantes sa˜o normalmente declaradas de forma simbo´lica atrave´s de uma
directiva define, mas a dimensa˜o continua obviamente a ser uma constante:
#define DIM_MAX 100
#define DIM_MAX_STRING 200
int x[DIM_MAX];
char s[DIM_MAX_STRING];
Por outras palavras, a necessidade de saber quanta memo´ria e´ necessa´ria para o vector que se
pretende utilizar implica que a dimensa˜o deste vector seja uma constante, cujo valor ja´ conhecido
durante a compilac¸a˜o do programa.
Como e´ sabido, as varia´veis locais a uma func¸a˜o teˆm um tempo de vida limitado a` execuc¸a˜o
da func¸a˜o2. O espac¸o para estas varia´veis e´ reservado na chamada zona da pilha (stack), uma
regia˜o de memo´ria dedicada pelo programa para armazenar varia´veis locais e que normalmente e´
ocupada por ordem inversa. Por outras palavras, sa˜o usados primeiros os enderec¸os mais elevados
e va˜o sendo sucessivamente ocupados os enderec¸os inferiores a` medida que sa˜o reservadas mais
varia´veis locais. Dada a forma como se realiza esta ocupac¸a˜o de memo´ria, o limite inferior da
regia˜o da pilha e´ geralmente designada por topo da pilha. Neste sentido, quando uma func¸a˜o
e´ chamada, o espac¸o para as varia´veis locais e´ reservado no topo da pilha, que assim decresce.
Quando uma func¸a˜o termina, todas as varia´veis locais sa˜o libertadas e o topo da pilha aumenta
novamente. Assim, por exemplo, dada a declarac¸a˜o
int function(int z){
int a;
int x[5];
int b;
/* ... */
a chamada da func¸a˜o func poderia dar origem a uma evoluc¸a˜o do preenchimento da memo´ria da
forma como se representa na figura 3.1.
2Excepto se a sua declarac¸a˜o for precedido do atributo static, mas este so´ e´ usado em circunstaˆncias excepcionais.
44 VECTORES E MEMO´RIA DINAˆMICA
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço Conteúdo Variável
topo da pilha
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
x[0]
x[1]
x[2]
x[3]
1001
1002
1003
1004
1005
1006
1007
1008
1009
Endereço Conteúdo Variável
...
b
a
z
topo da pilha
B
Figura 3.1: Mapa de memo´ria antes e apo´s a chamada a` func¸a˜o func (A) e mapa de memo´ria
durante a execuc¸a˜o da func¸a˜o (B).
A necessidade da dimensa˜o dos vectores ser conhecida na altura da compilac¸a˜o conduz a`
impossibilidade de declarac¸o˜es do tipo
int function(int n){
int x[n]; /* MAL: n e´ uma varia´vel, e o seu valor e´ desconhecido
na altura da compilac¸a˜o

Outros materiais