- Aprender
- Computação e TI
- Programação
- Programação em C
- Ponteiros em C
Ponteiros e endereços de memória em C
&, leia e altere valores por meio de ponteiros e passe variáveis para funções por referência. Percorra arranjos e cadeias de caracteres com aritmética de ponteiros, use ponteiros para ponteiros em argumentos de linha de comando e em funções que movem um ponteiro da função chamadora e chame funções por meio de ponteiros para funções, incluindo funções de comparação para o qsort padrão.BásicoAula 4 de 8 do curso Curso de Programação em C
O que você vai aprender
- Visualize como as variáveis de um programa em C ocupam bytes na memória, armazene endereços em variáveis ponteiro e use esses ponteiros para ler e alterar as variáveis para as quais apontam.
- Visualize como as variáveis são dispostas na memóriaDescreva a memória como bytes numerados, encontre o tamanho e o endereço de variáveis e elementos de arranjos e explique por quanto tempo cada tipo de variável existe.
- Declare ponteiros e obtenha endereços com &Declare variáveis ponteiro do tipo correto, armazene o endereço de uma variável com o operador de endereço e imprima, redirecione ou redefina um ponteiro com segurança.
- Leia e altere valores por meio de ponteirosUse o operador de desreferenciação para ler e escrever no objeto apontado por um ponteiro, rastreie código com diagramas de memória e evite desreferenciar ponteiros inválidos.
- Visualize como as variáveis são dispostas na memória
- Escreva funções que alterem variáveis da função chamadora por meio de parâmetros ponteiro, proteja dados somente de leitura com
conste retorne ponteiros que permaneçam válidos.- Passe variáveis por referência usando parâmetros ponteiroExplique por que as funções em C recebem cópias de seus argumentos e use parâmetros ponteiro para alterar variáveis da função chamadora, trocar valores, retornar vários resultados e ler entradas com
scanf. - Proteja dados com const e retorne ponteiros com segurançaMarque dados somente de leitura com
const, diferencie um ponteiro para dados constantes de um ponteiro constante e retorne ponteiros que permaneçam válidos após o término da função.
- Passe variáveis por referência usando parâmetros ponteiro
- Mova ponteiros por arranjos e cadeias de caracteres, explique como nomes de arranjos se relacionam com ponteiros e mantenha cada ponteiro dentro do arranjo ao qual pertence.
- Faça operações aritméticas com ponteirosSome inteiros a ponteiros, subtraia e compare ponteiros para elementos do mesmo arranjo e saiba quais resultados são válidos.
- Explique a relação entre arranjos e ponteirosUse a conversão de um nome de arranjo em ponteiro, a equivalência entre
a[i]e*(a + i)e as diferenças reais entre um arranjo e um ponteiro. - Percorra arranjos e cadeias de caracteres com ponteirosEscreva laços que percorram arranjos e cadeias terminadas em nulo com ponteiros, use corretamente expressões idiomáticas como
*p++e respeite os limites.
- Faça operações aritméticas com ponteiros
- Acompanhe vários níveis de indireção, permita que funções atualizem um ponteiro da função chamadora e use arranjos de ponteiros, como os argumentos de linha de comando.
- Use ponteiros para ponteirosDeclare e desreferencie um ponteiro para ponteiro, acompanhe-o em um diagrama de memória e use-o para permitir que uma função altere um ponteiro da função chamadora.
- Use arranjos de ponteiros e argumentos de linha de comandoCrie arranjos de ponteiros para cadeias de caracteres, processe
argceargvcomo ponteiros e diferencie um arranjo de ponteiros de uma matriz bidimensional.
- Use ponteiros para ponteiros
- Armazene e chame funções por meio de ponteiros, passe funções para outras funções e ordene dados de qualquer tipo com o
qsortpadrão e uma função de comparação.- Declare ponteiros para funções, atribua valores a eles e chame funções por meio delesEscreva e leia declarações de ponteiros para funções, chame funções por meio deles e crie tabelas de funções que escolhem o comportamento durante a execução do programa.
- Escreva funções de retorno de chamada e ordene com qsortPasse funções como argumentos, use ponteiros
voidpara manipular dados de qualquer tipo e escreva funções de comparação corretas paraqsort.
- Declare ponteiros para funções, atribua valores a eles e chame funções por meio deles
Guia de estudo de Ponteiros em C
1Memória, endereços e valores apontados
Uma variável tem um valor, ocupa espaço de armazenamento e tem um tempo de vida. Um ponteiro permite acessar essa variável por meio de seu endereço, então ler código agora significa acompanhar tanto o endereço contido em um ponteiro quanto o valor encontrado nele. Comece pela disposição dos bytes e depois acompanhe os efeitos de obter um endereço, redirecionar um ponteiro e escrever por meio dele. Pequenos diagramas de memória tornam essas operações visíveis antes que elas apareçam em funções e laços.
1.1 Bytes, tamanhos e tempos de vida
A memória pode ser visualizada como uma sequência de bytes numerados. Cada número é um endereço. Um objeto como um int ocupa bytes consecutivos, e seu endereço identifica seu primeiro byte. O nome usado no código-fonte permite que o compilador encontre esse objeto; a linguagem não exige que uma etiqueta com o nome seja armazenada ao lado de seu valor.
Meça o armazenamento com sizeof
sizeof informa o tamanho em bytes e produz um valor size_t. Use %zu para imprimi-lo:
int scores[4] = {12, 18, 15, 21};
printf("%zu %zu\n", sizeof scores, sizeof scores[0]);
Com valores int de 4 bytes, isso imprime 16 4. O primeiro resultado mede os quatro elementos; o segundo mede um. sizeof(char) é sempre 1. Uma disposição comum em computadores de mesa de 64 bits usa valores int de 4 bytes, valores double de 8 bytes e ponteiros para objetos de 8 bytes. Outros sistemas podem ser diferentes. Mesmo entre sistemas de 64 bits, long costuma ocupar 8 bytes no Linux e no macOS, mas 4 no Windows.
Tamanho de armazenamento e comprimento de texto respondem a perguntas diferentes. Para char word[20] = "hi";, sizeof word é 20, enquanto strlen(word) é 2. O arranjo ainda contém 20 elementos, embora sua cadeia use apenas duas letras e um terminador. Exceto para arranjos de tamanho variável, esses tamanhos de objetos são fixados em tempo de compilação.
Acompanhe os endereços de um arranjo
Endereços hexadecimais usam os dígitos de 0 a 9 e depois de a a f. O prefixo 0x indica a base 16. Para um arranjo ilustrativo que começa em 0x1000, quatro elementos de 4 bytes começam em:
| Elemento | Primeiro byte | Último byte |
|---|---|---|
scores[0] | 0x1000 | 0x1003 |
scores[1] | 0x1004 | 0x1007 |
scores[2] | 0x1008 | 0x100B |
scores[3] | 0x100C | 0x100F |
0x100C é 4.108 em decimal, doze bytes depois de 0x1000. Os elementos de um arranjo não têm espaços entre si. Variáveis declaradas separadamente não têm ordem nem distância garantidas; endereços reais impressos também podem mudar entre execuções. Um compilador pode manter um valor em um registrador do processador quando não é necessário um local de armazenamento endereçável.
Alinhamento e ordem dos bytes
O alinhamento restringe onde os objetos de um tipo podem começar. Uma disposição comum coloca valores int de 4 bytes em múltiplos de 4 e valores double de 8 bytes em múltiplos de 8. O compilador fornece alinhamento adequado para objetos declarados.
A ordem dos bytes é a ordem dos bytes dentro de um valor com vários bytes. Para um unsigned int de 4 bytes contendo 0x12345678, o armazenamento com o byte menos significativo primeiro apresenta os bytes 78 56 34 12 dos endereços menores aos maiores; com o byte mais significativo primeiro, apresenta 12 34 56 78. Um unsigned char * pode examinar a representação de um objeto byte por byte. A ordem dos bytes não altera a ordem dos índices do arranjo.
Mantenha o objeto existente
Uma variável local comum tem duração de armazenamento automática: seu tempo de vida dura enquanto seu bloco está em execução. Implementações típicas mantêm esses objetos no registro de ativação de uma chamada e reutilizam esse armazenamento posteriormente. Variáveis globais, variáveis static e literais de cadeia de caracteres duram durante toda a execução do programa. Salvar um endereço não prolonga o tempo de vida de seu objeto.
Principais conclusões
sizeofmede bytes;strlenconta os caracteres antes do terminador de uma cadeia.- Os elementos de um arranjo ocupam armazenamento consecutivo na ordem dos índices.
- Variáveis separadas não têm endereços relativos garantidos.
- O alinhamento controla os endereços iniciais adequados; a ordem dos bytes controla a posição dos bytes dentro de um valor.
- Um endereço salvo só permanece utilizável enquanto seu objeto existe.
1.2 Armazene e exiba um endereço
Um ponteiro armazena um endereço e tem um tipo que descreve o objeto que ele pode acessar. Declarar um int * fornece armazenamento para um ponteiro; isso não cria um int para esse ponteiro usar. Dê ao ponteiro um valor conhecido ao declará-lo.
Obtenha o endereço de um objeto
int count = 42;
int *p = &count;
O operador de endereço unário & obtém o endereço de count. Se count começa no endereço ilustrativo 0x1000, p contém 0x1000, enquanto count contém 42. O ponteiro também é um objeto: &p é o endereço do armazenamento separado que contém esse ponteiro.
Você pode obter o endereço de um elemento de arranjo, como &scores[2]. Uma constante numérica ou o resultado de um cálculo não é um objeto armazenado cujo endereço você possa obter dessa maneira: &42 e &(count + 1) são inválidos. A expressão binária a & b usa o mesmo símbolo para AND bit a bit; o número de operandos indica qual operação está presente.
Leia cada declarador
| Declaração | Significado |
|---|---|
int *p, q; | Um ponteiro e um int comum |
int *p, *q; | Dois ponteiros para int |
double *r; | Um ponteiro para double |
Os espaços em branco não alteram nenhum desses tipos. int* p e int *p são equivalentes. Declarar um ponteiro por linha torna seu asterisco mais fácil de perceber. Em computadores de mesa comuns de 64 bits, um ponteiro para objeto ocupa 8 bytes, mas seu próprio tamanho não informa o tamanho do objeto para o qual aponta.
O tipo apontado importa. Para double price = 9.5;, use double *r = &price;. Inicializar um int * com &price produz um diagnóstico de ponteiros incompatíveis. Uma conversão explícita que oculta o diagnóstico não torna válido um acesso como int a esse double: o tipo controla como os bytes armazenados são acessados.
Redirecione um ponteiro
int a = 5, b = 8;
int *p = &a;
p = &b;
A última atribuição substitui o endereço em p. Ela não move nenhum dos inteiros nem copia o valor de um inteiro para o outro. Agora p aponta para b; a continua sendo 5 e b continua sendo 8. Várias variáveis ponteiro também podem armazenar o mesmo endereço.
Use um estado vazio conhecido
Um ponteiro local não inicializado tem um valor indeterminado e às vezes é chamado de ponteiro não inicializado. Não o examine nem o desreferencie. Inicialize-o com o endereço de um objeto válido ou com NULL, disponível em cabeçalhos como <stddef.h> e <stdio.h>:
int *pending = NULL;
if (pending == NULL) {
pending = &count;
}
Um ponteiro nulo não designa nenhum objeto. !pending é outro teste de nulidade. Esses testes só fazem sentido para ponteiros inicializados; eles não validam um endereço arbitrário nem um ponteiro cujo objeto deixou de existir.
Imprima um ponteiro para objeto
printf("%p\n", (void *)p);
%p espera um void *, então forneça a conversão explícita ao usar um ponteiro para objeto de tipo específico. Sua representação impressa é escolhida pela implementação e costuma ser hexadecimal. %d e %x esperam argumentos inteiros de tipos específicos e não podem substituir %p.
Principais conclusões
- Inicialize um ponteiro com um endereço válido ou
NULL. &counté um endereço;counté o inteiro armazenado.- Cada nome de ponteiro precisa de seu próprio
*em uma declaração. - Os tipos de ponteiro devem ser adequados aos objetos acessados por meio deles.
p = &baltera o ponteiro e deixa os inteiros inalterados.- Imprima um ponteiro para objeto com
%pe um argumentovoid *.
1.3 Siga um ponteiro para ler ou escrever
O operador de desreferenciação unário * segue um ponteiro até seu objeto. Se p aponta para x, *p designa o próprio x. Ler *p lê esse objeto; atribuir um valor a *p o altera.
Rastreie uma escrita por meio de um endereço
int x = 10;
int *p = &x;
*p = *p + 5;
int y = *p * 2;
printf("%d %d %d\n", x, *p, y);
Primeiro, p armazena o endereço de x. No lado direito da atribuição, *p lê 10. Somar 5 resulta em 15, e o *p do lado esquerdo armazena 15 em x. A linha seguinte multiplica o valor atualizado por 2, então y passa a ser 30. A saída é 15 15 30.
O contexto distingue os asteriscos: int *p declara um ponteiro, o *p unário acessa um objeto e *p * 2 também contém uma multiplicação binária. Para um objeto válido x, *&x designa x; para um ponteiro para um objeto válido, &*p devolve o valor do ponteiro.
Separe endereços de conteúdos
Comece cada rastreamento com duas linhas de informação: o endereço contido em cada ponteiro e o valor contido em cada inteiro. Suponha que p aponte para a, que contém 3, e q aponte para b, que contém 8.
| Instrução considerada a partir desse estado inicial | Efeito |
|---|---|
*p = *q; | Copia 8 para a; os ponteiros mantêm seus destinos |
p = q; | Faz p apontar para b; os inteiros mantêm seus valores |
Um diagrama de memória usa uma caixa para cada objeto. Identifique uma caixa com seu nome e endereço, coloque seu valor atual dentro dela e desenhe uma seta de um ponteiro até seu destino. Uma atribuição de endereço move uma seta; uma atribuição ao objeto apontado altera o conteúdo da caixa de destino.
Após p = q, os ponteiros são referências alternativas a b. Se você executar *p = 20; em seguida, ler *q ou b resultará em 20. Copiar um endereço cria outro caminho para o mesmo objeto, não outro inteiro.
Verifique se uma desreferenciação é válida
Um ponteiro usado para acesso deve designar um objeto existente de tipo adequado, e a escrita também exige um objeto modificável. Ponteiros nulos não designam nenhum objeto. Ponteiros não inicializados não têm um destino conhecido utilizável. Um ponteiro fica pendente quando o tempo de vida de seu objeto termina.
Acessar por meio de um ponteiro inválido é comportamento indefinido: C não impõe requisitos ao resultado. O programa pode parar com uma falha de segmentação, corromper dados ou parecer executar normalmente. Uma execução aparentemente bem-sucedida não pode estabelecer que o acesso era válido.
Para um ponteiro que pode legitimamente ser nulo, teste-o antes de acessá-lo:
if (p != NULL) {
*p = 0;
}
Atribuir NULL a um ponteiro de que você não precisa mais pode evitar o uso acidental posterior dessa variável. Isso não redefine outras referências alternativas. Acompanhe separadamente o tempo de vida do objeto.
Use parênteses em torno do valor desejado
O ++ pós-fixado tem precedência maior que o * unário. Assim, *p++ significa *(p++): usa o valor anterior do ponteiro para acessar um valor e avança o ponteiro. (*p)++ incrementa o valor apontado e deixa o ponteiro no lugar. *p + 1 lê o valor e soma 1 sem armazenar o resultado. Essas são três ações diferentes.
Principais conclusões
*pdesigna o objeto acessado por meio dep.- Copiar um endereço cria referências alternativas; copiar
*pcopia um valor. - Uma desreferenciação exige um objeto existente e um tipo de acesso adequado.
- O comportamento indefinido não garante nenhum resultado, nem mesmo um encerramento inesperado.
*p++avança o ponteiro;(*p)++incrementa o objeto apontado.
2Permita que funções trabalhem com dados da função chamadora
Uma função recebe uma cópia de cada argumento, incluindo um argumento ponteiro. Esse endereço copiado ainda pode acessar um objeto pertencente à função chamadora, o que possibilita trocas, funções de entrada e várias saídas. A interface da função também deve mostrar se ela altera os dados. Acompanhe o endereço durante a chamada, diferencie alterações locais de escritas por meio dele e verifique quais objetos ainda existirão quando um ponteiro retornado for usado.
2.1 Altere variáveis da função chamadora por meio de parâmetros
C passa todo argumento por valor. Um parâmetro é uma variável local inicializada a partir do argumento. Passar um endereço não altera essa regra: a função recebe uma cópia do endereço, e essa cópia pode acessar o objeto da função chamadora.
Compare duas funções de redefinição
void reset_copy(int n) {
n = 0;
}
void reset(int *n) {
*n = 0;
}
Para int score = 12;, reset_copy(score) altera apenas seu n local. score continua sendo 12. A chamada reset(&score) copia o endereço de score para o parâmetro ponteiro n; atribuir um valor a *n altera score para 0. Essa técnica costuma ser chamada de passagem por referência, embora a passagem efetiva de argumentos da linguagem continue sendo por valor.
O endereço na chamada e a desreferenciação no corpo realizam tarefas diferentes. &score informa à função onde está o objeto. *n = 0 informa o que armazenar nele. Chamar reset(score) fornece um inteiro onde é exigido um ponteiro e gera um diagnóstico do compilador.
Acompanhe uma troca
void swap(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
/* Inside a calling function: */
int x = 3, y = 8;
swap(&x, &y);
aaponta paraxebaponta paray.tguarda o valor original dex, que é 3.*a = *bescreve 8 emx.*b = tescreve o 3 guardado emy.
As duas variáveis da função chamadora mudam. Uma versão que recebe int a, int b troca apenas cópias. Uma versão com ponteiros que executa int *t = a; a = b; b = t; também não troca os inteiros: ela redireciona apenas os parâmetros ponteiro locais.
Forneça mais de um resultado
Uma instrução de retorno fornece um valor, mas parâmetros ponteiro podem fornecer saídas adicionais:
void divide(int total, int size, int *q, int *r) {
*q = total / size;
*r = total % size;
}
/* size must be positive for these example inputs. */
int q, r;
divide(17, 5, &q, &r);
A divisão inteira resulta em q igual a 3 e r igual a 2. A função chamadora fornece dois objetos distintos nos quais é possível escrever. Outra interface útil retorna um status de sucesso e escreve uma saída apenas em caso de sucesso; as funções chamadoras verificam esse status antes de usar a saída.
Reconheça o mesmo padrão em chamadas de biblioteca
scanf("%d", &age) precisa do endereço de age porque armazena nele um inteiro extraído da entrada. Passar age fornece um valor em vez do int * exigido, produzindo comportamento indefinido. Verifique se scanf retorna 1 antes de usar o valor recém-lido.
Para texto, char name[20]; scanf("%19s", name); passa um ponteiro para o primeiro caractere do arranjo. A largura permite no máximo 19 caracteres de entrada, deixando espaço para '\0'. A expressão do arranjo já fornece o ponteiro para caractere exigido.
Um parâmetro de arranjo também recebe um endereço, não um bloco copiado de elementos. Em void zero(int *a, size_t n), atribuições a a[i] afetam o arranjo da função chamadora. Passar o endereço copia apenas um ponteiro, o que também permite que funções somente de leitura examinem arranjos grandes sem copiar cada elemento.
Principais conclusões
- C copia todo argumento, incluindo endereços.
- Uma escrita por meio de um parâmetro ponteiro pode alterar o objeto da função chamadora.
- Uma troca de inteiros que funciona troca
*ae*busando um valor guardado. - Parâmetros de saída precisam de objetos válidos e modificáveis fornecidos pela função chamadora.
scanfrecebe endereços e informa quantas conversões foram bem-sucedidas.- Parâmetros de arranjo compartilham o acesso aos elementos pertencentes à função chamadora.
2.2 Acesso com const e valores de retorno seguros
Uma interface com ponteiro deve deixar duas coisas claras: se a função pode escrever por meio dele e por quanto tempo o armazenamento apontado permanece válido. const responde à primeira pergunta. O tempo de vida do objeto responde à segunda.
Identifique a restrição de const
| Declaração | Pode redirecionar p? | Pode atribuir por meio de *p? |
|---|---|---|
const int *p = &x; | Sim | Não |
int *const p = &x; | Não | Sim |
const int *const p = &x; | Não | Não |
Um ponteiro para dados constantes restringe esse caminho de acesso. Se x foi definida como em int x = 5;, sem const, x = 6; ainda é permitido mesmo enquanto um const int * aponta para ela. Se o próprio objeto foi definido com const, tentar modificá-lo por meio de qualquer ponteiro é comportamento indefinido.
Um ponteiro constante mantém um único endereço. Ele deve ser inicializado, e o endereço não pode ser substituído posteriormente. O objeto nesse endereço ainda pode mudar quando seu tipo permite. Leia a partir do nome em direção às partes externas: int *const p torna p constante; const int *p protege o int acessado por meio de p.
Use um parâmetro somente de leitura como const char *s quando uma função apenas examinar uma cadeia de caracteres. Um char * comum pode ser passado para esse parâmetro sem conversão explícita. A função pode avançar seu s local, mas uma atribuição por meio de *s é rejeitada. Converter de volta para um tipo que permita escrita exige mais que eliminar um aviso do compilador: o objeto subjacente deve de fato ser modificável.
Retorne uma posição no armazenamento da função chamadora
int *find(int *a, size_t n, int target) {
for (size_t i = 0; i < n; i++) {
if (a[i] == target) return &a[i];
}
return NULL;
}
Para int v[] = {4, 9, 2, 9};, find(v, 4, 9) retorna &v[1]. A função termina, mas v pertence à função chamadora e continua existindo nela. Após verificar se o resultado é diferente de NULL, a função chamadora pode calcular found - v para obter o índice 1 ou executar *found = 0 para atualizar o elemento. Uma busca por 7 retorna NULL, que não deve ser desreferenciado. strchr e strstr usam a mesma convenção de retornar nulo quando não encontram o resultado.
Rejeite endereços de variáveis locais que deixaram de existir
int *bad(void) {
int result = 42;
return &result;
}
result tem duração de armazenamento automática. Seu tempo de vida termina quando a chamada retorna, deixando a função chamadora com um ponteiro pendente. O compilador pode emitir um aviso, e qualquer acesso posterior por meio desse ponteiro tem comportamento indefinido. Os bytes antigos estarem visíveis em uma execução não mantém o objeto existente.
O mesmo problema pode ocorrer na chave de fechamento de um bloco interno. Um ponteiro externo não pode prolongar o tempo de vida de uma variável interna cujo endereço ele salvou.
Escolha armazenamento que dure o suficiente
Retorne um valor int quando a função chamadora precisar apenas de um resultado inteiro. Para vários resultados, permita que a função chamadora forneça o armazenamento a ser preenchido. Um ponteiro retornado também pode se referir ao arranjo da função chamadora, a um literal de cadeia de caracteres exposto por meio de const char * ou a um objeto static.
Uma variável local estática é inicializada uma vez e dura durante todo o programa, mas todas as chamadas a compartilham. Se uma chamada posterior armazenar um novo valor nela, todo ponteiro retornado anteriormente verá essa alteração. O armazenamento estático resolve o problema de tempo de vida sem fornecer a cada chamada seu próprio objeto de resultado independente.
Principais conclusões
const int *restringe escritas por meio desse ponteiro, enquantoint *constfixa o endereço.- Um objeto definido com
constnão pode ser modificado de forma válida. - Ponteiros para elementos de um arranjo ainda existente da função chamadora podem permanecer válidos após o retorno de uma função.
- Verifique um resultado de busca possivelmente nulo antes de acessá-lo ou subtraí-lo.
- Retornar o endereço de uma variável local automática deixa um ponteiro pendente.
- Um objeto estático de resultado é compartilhado por todas as chamadas.
3Percorra arranjos e cadeias de caracteres
A aritmética de ponteiros relaciona o endereço de um elemento à sua posição em um arranjo. O tipo apontado fornece o tamanho do passo, enquanto o arranjo fornece a faixa permitida. Use esse modelo para ler índices, passar uma fatia para uma função e criar laços que parem antes de um acesso inválido. Cadeias de caracteres acrescentam uma segunda regra de parada: o caractere nulo de terminação marca o fim do texto, mesmo quando o armazenamento contém mais bytes.
3.1 Passos entre elementos e limites do arranjo
Somar um inteiro a um ponteiro o move por elementos do tipo apontado. Se p aponta para o elemento i de um arranjo, p + n aponta para o elemento i + n, desde que a posição resultante esteja dentro do arranjo ou imediatamente após seu último elemento.
Aplique a escala ao endereço uma vez
No endereço ilustrativo 0x1000, somar 3 fornece estes resultados:
| Tipo de ponteiro | Tamanho de um elemento | Endereço após p + 3 |
|---|---|---|
int * | 4 bytes | 0x100C |
double * | 8 bytes | 0x1018 |
char * | 1 byte | 0x1003 |
O compilador aplica a escala. Escrever p + 3 * sizeof *p aplica a escala duas vezes e geralmente chega ao elemento errado. Use unsigned char * quando o passo desejado for realmente um byte da representação de um objeto.
Para int a[6] = {10, 20, 30, 40, 50, 60}; e int *p = &a[1];, *(p + 2) lê a[3], que é 40. A atribuição p += 3; move o próprio ponteiro para a[4], que contém 50. A partir daí, *(p - 1) lê 40. p++, p--, p += n e p -= n usam passos por elementos.
Subtraia posições para contar elementos
&a[5] - &a[1] é 4, e inverter os operandos resulta em -4. O resultado já conta elementos; não o divida por sizeof(int). Seu tipo com sinal é ptrdiff_t, declarado em <stddef.h>, e %td o imprime.
Subtraia apenas ponteiros para posições no mesmo arranjo, incluindo a posição imediatamente após seu último elemento, com um resultado representável em ptrdiff_t. Essa operação descreve posições dentro desse arranjo. Subtrair endereços de variáveis não relacionadas é comportamento indefinido, mesmo que os números impressos por acaso estejam próximos.
Use a posição após o último elemento como limite
Para seis elementos, a + 6 é um ponteiro para a posição imediatamente após o último elemento válido. Ele pode servir como limite e participar de uma subtração válida dentro do mesmo arranjo, mas não pode ser desreferenciado. a + 7 e a - 1 já são comportamento indefinido quando formados; evitar uma desreferenciação posterior não os corrige.
for (int *p = a; p < a + 6; p++) {
printf("%d\n", *p);
}
O corpo é executado para os índices de 0 a 5. Após o último incremento, p é igual a a + 6, e o teste seguinte interrompe o laço. Usar <= permitiria que o corpo acessasse essa posição que não é um elemento.
Para as regras de aritmética, um objeto isolado funciona como um arranjo de um elemento. Você pode formar &x + 1; não pode usá-lo para ler uma suposta variável seguinte.
Compare apenas posições que façam sentido
Operadores de ordem como < comparam posições dentro de um arranjo. Não ordene ponteiros para objetos não relacionados. Igualdade e desigualdade podem comparar tipos de ponteiro compatíveis e testar se um ponteiro é nulo.
Você não pode somar dois ponteiros, multiplicar ponteiros nem fazer aritmética do C padrão com um void * ou um ponteiro para função. Para encontrar o ponto médio de um intervalo no mesmo arranjo [p, q), use p + (q - p) / 2: subtraia as posições para obter uma contagem, divida essa contagem e avance a partir de p.
Principais conclusões
- A adição de inteiros move um ponteiro por elementos de seu tipo apontado.
- Uma diferença entre ponteiros para posições no mesmo arranjo já conta elementos e tem tipo
ptrdiff_t. - Ponteiros para a posição imediatamente após o último elemento podem marcar um limite, mas não podem ser desreferenciados.
- Formar um ponteiro para antes do arranjo ou além da posição imediatamente após seu último elemento é inválido.
- Use posições no mesmo arranjo para comparações de ordem.
- Subtraia as posições antes de calcular um ponto médio.
3.2 Expressões de arranjo e parâmetros ponteiro
Um arranjo é um bloco de elementos. Um ponteiro é um objeto separado que contém um endereço. A relação próxima entre eles vem de uma conversão automática que ocorre na maioria das expressões, e não de serem o mesmo tipo de objeto.
Converta uma expressão de arranjo
int a[5] = {3, 1, 4, 1, 5};
int *p = a;
Aqui, a é convertido em um ponteiro para a[0]. Essa conversão de arranjo em ponteiro torna o inicializador equivalente a &a[0]. Tanto *p quanto a[0] leem 3.
C define a indexação por meio de aritmética: a[i] significa *(a + i). Portanto, p[3] também funciona e lê 1. &a[i] e a + i identificam o mesmo elemento. Até 3[a] tem o mesmo significado definido que a[3], embora a forma usual seja muito mais clara.
Saiba quando o arranjo inteiro continua sendo um arranjo
A conversão não ocorre para operandos de sizeof ou do & unário. Ela também não ocorre para typeof e typeof_unqual de C23, nem quando um literal de cadeia de caracteres inicializa um arranjo de caracteres.
Expressão, com valores int de 4 bytes e um ponteiro de 8 bytes | Resultado |
|---|---|
sizeof a | 20 bytes para os cinco elementos |
sizeof p | 8 bytes para o objeto ponteiro |
sizeof a / sizeof a[0] | 5 elementos |
Você não pode atribuir um novo endereço a um arranjo nem incrementar seu nome: a = p; e a++; são inválidos. Você pode redirecionar p, por exemplo, com p = a + 2;. Copie o conteúdo de um arranjo com um laço sobre os elementos ou uma chamada adequada a memcpy.
Acompanhe o tipo de parâmetro ajustado
int sum(const int *values, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += values[i];
return total;
}
A função chamadora fornece um endereço e uma contagem. Para o arranjo acima, sum(a + 2, 3) visita os índices originais 2, 3 e 4, somando 4, 1 e 5 para retornar 10. Dentro da função, o índice 0 é relativo ao ponteiro inicial fornecido.
Em uma lista de parâmetros de função, int a[], int a[10] e int *a declaram um parâmetro ponteiro. A forma simples [10] não fornece um limite verificado nem transfere um comprimento. Dentro dessa função, sizeof a mede um ponteiro. A expressão sizeof a / sizeof a[0] resultaria em 2 em um sistema com ponteiros de 8 bytes e valores int de 4 bytes, independentemente do comprimento real do arranjo da função chamadora.
Diferencie um ponteiro para elemento de um ponteiro para arranjo
a é convertido em int *, enquanto &a tem tipo int (*)[5], um ponteiro para o arranjo inteiro de cinco elementos. Ambos identificam o início do mesmo armazenamento, mas a aritmética segue seus tipos diferentes. a + 1 avança um int, ou 4 bytes nessa disposição. &a + 1 avança um arranjo inteiro, ou 20 bytes, até a posição imediatamente após seu último elemento.
Um endereço inicial impresso não revela essa diferença de tipo. Leia a declaração e mantenha o tipo apontado junto ao ponteiro ao rastrear uma expressão.
Principais conclusões
- A maioria das expressões de arranjo é convertida em um ponteiro para o primeiro elemento.
a[i]é definido como*(a + i).sizeof amede o arranjo inteiro apenas ondearealmente é um arranjo.- Um parâmetro de arranjo é ajustado para um ponteiro; passe seu comprimento utilizável separadamente.
&aaponta para um arranjo inteiro, então sua aritmética tem um passo maior.
3.3 Laços com ponteiros sobre números e texto
Um laço com ponteiro precisa de uma posição atual e de uma regra de parada. Um arranjo com quantidade conhecida usa um ponteiro para o fim; uma cadeia de caracteres usa seu '\0' de terminação. Escolha a forma que torne os elementos visitados mais fáceis de visualizar.
Percorra um intervalo com quantidade conhecida
int a[] = {3, 1, 4, 1, 5};
int total = 0;
for (const int *p = a; p < a + 5; p++) {
total += *p;
}
Os totais acumulados são 3, 4, 8, 9 e 14. Um laço com índice que soma a[i] para índices menores que 5 fornece o mesmo resultado. Compiladores modernos com otimização costumam gerar código semelhante para essas formas; a sintaxe com ponteiros, por si só, não garante vantagem de velocidade.
Leia antes ou depois de mover
Com p no 3 de {3, 1, 4}, int v = *p++; define v como 3 e deixa p no 1. O incremento pós-fixado fornece o valor anterior do ponteiro a * e atualiza o ponteiro. *++p primeiro move para o próximo elemento e depois o lê. (*p)++ altera o elemento atual e não move o ponteiro.
Pare no terminador de uma cadeia de caracteres
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') p++;
return (size_t)(p - s);
}
Para "pointer", p para no terminador, sete posições a partir do início. O resultado exclui esse terminador. Uma contagem de caracteres usa o mesmo percurso: examinar "mississippi" e incrementar um contador sempre que *p == 's' conta quatro ocorrências entre onze letras.
Um ponteiro para o meio de uma cadeia de caracteres dá acesso a seu sufixo. Com const char *msg = "hello world";, puts(msg + 6) imprime "world". Os caracteres anteriores permanecem onde estavam.
Copie também o terminador
char destination[4];
const char *src = "cat";
char *dst = destination;
while ((*dst++ = *src++) != '\0') {
}
Cada iteração atribui um caractere, testa o valor atribuído e avança ambos os ponteiros. A última iteração copia '\0', então o destino é uma cadeia de caracteres completa. Ambos os ponteiros ficam imediatamente após seus terminadores. O armazenamento precisa do comprimento do texto mais um byte; omitir esse byte extra causa um estouro de buffer. Esse padrão de cópia também exige que as regiões de origem e destino não se sobreponham.
Inverta a partir das duas extremidades
Para uma cadeia de caracteres modificável, obtenha primeiro seu comprimento. Se o comprimento for zero, pare antes de formar um ponteiro para o último caractere. Caso contrário, defina left = s e right = s + length - 1. Enquanto left < right, troque os caracteres, incremente left e decremente right. Para "stressed", quatro trocas produzem "desserts"; o terminador permanece no fim original.
A verificação de cadeia vazia importa porque subtrair 1 de seu ponteiro inicial formaria um ponteiro para antes do arranjo.
Escolha armazenamento modificável para caracteres
char word[] = "hello"; cria um arranjo modificável de seis bytes, incluindo o terminador. const char *word = "hello"; aponta para um literal de cadeia de caracteres com duração de armazenamento estática. Modificar um literal de cadeia de caracteres é comportamento indefinido, mesmo que uma declaração use char * sem const. Use const char * para que escritas acidentais sejam um erro de compilação.
Um laço sobre um arranjo deve parar antes da posição imediatamente após seu último elemento, e um percurso de cadeia de caracteres deve realmente encontrar um terminador dentro do armazenamento acessível. Um ponteiro não carrega um comprimento nem uma garantia de que os bytes formam uma cadeia de caracteres.
Principais conclusões
- Um intervalo com quantidade conhecida termina em um ponteiro para a posição imediatamente após o último elemento; uma cadeia termina em seu caractere nulo.
*p++usa a posição anterior e avança o ponteiro.- A cópia de uma cadeia inclui o terminador e precisa do comprimento mais um byte.
- Verifique se a cadeia está vazia antes de formar seu ponteiro para o último caractere.
- Arranjos de caracteres inicializados a partir de texto são modificáveis; literais de cadeia de caracteres não são.
- Um ponteiro sozinho não carrega um comprimento nem uma garantia de terminação.
4Siga e atualize outro ponteiro
Um ponteiro pode ser, ele próprio, o objeto acessado por meio de um endereço. Seguir dois níveis permite que uma função substitua o ponteiro da função chamadora e que um programa percorra um arranjo de endereços de cadeias de caracteres. Mantenha visível o tipo de cada nível: um ponteiro para um inteiro, um ponteiro para esse ponteiro e um ponteiro para uma linha inteira representam disposições diferentes. Acompanhe o objeto alcançado por cada desreferenciação antes de decidir o que uma atribuição alterará.
4.1 Dois níveis de indireção
Um ponteiro é um objeto armazenado, então tem um endereço que outro ponteiro pode conter. Um ponteiro para ponteiro acrescenta mais um passo entre a expressão e os dados finais.
Siga as setas uma de cada vez
int x = 7;
int *p = &x;
int **pp = &p;
Use endereços ilustrativos: x em 0x2000 contém 7; p em 0x3000 contém 0x2000; pp em 0x4000 contém 0x3000. Avaliar *pp alcança p, cujo valor tem tipo int *. Avaliar **pp segue esse ponteiro até x, um int.
| Expressão | Tipo | Objeto ou valor alcançado |
|---|---|---|
pp | int ** | Endereço de p |
*pp | int * | O ponteiro armazenado em p |
**pp | int | O inteiro armazenado em x |
&p tem um nível de ponteiro a mais que p; cada desreferenciação remove um nível. Inicializar int **pp com &x é incompatível porque &x tem tipo int *.
Escolha o nível da atribuição
Com outro inteiro existente y, **pp = 9; altera x. A atribuição *pp = &y; altera o ponteiro p, então usos futuros de **pp alcançam y. Ela deixa x com seu valor atual. Se q for outro int *, pp = &q; altera apenas o ponteiro externo, fazendo sua primeira seta alcançar q em vez de p.
Não conte os asteriscos sem verificar também o objeto em cada nível. Antes de avaliar **pp, tanto o caminho até o objeto ponteiro quanto o caminho desse objeto até o inteiro devem ser válidos.
Mova um ponteiro para cadeia da função chamadora
void skip_spaces(const char **s) {
while (**s == ' ') {
(*s)++;
}
}
/* In the caller: */
const char *text = " hi";
skip_spaces(&text);
puts(text);
A função chamadora passa o endereço de sua variável ponteiro. Portanto, *s designa text; **s lê o caractere atual. Cada (*s)++ avança text em um caractere. Após três iterações, ele aponta para 'h', e puts imprime "hi". A função exige um ponteiro válido para um ponteiro que aponta para uma cadeia terminada em nulo.
Uma versão que recebe const char *s e incrementa s altera apenas uma cópia local. Os parênteses em (*s)++ também são essenciais: *s++ incrementa o ponteiro local externo em vez do ponteiro para caractere da função chamadora. Em const char **s, os caracteres são somente de leitura por esse caminho de acesso, mas o ponteiro da função chamadora pode se mover.
Leia um ponteiro de saída de uma função de biblioteca
strtol de <stdlib.h> usa um parâmetro de saída do tipo char **:
char *end;
long number = strtol("42abc", &end, 10);
Ela retorna 42 e armazena em end um ponteiro para o primeiro caractere não usado, que agora alcança "abc". Se o ponteiro de fim for igual ao ponteiro inicial, nenhum dígito foi convertido. Um analisador numérico com verificação completa também trata erros de faixa; aqui, a função do ponteiro é informar a posição de parada.
Principais conclusões
int **pppode conter o endereço de um objetoint *.- Cada desreferenciação segue um nível de ponteiro.
**ppaltera o inteiro final;*pppode alterar o ponteiro intermediário.- Atualizar um ponteiro da função chamadora exige o endereço desse ponteiro.
strtolusachar **para retornar a posição do primeiro caractere não usado.
4.2 Arranjos de ponteiros para cadeias e matrizes retangulares
Um arranjo pode armazenar ponteiros como elementos. Uma matriz bidimensional, por sua vez, armazena linhas completas como elementos. Ambos aceitam dois índices, mas seu armazenamento e os tipos dos ponteiros resultantes da conversão diferem.
Armazene endereços de cadeias separadas
const char *days[] = {"Mon", "Tue", "Wed"};
days contém três ponteiros para caracteres. Cada um aponta para um literal de cadeia de caracteres separado. days[1] é o ponteiro para "Tue", e days[1][0] lê 'T'. Com ponteiros para objetos de 8 bytes, sizeof days é 24, independentemente dos comprimentos das cadeias. As próprias cadeias ocupam armazenamento em outro lugar.
Os comprimentos podem ser diferentes, formando uma coleção irregular. Reordenar a lista pode trocar elementos ponteiro sem copiar o texto. Atribuir days[0] = "Thursday"; é permitido: o elemento ponteiro pode mudar, embora os caracteres acessados por ele sejam somente de leitura.
Na maioria das expressões, days é convertido em um ponteiro para seu primeiro elemento. Como um elemento é um const char *, o tipo convertido é const char **:
for (const char **d = days; d < days + 3; d++) {
puts(*d);
}
O laço avança um elemento ponteiro por iteração. *d recupera o ponteiro para caractere de que puts precisa.
Leia cadeias da linha de comando
int main(int argc, char *argv[]) recebe o mesmo tipo de parâmetro que char **argv. argc não é negativo e conta as cadeias disponíveis. Quando é positivo, argv[0] representa o nome do programa ou uma cadeia vazia se esse nome não estiver disponível. Os argumentos do usuário ocupam os índices de 1 até argc - 1, e argv[argc] é um ponteiro nulo.
Para uma chamada típica ./greet Ada 42, argc é 3, argv[1] aponta para "Ada" e argv[2] aponta para "42". O último argumento é texto até que uma conversão como strtol o leia. argv[1][0] é 'A'. Verifique argc > 2 antes de usar argv[2].
Um laço que imprime os argumentos do usuário pode usar for (int i = 1; i < argc; i++) puts(argv[i]);. Para percorrer a partir de argv + 1 até um ponteiro nulo, verifique primeiro argc > 0; se argc for zero, não há um intervalo de argumentos do usuário que possa ser percorrido dessa maneira. As cadeias de caracteres dos argumentos são modificáveis, ao contrário dos literais de cadeia.
Percorra linhas completas
int grid[3][4] contém doze inteiros armazenados por linhas: toda a linha 0, depois a linha 1 e depois a linha 2. Com valores int de 4 bytes, cada linha ocupa 16 bytes. grid[2][1] fica 36 bytes após grid[0][0]: duas linhas completas contribuem com 32 bytes e a coluna contribui com 4.
| Declaração ou expressão | Significado |
|---|---|
int (*rows)[4] | Ponteiro para uma linha de quatro inteiros |
int *ptrs[4] | Arranjo de quatro ponteiros para inteiros |
grid convertido | int (*)[4] |
grid + 1 avança uma linha. Uma função que o recebe pode usar int g[][4] ou int (*g)[4], com uma contagem de linhas separada. Ela não pode usar int **: a matriz não tem ponteiros para linhas armazenados para desreferenciar. Uma conversão explícita não pode criar esses ponteiros ausentes.
Da mesma forma, char names[3][6] contém dezoito posições para caracteres em linhas de tamanho fixo. const char *names[3] contém três posições para ponteiros que podem se referir a cadeias de comprimentos diferentes.
Principais conclusões
- Um arranjo de ponteiros para cadeias armazena endereços, com o texto em outro lugar.
- Um arranjo de ponteiros é convertido em um ponteiro para seu primeiro elemento ponteiro.
argv[argc]é nulo; verifique a contagem de argumentos antes de ler uma cadeia específica.- Uma matriz bidimensional armazena linhas contíguas e não contém objetos ponteiro para linhas.
int (*)[4]eint **descrevem disposições de armazenamento diferentes.- Dois índices, por si só, não estabelecem que dois objetos tenham o mesmo tipo.
5Escolha funções e forneça funções de retorno de chamada
Um ponteiro para função permite que um programa escolha uma operação durante a execução. Seu tipo registra a chamada permitida, e um parâmetro de função de retorno de chamada permite que outra função solicite essa operação quando necessário. Use essas ideias primeiro em uma pequena tabela de funções aritméticas e depois em qsort, onde a função de retorno de chamada deve interpretar corretamente os endereços dos elementos e retornar uma ordem consistente. Ordenar ponteiros para cadeias reúne ponteiros para funções e dois níveis de ponteiros para objetos.
5.1 Declarações e chamadas de ponteiros para funções
Um ponteiro para função armazena uma referência a uma função que pode ser chamada posteriormente. Seu tipo inclui o tipo de retorno e os tipos dos parâmetros. Esses tipos devem corresponder à função usada por meio do ponteiro.
Coloque parênteses em torno do nome do ponteiro
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int (*op)(int, int) = add;
Leia a partir de op: ele é um ponteiro, o elemento apontado é uma função que recebe dois valores int, e essa função retorna um int. Sem os parênteses, int *op(int, int); declara uma função que retorna um ponteiro para inteiro. Os parênteses mudam o significado da declaração.
O nome de uma função é convertido em ponteiro para função nesse inicializador. op = &add; é equivalente a op = add;. Tanto op(6, 3) quanto (*op)(6, 3) chamam add e retornam 9. Atribuir op = sub; faz a mesma chamada retornar 3. Nada na expressão de chamada mudou, exceto a função armazenada no ponteiro.
Mantenha a assinatura compatível
Um ponteiro do tipo int (*)(int, int) não pode chamar com segurança uma função que recebe e retorna double. Por exemplo, atribuir sqrt a ele produz um diagnóstico de tipo incompatível. Converter explicitamente o ponteiro para função para silenciar um diagnóstico não torna válida uma chamada incompatível; o comportamento é indefinido.
O C padrão trata separadamente ponteiros para objetos e ponteiros para funções. void * é o tipo de ponteiro genérico para objeto, não um recipiente portável para um ponteiro para função. Ponteiros para funções permitem testes de igualdade e desigualdade, mas não aritmética de ponteiros.
Um ponteiro para função nulo significa que não há função que possa ser chamada. Quando uma interface permite esse estado, verifique-o:
if (op != NULL) {
int result = op(6, 3);
printf("%d\n", result);
}
Chamar uma função por meio de um ponteiro para função nulo é comportamento indefinido.
Nomeie o tipo uma vez
typedef int (*binop)(int, int);
binop ops[] = {add, sub, mul};
O typedef nomeia um tipo de ponteiro. binop selected = mul; declara um ponteiro; binop pick(char symbol); declara uma função que retorna um ponteiro desse tipo. Sem o typedef, essa declaração de retorno seria int (*pick(char symbol))(int, int);.
Um arranjo de ponteiros para funções é uma tabela de despacho. ops[2](6, 3) seleciona mul e retorna 18. Para um índice de seleção com sinal i, verifique i >= 0 && i < 3 antes de avaliar ops[i]. Acessar um índice fora da tabela é inválido, mesmo que todas as funções armazenadas possam ser chamadas.
Leia declarações maiores a partir do nome
Comece pelo nome e resolva os parênteses e as partes pós-fixadas antes de avançar para as partes externas. Em int (*table[3])(int, int), table[3] cria um arranjo de três elementos, * torna cada elemento um ponteiro e (int, int) torna seu destino uma função que retorna int.
Em char *(*f)(const char *), f é um ponteiro para uma função que recebe um const char *; essa função retorna um char *. Mantenha os requisitos de tempo de vida do ponteiro retornado separados dos requisitos de chamada compatível do ponteiro para função.
Principais conclusões
- Os parênteses distinguem um ponteiro para função de uma função que retorna um ponteiro.
- Um ponteiro para função registra os tipos de retorno e de parâmetros das chamadas válidas.
- O nome de uma função pode inicializar um ponteiro para função compatível sem
&. - Um typedef pode tornar tabelas de ponteiros para funções e tipos de retorno mais fáceis de ler.
- Valide o índice da tabela e qualquer ponteiro que possa ser nulo antes da chamada.
5.2 Funções de retorno de chamada e comparações corretas para qsort
Uma função de retorno de chamada é uma função fornecida a outra função, que a chama como parte de seu trabalho. Isso separa a operação geral de percurso ou ordenação da ação ou ordem específica que você escolhe.
Passe uma operação sem chamá-la
void twice(int *x) { *x *= 2; }
void for_each(int *a, size_t n, void (*fn)(int *)) {
for (size_t i = 0; i < n; i++) fn(&a[i]);
}
Para int v[] = {1, 2, 3};, for_each(v, 3, twice); produz {2, 4, 6}. O nome da função fornece um ponteiro. Escrever twice() tentaria chamá-la imediatamente, sem o argumento exigido, em vez de fornecer uma operação para chamadas posteriores.
Recupere um tipo a partir de um ponteiro genérico para objeto
Um void * pode conter um ponteiro para objeto e ser convertido de volta ao tipo original. Ele não pode ser desreferenciado nem usado em aritmética de ponteiros do C padrão porque não fornece um tipo de elemento. Preserve os qualificadores: dados somente de leitura passam por const void * e depois por um ponteiro adequado para dados constantes.
qsort de <stdlib.h> recebe um endereço base, uma contagem de elementos, um tamanho de elemento em bytes e uma função de comparação. A função de retorno de chamada tem tipo int (*)(const void *, const void *). Cada argumento aponta para um elemento do arranjo, e o sinal retornado indica onde o primeiro elemento deve ficar:
| Sinal retornado | Ordem |
|---|---|
| Negativo | Primeiro antes do segundo |
| Zero | Equivalentes para essa ordenação |
| Positivo | Primeiro depois do segundo |
Ordene inteiros sem estouro
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
/* In the caller: */
int v[] = {42, -7, 19, 0, 3};
qsort(v, 5, sizeof v[0], cmp_int);
O arranjo torna-se {-7, 0, 3, 19, 42}. As comparações produzem 0 ou 1, então a expressão de retorno produz apenas -1, 0 ou 1. Inverter a expressão de ordenação para (y > x) - (y < x) ordena em ordem decrescente.
Não use return x - y; para inteiros com sinal arbitrários. Para int de 32 bits, subtrair -2.000.000.000 de 2.000.000.000 ultrapassa INT_MAX. O estouro de inteiro com sinal tem comportamento indefinido, então não há garantia de um resultado com redução modular nem de um sinal de comparação confiável.
Ordene ponteiros para cadeias de caracteres
int cmp_str(const void *a, const void *b) {
return strcmp(*(const char *const *)a,
*(const char *const *)b);
}
const char *names[] = {"Lin", "Ada", "Grace"};
qsort(names, 3, sizeof names[0], cmp_str);
Inclua <string.h> para strcmp. Um elemento do arranjo aqui é, ele próprio, um ponteiro. A função de retorno de chamada recebe um endereço desse elemento ponteiro, então desreferenciar uma vez recupera o endereço da cadeia. O const interno acrescentado impede a alteração do elemento por esse caminho de acesso. Após a ordenação, a ordem é "Ada", "Grace", "Lin".
strcmp(a, b) leria os bytes das posições dos ponteiros como texto. Use também sizeof names[0], o tamanho de um elemento ponteiro, não sizeof(char) nem o comprimento de um nome.
As funções de comparação devem deixar os elementos inalterados e responder de modo consistente, incluindo uma ordenação transitiva. qsort não promete preservar a ordem dos elementos considerados iguais na comparação. bsearch aceita o mesmo formato de função de comparação para buscar em um arranjo já ordenado segundo essa ordem.
Principais conclusões
- Passe o nome de uma função para fornecer uma função de retorno de chamada; parênteses chamam uma função imediatamente.
- Recupere o tipo do elemento de cada argumento genérico de comparação antes do acesso.
qsortprecisa de uma contagem, do tamanho de um elemento e de uma função de comparação compatível.- Use sinais de comparação sem uma subtração que possa causar estouro.
- Funções de comparação de ponteiros para cadeias recebem endereços de posições de ponteiros.
- Resultados de comparação iguais não garantem uma ordem estável após
qsort.
Exercícios resolvidos de Ponteiros em C
1Acompanhe um endereço e o valor que ele alcança
13 passos
- 1IntroduçãoMantenha três coisas separadas
Um inteiro armazenado tem um valor, uma localização e um tempo de vida. Um ponteiro acrescenta outro objeto cujo valor é um endereço. Acompanhe um pequeno programa registrando ambos os tipos de valores: inteiros como 10 e endereços como
0x1000.A pergunta útil em cada linha é concreta: essa instrução altera um endereço, altera o objeto alcançado por esse endereço ou apenas lê um valor?
- 2ExplicaçãoDisponha quatro inteiros
Imagine
int scores[4]começando em0x1000em um sistema com inteiros de 4 bytes. O primeiro elemento usa os bytes de0x1000a0x1003. O seguinte começa em0x1004, seguido por0x1008e0x100C.Os elementos ocupam armazenamento consecutivo. Essa regra de disposição pertence ao arranjo; ela não garante uma ordem para dois inteiros declarados separadamente.
- 3ExemploMeça o arranjo e depois seu conteúdo
Para
char word[20] = "hi";, duas medições fornecem resultados diferentes:sizeof wordé 20 bytes de armazenamento do arranjo.strlen(word)é 2 caracteres antes de'\0'.
O terminador ocupa outro elemento do arranjo, e os elementos restantes continuam pertencendo ao arranjo.
sizeofretornasize_t, impresso com%zu; ele não conta quantos elementos o programa usa atualmente. - 4ConexãoSepare disposição de tempo de vida
O alinhamento controla os endereços iniciais adequados, enquanto a ordem dos bytes controla a ordem dos bytes dentro de um valor. Nenhum dos dois informa por quanto tempo um objeto existe.
Uma variável local comum dura enquanto seu bloco é executado. Uma variável local estática ou global dura durante toda a execução do programa. Salvar um endereço registra um caminho para o armazenamento; isso não pode manter uma variável local automática existente após o término do bloco.
- 5DesafioEncontre o endereço de um elemento
Um arranjo de inteiros de seis elementos começa em
0x1800, com 4 bytes por elemento. Onde começa o elemento 3 e qual é o tamanho do arranjo inteiro?---reveal---
O elemento 3 começa doze bytes após o início, em
0x180C. Seis elementos de 4 bytes ocupam 24 bytes. O índice fornece um deslocamento em elementos; multiplicá-lo pelo tamanho do elemento fornece o deslocamento em bytes. - 6ExplicaçãoDê um destino ao ponteiro
Considere
int count = 42; int *p = &count;. O operador&obtém o endereço decount, eparmazena esse endereço. Secountestá no endereço ilustrativo0x2000, o valor do ponteiro é0x2000; o valor do inteiro continua sendo 42.O ponteiro também ocupa seu próprio armazenamento. Seu endereço é
&p, uma informação diferente do endereço contido emp. - 7DicaLeia o asterisco ao lado de cada nome
int *p, q;declara um ponteiro e um inteiro comum. Para declarar dois ponteiros, useint *p, *q;ou coloque as declarações em linhas separadas.Inicialize um ponteiro com o endereço de um objeto adequado ou com
NULL. Um ponteiro nulo é um estado vazio conhecido; um ponteiro local não inicializado tem valor indeterminado e não pode ser testado com segurança como se já fosse nulo. - 8DesafioRedirecione sem alterar os dados
Comece com
int a = 9, b = 16; int *p = &a;. Depois, executep = &b;. Quais valores inteiros mudam e o quepcontém agora?---reveal---
Nenhum dos inteiros muda:
acontinua sendo 9 ebcontinua sendo 16. Apenaspmuda, armazenando o endereço deb. A atribuição de ponteiro substitui um endereço; ela não copia o conteúdo do objeto apontado anteriormente nem do novo. - 9ExemploLeia, some e escreva por meio de p
Continue com
int x = 10; int *p = &x;e execute:*p = *p + 5;O
*pdo lado direito lê 10 dex. Somar 5 resulta em 15. O*pdo lado esquerdo designa o mesmo objeto, então a atribuição escreve 15 emx. O endereço empnão mudou. Em seguida,int y = *p * 2;lê 15 e cria um inteiro separadoycontendo 30. - 10Ideia-chaveUma cópia do endereço cria outro caminho
Para
int n = 6; int *p = &n; int *q = p;, ambos os ponteiros alcançamn. A atribuição copiou um endereço, não o objeto inteiro.Após
*q = 14;, lern,*pou*qresulta em 14. Desenhe uma caixa para o inteiro e duas setas apontando para ela. Criar duas caixas para inteiros sugeriria incorretamente dados independentes. - 11DicaVerifique o objeto apontado antes do acesso
O acesso exige um ponteiro para um objeto existente de tipo adequado. Um ponteiro nulo não designa nenhum objeto, e um ponteiro pendente perdeu seu objeto existente. Um teste de nulidade trata o estado vazio possível de um ponteiro inicializado; ele não estabelece que um objeto apontado por um ponteiro não nulo ainda exista.
Um acesso inválido tem comportamento indefinido. C não promete um valor específico nem um encerramento inesperado, mesmo quando uma execução parece normal.
- 12DesafioSiga um endereço copiado
Quais são os valores finais de
aeb?int a = 6, b = 19; int *p = &a, *q = p; p = &b; *q = *p + 1;---reveal---
apassa a ser 20 ebcontinua sendo 19. Redirecionarpdeixaqapontando paraa. A última linha lêbpor meio depe escreve a soma emapor meio deq. - 13RecapitulaçãoAcompanhe o objeto designado pelo lado esquerdo
Leia o lado esquerdo antes de prever uma alteração. Atribuir um valor a
paltera um endereço; atribuir um valor a*paltera o objeto apontado. Mantenha ponteiros copiados como setas separadas, verifique o tempo de vida do objeto apontado e use o tipo apontado para interpretar cada acesso.
2Faça uma função alterar dados da função chamadora
11 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoAcompanhe o endereço durante uma chamada
- 2ExplicaçãoUm endereço copiado ainda alcança a função chamadora
- 3ExemploGuarde o valor antes de substituí-lo
- 4ExemploEscreva o segundo valor na primeira posição
- 5ExemploConclua a troca
- 6DesafioForneça duas saídas
- 7ExplicaçãoColoque const no nível desejado
- 8ExemploRetorne uma posição válida
- 9DesafioExplique a falha de tempo de vida
- 10DesafioEscolha uma interface de busca que permita escrita
- 11RecapitulaçãoVerifique a escrita e o tempo de vida
3Percorra um arranjo sem ultrapassar seu limite
15 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoEncontre o início, o passo e a parada
- 2ExplicaçãoMova-se por elementos
- 3ExemploSubtraia duas posições
- 4Ideia-chaveUse um limite que não é um elemento
- 5DesafioLocalize o ponto médio
- 6ExplicaçãoUma expressão de arranjo fornece um ponteiro inicial
- 7ExemploUse sizeof no contexto correto
- 8DesafioPasse um intervalo menor
- 9ExemploLeia e avance, ou avance e leia
- 10ExplicaçãoEncontre o fim de uma cadeia de caracteres
- 11ExemploCopie um caractere por vez
- 12DicaReserve a posição do terminador
- 13DesafioCorrija a última visita do laço
- 14DesafioEscolha uma preparação segura para a inversão
- 15RecapitulaçãoTorne explícita cada posição visitada
4Altere um ponteiro e leia um arranjo de ponteiros
12 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoTorne o próprio ponteiro um objeto apontado
- 2ExemploDesenhe duas setas
- 3ExplicaçãoEscolha qual nível muda
- 4ExemploPasse por endereço a posição da função chamadora
- 5DesafioPreveja uma alteração apenas no ponteiro
- 6ConexãoLeia a posição de parada de uma conversão
- 7ExplicaçãoUm arranjo pode conter posições para ponteiros
- 8ExemploAvance pelos endereços
- 9DicaVerifique a contagem de argumentos antes de acessar cadeias
- 10DesafioLeia o tipo de uma linha inteira
- 11DesafioAjuste uma função ao armazenamento fornecido
- 12RecapitulaçãoNomeie o objeto em cada nível
5Escolha uma operação e ordene com uma função de retorno de chamada
15 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoSepare uma tarefa geral de sua operação escolhida
- 2ExplicaçãoLeia o tipo da função que pode ser chamada
- 3ExemploSelecione outra função
- 4DicaNomeie um tipo de ponteiro para uma tabela
- 5DesafioLeia uma declaração relacionada
- 6ExplicaçãoForneça uma função de retorno de chamada para uma ação
- 7ExemploForneça os quatro argumentos da ordenação
- 8ExplicaçãoRecupere o tipo inteiro do elemento
- 9ExemploRetorne o sinal da ordenação
- 10DesafioEvite um atalho que cause estouro
- 11ConexãoOrdene ponteiros para cadeias, não linhas de caracteres
- 12DesafioEscolha o tamanho correto para a ordenação
- 13Ideia-chaveIgualdade não garante estabilidade
- 14DesafioAcompanhe uma função de comparação selecionada
- 15RecapitulaçãoCombine chamada, disposição e ordenação
Questões de Ponteiros em C
Conjunto 1: Acompanhe endereços e chamadas de funções10 Questões de Prática
Questão 1 de 10
int x = 6, y = 17; const int *p = &x;, qual atribuição é permitida?Mostrar respostaOcultar resposta
p é um ponteiro para dados inteiros constantes, então pode ser redirecionado para y. Atribuir um valor por meio de *p é rejeitado porque esse caminho de acesso é somente de leitura. A restrição se aplica ao acesso por esse ponteiro; a própria x foi definida sem const e ainda pode ser alterada por seu nome.Questão 2 de 10
Quais são os valores finais de a e b?
int a = 4, b = 13;
int *p = &a, *q = p;
*p += 3;
p = &b;
*q = *p - 2;
*p = *q + 5;
Mostrar respostaOcultar resposta
a ser igual a 7, e redirecionar p deixa q apontando para a. A atribuição seguinte define a como 11 usando o valor 13 de b, e a última escrita define b como 16. Manter a em 7 move incorretamente q quando apenas p foi redirecionado.Questão 3 de 10
int ocupa 4 bytes. Um arranjo int levels[7] começa no endereço ilustrativo 0x2400. Em qual endereço levels[4] começa?Mostrar respostaOcultar resposta
0x10, e o endereço é 0x2410. Somar apenas 4 ao endereço trataria o índice como uma contagem de bytes e alcançaria o segundo elemento.Questão 4 de 10
int items = 16;, qual declaração cria um ponteiro para items?Mostrar respostaOcultar resposta
&items obtém o endereço do inteiro, e int *p declara o tipo de ponteiro correspondente. Inicializar o ponteiro com items forneceria o inteiro armazenado 16 em vez do endereço de um objeto inteiro. Obter um endereço deixa o valor de items inalterado.Questão 5 de 10
Qual valor n contém após este código?
int n = 14;
int *p = &n;
*p = 23;
Mostrar respostaOcultar resposta
p contém o endereço de n, então *p designa esse mesmo inteiro. Atribuir 23 por meio de *p substitui o valor em n. Manter 14 confundiria uma escrita por meio do ponteiro com uma alteração do endereço armazenado no próprio ponteiro.Questão 6 de 10
Qual é o valor de score após o retorno de clear(score)?
void clear(int value) { value = 0; }
/* In the caller: */
int score = 27;
clear(score);
Mostrar respostaOcultar resposta
value como zero altera esse parâmetro, então score da função chamadora continua sendo 27. Para esperar zero, seria necessária uma função que recebesse &score por meio de um ponteiro para inteiro e escrevesse no objeto apontado.Questão 7 de 10
Em um sistema com ponteiros para objetos de 8 bytes, o que isto imprime?
char label[12] = "oak";
char *p = label;
printf("%zu %zu
", sizeof label, sizeof p);
Mostrar respostaOcultar resposta
label é um arranjo de doze caracteres, então sizeof label é 12, embora o texto armazenado tenha apenas três letras. p é um objeto ponteiro separado e ocupa 8 bytes nesse sistema. Contar o terminador fornece o prefixo ocupado pela cadeia de caracteres, não o tamanho declarado do arranjo.Questão 8 de 10
Quais são os valores de left e right após a chamada?
void exchange(int *a, int *b) {
int *t = a;
a = b;
b = t;
}
/* In the caller: */
int left = 11, right = 29;
exchange(&left, &right);
Mostrar respostaOcultar resposta
*a ou *b. Nenhum objeto inteiro é alterado, então left continua sendo 11 e right continua sendo 29. Esperar que os valores sejam trocados confunde mover valores de endereços locais com copiar os inteiros alcançados por esses endereços.Questão 9 de 10
int read_count(int *out);. Ela retorna 1 em caso de sucesso e escreve a contagem apenas nesse caso. Qual código da função chamadora imprime com segurança uma contagem recém-lida?Mostrar respostaOcultar resposta
n não inicializado quando a função falhasse sem escrever. Um ponteiro não inicializado não fornece um objeto de saída válido para a função preencher.Questão 10 de 10
Qual par a última linha imprime?
int *next_value(void) {
static int n = 20;
n += 3;
return &n;
}
/* In the caller: */
int *first = next_value();
int saved = *first;
int *second = next_value();
printf("%d %d
", saved, *first + *second);
Mostrar respostaOcultar resposta
saved. A segunda chamada altera o mesmo inteiro estático para 26, e ambos os ponteiros retornados o alcançam, então seus valores somam 52. O resultado 49 trata o primeiro ponteiro como um registro preservado do valor anterior, mas apenas saved contém um inteiro copiado e separado.Conjunto 2: Raciocine sobre intervalos de arranjos e cadeias de caracteres10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
O que total(data + 1, 3) retorna?
int total(const int *p, size_t n) {
int s = 0;
for (size_t i = 0; i < n; i++) s += p[i];
return s;
}
/* In the caller: */
int data[] = {9, 4, 7, 2, 8};
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Em um sistema com ponteiros de 8 bytes e valores int de 4 bytes, o que count(values) retorna?
size_t count(int a[9]) {
return sizeof a / sizeof a[0];
}
/* In the caller: */
int values[9] = {0};
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Quais são os valores de taken e a[0] depois?
int a[] = {8, 15, 22};
int *p = a;
int taken = *p++;
(*p)++;
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
puts(message + 5) imprime para const char *message = "blue sky";?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Qual é o valor de *(p - 2) após estas instruções?
int bins[] = {6, 12, 18, 24, 30, 36, 42};
int *p = bins + 1;
p += 4;
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
double values[8];, quanto vale &values[6] - &values[2]?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
Após este código, qual é o maior inteiro não negativo k para o qual p + k pode ser formado dentro do intervalo permitido para ponteiros?
int a[8];
int *p = a + 1, *end = a + 8;
p += (end - p) / 2;
end -= 2;
p += end - p;
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Uma cadeia de caracteres modificável terminada em nulo é invertida com esta preparação. Qual entrada revela um cálculo inválido de ponteiro antes do início do laço?
size_t n = strlen(s);
char *left = s;
char *right = s + n - 1;
while (left < right) {
char t = *left; *left++ = *right; *right-- = t;
}
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
int a[6] começa no endereço ilustrativo 0x3000, e cada inteiro ocupa 4 bytes. Quais endereços a + 1 e &a + 1 representam?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Quais são os valores de count e p - text após este laço?
char text[] = "abaca";
char *p = text;
int count = 0;
while (*p != '\0') {
if (*p++ == 'a') count++;
}
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 3: Acompanhe níveis de ponteiros e disposições de armazenamento10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
int n = 31; int *p = &n; int **pp = &p;, qual é o tipo de *pp?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Com ponteiros para objetos de 8 bytes, quanto vale sizeof words?
const char *words[] = {"red", "violet", "gold", "teal"};
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Quanto vale *p depois?
int a = 18, b = 25;
int *p = &a;
int **pp = &p;
*pp = &b;
**pp -= 4;
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
char *end; long n = strtol("57kg", &end, 10);, para onde end aponta após a conversão?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
./paint amber 6 na disposição usual de um ambiente hospedado. Quais são os valores de argc e argv[2][0]?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
int board[2][5] da função chamadora após sua conversão usual de arranjo?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
Quais são os valores de a, b e *p após a chamada?
void shift(int **slot, int *next) {
**slot += 2;
*slot = next;
**slot += 3;
}
/* In the caller: */
int a = 5, b = 12;
int *p = &a;
shift(&p, &b);
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
map[2][3] em relação a map[0][0] e qual é o tipo de map + 1, para int map[4][6] com inteiros de 4 bytes?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Quais são word e o índice de p depois?
char word[] = "abcd";
char *p = word;
char saved = *p++;
*p = saved;
p += 2;
*p = 'z';
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 4: Escolha operações e crie funções de retorno de chamada para ordenação10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
int (*filter)(int); declara?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
void visit(int *a, size_t n, void (*action)(int *));, e void mark(int *x); está disponível. Como a função chamadora fornece mark para um arranjo data de cinco elementos?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Qual valor é atribuído a result?
int difference(int a, int b) { return a - b; }
int product(int a, int b) { return a * b; }
int (*op)(int, int) = difference;
op = product;
int result = (*op)(7, 4);
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
int readings[8];, qual chamada ordena todos os elementos usando uma função de retorno de chamada compare compatível?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Quais são os valores de result e calls após este código?
int calls = 0;
int add_one(int x) { calls++; return x + 1; }
int twice(int x) { calls++; return x * 2; }
/* In a function: */
int (*ops[])(int) = {twice, add_one};
int result = ops[1](ops[0](5));
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
int representável em ordem crescente. Qual expressão de retorno atende a esse requisito após ler os elementos em x e y?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
const char *labels[], o que recupera a cadeia de caracteres passada como parâmetro const void *a da função de comparação antes de chamar strcmp?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
int (*ops[3])(int, int) tem funções válidas nas posições 0 e 2 e NULL na posição 1. Para um inteiro com sinal choice, qual condição permite ops[choice](8, 2) com segurança?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
{4, -4, 2, -2} com qsort. Qual resultado é garantido?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Uma lista é ordenada por comprimento crescente das cadeias de caracteres usando este corpo de função de retorno de chamada. Qual é o defeito?
const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
size_t nx = strlen(x), ny = strlen(y);
return nx < ny;
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Travou em uma questão sua? Ajuda com Tarefas
Termos-Chave
A
Ajuste de parâmetro de arranjo para ponteiro
int a[10] é um ponteiro dentro da função, então sizeof a mede esse ponteiro.Alinhamento
argc
argc é a contagem não negativa de argumentos passada à main em ambiente hospedado, contando as cadeias fornecidas por argv, incluindo o nome do programa quando presente.argv[2], teste se argc > 2 para garantir que a cadeia de argumento solicitada existe.argv
argv é a lista de ponteiros para caracteres passada à main em ambiente hospedado, com um ponteiro nulo no índice argc e cadeias de argumentos do usuário a partir do índice 1../sum 8 13, argv[2] alcança "13"; o argumento é texto antes da conversão numérica.Aritmética de ponteiros
sizeof.Armazenamento por linhas
int a[3][4], somar 1 ao a convertido avança uma linha completa de quatro inteiros, não um inteiro.Arranjo de ponteiros
Assinatura de função
B
Byte
sizeof; um char em C ocupa sempre um byte, embora os tamanhos de outros tipos dependam do sistema.C
Caractere nulo
'\0', que termina uma cadeia de caracteres em C dentro de seu armazenamento acessível.Comportamento indefinido
Conversão de arranjo em ponteiro
int *p = values; inicializa um ponteiro para values[0]; todos os elementos continuam pertencendo ao arranjo original.D
Declarador de ponteiro
* com um nome declarado individual para indicar que o objeto nomeado tem tipo de ponteiro.int *left, right;, apenas left é um ponteiro; declarar right como ponteiro exige outro asterisco.Desreferenciação
* unário a um ponteiro para designar o objeto apontado, permitindo uma leitura ou escrita adequada por esse caminho.int *p = &score;, a atribuição *p = 28; altera o mesmo inteiro lido posteriormente por meio de score.Diferença entre ponteiros
&a[5] - &a[2] resulta em 3 elementos, independentemente de quantos bytes um elemento ocupa nesse sistema.Duração de armazenamento
Duração de armazenamento automática
Duração de armazenamento estática
E
Endereço de memória
0x2000, seus bytes restantes seguem até 0x2003.Estouro de buffer
Estouro de inteiro com sinal
x - y pode causar estouro mesmo quando cada entrada separadamente cabe em um int.F
Falha de segmentação
Fatia de arranjo
data + 2 com uma contagem de 3 seleciona os índices originais de 2 a 4 sem copiá-los.Função de comparação
(x > y) - (x < y) ordena inteiros em ordem crescente sem subtrair os próprios valores.Função de retorno de chamada
I
Incremento pós-fixado
*p++.int x = *p++; lê o elemento na posição anterior e deixa p apontando para a posição seguinte.L
Limites do arranjo
a - 1 para um ponteiro no início de um arranjo é inválido, mesmo que o programa nunca leia por meio dele.Literal de cadeia de caracteres
const char *p = "oak"; expõe o literal por um caminho somente de leitura, enquanto char a[] = "oak"; cria uma cópia modificável.M
Matriz bidimensional
int grid[2][5] é convertido em int (*)[5]; passá-lo como int ** descreveria uma disposição diferente.Matriz irregular
"red" e "violet" sem reservar linhas de caracteres de mesmo tamanho para as duas palavras.N
Nível de indireção
int **pp, a primeira desreferenciação fornece um int *, e a segunda alcança um objeto inteiro.O
Operador de endereço
& obtém o endereço de um objeto endereçável ou de uma função, produzindo um tipo de ponteiro correspondente.&values[2] fornece o endereço do terceiro elemento sem alterar seu conteúdo.Operador de indexação
a[i] como *(a + i), usando o tipo de elemento do ponteiro para aplicar a escala à posição.p aponta para o elemento original 2, então p[1] acessa o elemento original 3 do mesmo arranjo.Ordem dos bytes
0x12345678 de quatro bytes tem os bytes 78 56 34 12 em ordem crescente de endereços em um sistema que armazena o byte menos significativo primeiro.Ordenação estável
qsort não oferece essa garantia.P
Parâmetro de saída
Parâmetro ponteiro
&score por meio de int *out e escreve zero no inteiro da função chamadora usando *out.Passagem por referência
&x e &y altera variáveis da função chamadora ao escrever por meio dos dois parâmetros ponteiro locais.Passagem por valor
int deixa o inteiro da função chamadora inalterado; escrever por meio de um endereço copiado pode alterá-lo.Ponteiro
int *p = &count;, a variável ponteiro armazena um endereço enquanto o inteiro mantém seu próprio valor.Ponteiro constante
int *const p = &x;, *p = 3; é permitido, mas p = &y; não pode redirecionar o ponteiro.Ponteiro de fim da conversão
"73ms" com strtol pode retornar 73 e definir o ponteiro de fim da função chamadora para o m.Ponteiro genérico para objeto
void *, ou uma forma qualificada, para conter um endereço de objeto que deve ser convertido para um tipo adequado antes do acesso.const void * em const int * antes de ler o elemento do arranjo endereçado.Ponteiro nulo
NULL quando não encontra correspondência, então a função chamadora testa o resultado antes de desreferenciá-lo.Ponteiro para a posição imediatamente após o último elemento
int a[5], um percurso pode parar quando seu ponteiro atual for igual a a + 5, antes de acessar outro elemento.Ponteiro para arranjo
int a[4], &a tem tipo int (*)[4]; seu passo aritmético difere do passo de &a[0].Ponteiro para dados constantes
const int *p = &x; permite p = &y;, enquanto uma atribuição como *p = 3; é rejeitada.Ponteiro para função
int (*op)(int, int) = add; armazena uma função de adição que pode ser chamada posteriormente como op(4, 7).Ponteiro para ponteiro
int **pp = &p;, a atribuição *pp = &y; redireciona p, enquanto **pp = 8; altera o objeto apontado por ele.Ponteiro pendente
Precedência de operadores
++ pós-fixado tem precedência maior que o * unário, então *p++ é agrupado como *(p++), não como (*p)++.ptrdiff_t
ptrdiff_t é o tipo inteiro com sinal usado para subtração de ponteiros; uma subtração definida deve ter resultado representável nesse tipo.<stddef.h> para ptrdiff_t e use %td para imprimir uma diferença entre posições válidas no mesmo arranjo.Q
qsort
qsort é a função da biblioteca padrão que rearranja um arranjo usando sua contagem de elementos, o tamanho de cada elemento e uma função de comparação fornecida.sizeof names[0] para o tamanho da posição, não a contagem de caracteres de uma cadeia.R
Referência ao mesmo objeto por caminhos distintos
int *q = p;, ambos os ponteiros alcançam o mesmo inteiro até que uma atribuição posterior redirecione um deles.Registro de ativação
S
sizeof
sizeof informa o tamanho de armazenamento de um tipo ou objeto em bytes e retorna um valor do tipo inteiro sem sinal size_t.char code[10] = "go";, sizeof code informa 10, embora o texto tenha apenas dois caracteres.strtol
strtol é a função da biblioteca padrão que converte o texto numérico inicial em long e pode informar sua posição de parada por meio de char **.&end permite que strtol substitua o ponteiro para caractere da função chamadora pelo endereço do primeiro caractere não usado.T
Tabela de despacho
typedef
typedef introduz um nome para um tipo existente, facilitando a reutilização de um tipo complexo de ponteiro para função em declarações.typedef int (*operation)(int);, operation chosen; declara um ponteiro para função, não um corpo de função.V
Valor indeterminado de ponteiro
int *p; sozinha não permite que o programa teste, siga ou escreva por meio desse ponteiro com segurança.Sobre esta aula
Crie seus próprios Materiais de Estudo
Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.