paulo1205
(usa Ubuntu)
Enviado em 03/02/2022 - 22:44h
ApprenticeX escreveu:
O que entendi aqui é que então todo ponteiro declarado da forma abaixo são variáveis constantes certo?
Não é isso, não. E você tem de saber a diferença entre ponteiros para dados que são constantes e variáveis ponteiros que podem ser elas próprias constantes mas podem apontar para dados que não são constantes.
Compare as declarações abaixo.
int *pi; // Ponteiro (não-constante) para dado inteiro (não-constante).
const int *pci; // Ponteiro (não-constante) para dado inteiro constante.
int const *pci2; // Outra forma de fazer ponteiro (não-constante) para dado inteiro constante (i.e. “int const” é sinônimo de “const int”).
typedef unsigned char byte;
// Com ponteiros constantes, convém definir seu valor (para onde aponta) no momento da declaração, pois depois não será possível alterá-lo.
byte *const cp_video_mem=(byte *)0x000b8000; // Ponteiro constante para (região de) dados do tipo byte (não-constantes) (0x000b8000 até 0x000bffff é a região de memória de vídeo para texto e modos gráficos de baixa resolução). Posso ler e alterar o conteúdo da memória de vídeo, pois apesar de o ponteiro ser constante, o dado apontado não o é.
const byte *const cp_mb_bios=(byte *)0x000f0000; // Ponteiro constante para (região de) dados constantes do tipo byte (0x000f0000 até 0x000fffff é (era) a região do BIOS da placa-mãe). Posso usar cp_mb_bios para examinar o conteúdo do BIOS, mas não para modificá-lo, porque aqui eu disse que os dados são constantes.
Isso porque declarei elas como Ponteiro! Constantes porque foram alocadas em um endereço de memória e como o Ponteiro apenas aponta para um endereço, eu NUNCA poderei mudar os valores registrados e alocados na memória, o que faço apenas é apontar o Ponteiro para outro local sempre!
const int *Numbers[] = { 3, 0, 1, 8, 7, 2, 5, 4, 9, 6 }; // Constante pq declarei como um ponteiro. Certo?
const *int Number = 10; // Constante pq declarei como um ponteiro. Certo?
const char *Nome; // Constante pq declarei como um ponteiro. Certo?
As duas primeiras declarações estão erradas:
• você declarou
Numbers como um
array de ponteiros para inteiros constantes, mas tentou atribuir valores inteiros diretamente a cada elemento do
array , sendo que tais elementos são ponteiros (talvez algum compilador mais antigo ou com opções de compilação que o deixem mais tolerante a tipos incompatíveis deixe isso passar, providenciando a conversão implícita de cada valor inteiro na lista de inicialização para um valor absoluto de endereço correspondente, mas eu não creio que era isso que você queria que acontecesse);
• a declaração de
Number está com um erro de sintaxe (“
const *int ” não é uma construção permitida, e eu não sei se você quis dizer “
const int *Number ”, para declarar um ponteiro para valor inteiro constante, ou “
int *const Number ”, para declarar um ponteiro constante para valor inteiro (não-constante), mas, de um jeito ou de outro, a atribuição a esse ponteiro do valor inteiro
10 seria errônea.
Declarar alguma coisa como ponteiro não implica, por si só, nem que o ponteiro será constante nem que o objeto apontado será constante, então os comentários que acompanham cada declaração acima podem não fazer muito sentido.
Recapitulando, e quiçá esclarecendo, o que já foi dito acima e e em outros tópicos:
• Um ponteiro para dados constantes significa que o objeto referenciado não poderá ter seu valor alterado quando o acesso for feito por meio desse ponteiro, quer seja tal ponteiro contido numa variável ponteiro, quer seja obtido por avaliação de alguma expressão.
• Como caso particular, é perfeitamente válido ter um ponteiro para dados constantes apontando para um objeto que inicialmente não é constante, e isso significa, como dito explicitamente acima, que esse ponteiro, em particular, não poderá ser usado para modificar o objeto original.
• O contrário, isto é, um ponteiro para dados não-constantes referenciando um objeto que é constante, é que normalmente indica algum erro cometido no projeto ou na codificação do programa, ou pode induzir a erros de execução.
• Essa observação continua válida mesmo quando esse uso é tolerado pela linguagem, como é o caso particular, em C (mas não em C++), de ponteiros para caracteres (não-constantes) podendo receber valores provenientes de constantes literais
strings entre aspas.
/* 01 */ #include <stdio.h>
/* 02 */
/* 03 */ const char vowels[]={'a', 'e', 'i', 'o', 'u'}; // Array de 5 elementos explicitamente definidos como constantes (e que não é string, porque falta o byte nulo).
/* 04 */ char *p1=vowels; // Tipo de ponteiro incompatível com objeto apontado (ponteiro que permite acesso de modificação do dado, mas objeto apontado é constante).
/* 05 */ // Dependendo das opções de compilação, esse tipo incompatível pode ser considerada como erro (interrompendo a compilação), ou pode ser
/* 06 */ // apenas exibido um alerta de incompatibilidade, e a compilação pode prosseguir. Recomendo sempre parar a compilação nesses casos.
/* 07 */
/* 08 */ const char vowels_str[]="aeiou"; // Array de 6 elementos constantes (tanto os cinco do texto quanto o byte nulo).
/* 09 */ char *p2=vowels_str; // Tipo de ponteiro incompatível com objeto apontado. Mesmos comentários feitos sobre p1, acima, aplicam-se aqui.
/* 10 */
/* 11 */ const char vowels_str2[]="aeiou"; // Outro array, distinto do primeiro, embora o conteúdo seja idêntico ao daquele, de 6 elementos constantes.
/* 12 */
/* 13 */ // NOTA (e corolário para guardar): Arrays explicitamente declarados como tais ocupam regiões de memória distintas de outros arrays.
/* 14 */
/* 15 */ char *p3="aeiou"; // Constante literal produz array anônimo com seis elementos constantes, distinto de todos os arrays acima. A atribuição array de elementos
/* 16 */ // constantres ao ponteiro para dado não-constante é válida em C, porém perigosa.
/* 17 */ const char *p4="aeiou"; // O compilador pode (e normalmente vai) reaproveitar o mesmo array anônimo para essas duas constantes literais idênticas, de modo que
/* 18 */ // p3 e p4 podem apontar para o mesmo array anônimo (mesmo valor de ponteiro em duas variáveis ponteiro diferentes).
/* 19 */
/* 20 */ // Todos os arrays acima são tipicamente dispostos numa região de memória que tem o atributo de somente leitura (read-only): os
/* 21 */ // explicitamente declarados porque usaram o atributo const para seus elementos, e os decorrentes das constantes literais string
/* 22 */ // porque — oh! surpresa! — são literalmente constantes. É por esse motivo que não é seguro usar um ponteiro que não especifica
/* 23 */ // que seus dados são constantes para referir-se a algo que é constante: alguém (incluindo o compilador) poderia achar que o fato
/* 24 */ // de o ponteiro não restringir a modificação do dado apontado implica que o dado necessariamente pode ser modificado.
/* 25 */
/* 26 */ char some_chars[]="aeiou"; // Array com elementos que não são constantes. Os elementos serão dispostos numa região de memória específica para este novo array e, ao
/* 27 */ // contrário dos arrays acima, será colocado numa região de memória que não tem o atributo de apenas leitura.
/* 28 */ char *p5=some_chars; // Aponto com um ponteiro que permite acesso indireto de modificação, o que é OK, neste caso, pois o dado apontado realmente é modificável.
/* 29 */ const char *p6=some_chars; // Aponto com um ponteiro que permite acesso indireto de consulta, mas não de modificação. Isso também é OK: o atributo const, aplicado ao
/* 30 */ // dado apontado, não especifica a natureza do dado apontado, mas sim de que modo o ponteiro pode ser usado para obter acesso ao conteúdo.
/* 31 */
/* 32 */ const char *f(void){
/* 33 */ static const char static_vowels[]="aeiou"; // Dados locais estáticos, assim como variáveis globais, têm seus valores dispostos em memória durante a compilação, e não redefinidos cada
/* 34 */ static char static_chars[]="aeiou"; // vez que a função é invocada (são “locais” apenas no sentido de seu escopo de visibilidade, não em termos de tempo ou fluxo de vida).
/* 35 */
/* 36 */ const char local_vowels[]="aeiou"; // Dados locais automáticos (não-estáticos) têm seus valores iniciais atribuídos cada vez que a função é invocada. Nestas duas linhas,
/* 37 */ char local_chars[]="aeiou"; // “"abcde"” é uma constante literal string, que pode produzir um array anônimo com elementos constantes (podendo até ser o mesmo array anônimo
/* 38 */ // gerado na linha 15 e reaproveitado na linha 17), e esses elementos são copiados para os arrays locais automáticos cada vez que o fluxo
/* 39 */ // de execução passa pelas linhas 36 e 37. A memória de dados automáticos normalmente não possui o atributo de somente leitura, o que
/* 40 */ // significa que garantir que o atributo const na linha 34 será honrado por todos os ponteiros que vierem a apontar para o objeto será
/* 41 */ // responsabilidade exclusivamente do compilador (mas é bom o programador ajudar, não tentando abusar do sistema de tipos).
/* 42 */
/* 43 */ char *lp1=static_vowels; // ERRO/ALERTA: Tipo de ponteiro incompatível com objeto apontado (ponteiro sugere que dado pode ser modificado, mas objeto apontado é constante).
/* 44 */ char *lp2=static_chars; // OK: ponteiro que permite acesso de modificação apontando para dado modificável.
/* 45 */ char *lp3=local_vowels; // ERRO/ALERTA: Tipo de ponteiro incompatível com objeto apontado (ponteiro sugere que dado pode ser modificado, mas objeto apontado é constante).
/* 46 */ char *lp4=local_chars; // OK: ponteiro que permite acesso de modificação apontando para dado modificável.
/* 47 */
/* 48 */ const char *lp5=static_vowels; // OK: ponteiro que não permite acesso de modificação apontando para dado que não permite modificação.
/* 49 */ const char *lp6=static_chars; // OK: ponteiro que não permite acesso de modificação apontando para dado que permite modificação.
/* 50 */ const char *lp7=local_vowels; // OK: ponteiro que não permite acesso de modificação apontando para dado que não permite modificação.
/* 51 */ const char *lp8=local_chars; // OK: ponteiro que não permite acesso de modificação apontando para dado que permite modificação.
/* 52 */
/* 53 */ printf("%s, %s, %s, %s\n", lp1, lp2, lp3, lp4);
/* 54 */
/* 55 */ ++*lp1; // Provavelmente isto aqui vai fazer o programa capotar com falha de segmentação (SIGSEGV) — o ponteiro permite o acesso de modificação, mas o
/* 56 */ // dado apontado reside numa região de memória marcada com acesso somente de leitura.
/* 57 */ ++*lp2; // OK.
/* 58 */ ++*lp3; // Comportamento indeterminado (se a compilação não tiver parado na linha 45 por causa do erro de incompatibilidade de tipos, pode ser que a
/* 59 */ // CPU não tenha como verificar, em tempo de execução, que este array disposto na memória de dados automáticos não deveria ser modificado).
/* 60 */ ++*lp4; // OK.
/* 61 */ //++*lp5; ++*lp6; ++*lp7; ++*lp8; // Linha comentada porque o compilador não vai permitir nenhuma dessas operações que tentam usar um ponteiro para dado constante para modificar
/* 62 */ // o dado apontado (e isso ocorre mesmo que você não use a opção de compilação que transforma alertas em erros).
/* 63 */
/* 64 */ printf("%s, %s, %s, %s\n", lp5, lp6, lp7, lp8);
/* 65 */
/* 66 */ return lp2;
/* 67 */ }
/* 68 */
/* 69 */ int main(void){
/* 70 */ printf("vowels: addr=%p, size=%zu, contents=\"%.5s\"; p1: addr=%p, value=%p, contents=\"%.5s\"\n", vowels, sizeof vowels, vowels, &p1, p1, p1);
/* 71 */ ++*p1;
/* 72 */ printf("vowels: addr=%p, size=%zu, contents=\"%.5s\"; p1: addr=%p, value=%p, contents=\"%.5s\"\n\n", vowels, sizeof vowels, vowels, &p1, p1, p1);
/* 73 */
/* 74 */ printf("vowels_str: addr=%p, size=%zu, contents=\"%s\"; p2: addr=%p, value=%p, contents=\"%s\"\n", vowels_str, sizeof vowels_str, vowels_str, &p2, p2, p2);
/* 75 */ ++*p2;
/* 76 */ printf("vowels_str: addr=%p, size=%zu, contents=\"%s\"; p2: addr=%p, value=%p, contents=\"%s\"\n\n", vowels_str, sizeof vowels_str, vowels_str, &p2, p2, p2);
/* 77 */
/* 78 */ printf("vowels_str2: addr=%p, size=%zu, contents=\"%s\"\n\n", vowels_str2, sizeof vowels_str2, vowels_str2);
/* 79 */
/* 80 */ printf("p3: addr=%p, value=%p, contents=\"%s\"; p4: addr=%p, value=%p, contents=\"%s\"\n", &p3, p3, p3, &p4, p4, p4);
/* 81 */ ++*p3;
/* 82 */ printf("p3: addr=%p, value=%p, contents=\"%s\"; p4: addr=%p, value=%p, contents=\"%s\"\n\n", &p3, p3, p3, &p4, p4, p4);
/* 83 */
/* 84 */ printf("some_chars: addr=%p, size=%zu, contents=\"%s\"; p5: addr=%p, value=%p, contents=\"%s\"; p6: addr=%p, value=%p, contents=\"%s\"\n", some_chars, sizeof some_chars, some_chars, &p5, p5, p5, &p6, p6, p6);
/* 85 */ ++*p5;
/* 86 */ printf("some_chars: addr=%p, size=%zu, contents=\"%s\"; p5: addr=%p, value=%p, contents=\"%s\"; p6: addr=%p, value=%p, contents=\"%s\"\n\n", some_chars, sizeof some_chars, some_chars, &p5, p5, p5, &p6, p6, p6);
/* 87 */
/* 88 */ printf("%s\n", f());
/* 89 */ printf("%s\n", f());
/* 90 */ }
/*
As linhas 04, 09, 43 e 45 produzem mensagens de alerta (ou erro — eu recomendo usar as opções que fazem com que dê erro!).
Sem forçar a compilação sem que os abusos provoquem erro, mas apenas alertas, memso assim, as linhas 55, 71, 75 e 81 têm de
ser comentadas. Caso contrário, o programa vai capotar.
A linha 50 representa uma violação que fica latente, embora seja semanticamente parecida com a da linha 48, mas o programa
não capota na linha 58, como o faz na linha 55 se não estiver comentada, porque o hardware do PC não consegue marcar a memória
automática onde local_vowels é colocada como read-only.
A saída do programa, quando compilado ingorando os alertas e comentando as linhas indicadas acima, está abaixo. Note como,
ao se chamar f(), uma das strings que é declarada como constante (local_vowels) acaba sendo alterada por conta de um ponteiro
para dados não constantes para apontar para ela.
vowels: addr=0x5621b350abe8, size=5, contents="aeiou"; p1: addr=0x5621b370c020, value=0x5621b350abe8, contents="aeiou"
vowels: addr=0x5621b350abe8, size=5, contents="aeiou"; p1: addr=0x5621b370c020, value=0x5621b350abe8, contents="aeiou"
vowels_str: addr=0x5621b350abed, size=6, contents="aeiou"; p2: addr=0x5621b370c028, value=0x5621b350abed, contents="aeiou"
vowels_str: addr=0x5621b350abed, size=6, contents="aeiou"; p2: addr=0x5621b370c028, value=0x5621b350abed, contents="aeiou"
vowels_str2: addr=0x5621b350abf3, size=6, contents="aeiou"
p3: addr=0x5621b370c030, value=0x5621b350abf9, contents="aeiou"; p4: addr=0x5621b370c038, value=0x5621b350abf9, contents="aeiou"
p3: addr=0x5621b370c030, value=0x5621b350abf9, contents="aeiou"; p4: addr=0x5621b370c038, value=0x5621b350abf9, contents="aeiou"
some_chars: addr=0x5621b370c010, size=6, contents="aeiou"; p5: addr=0x5621b370c040, value=0x5621b370c010, contents="aeiou"; p6: addr=0x5621b370c048, value=0x5621b370c010, contents="aeiou"
some_chars: addr=0x5621b370c010, size=6, contents="beiou"; p5: addr=0x5621b370c040, value=0x5621b370c010, contents="beiou"; p6: addr=0x5621b370c048, value=0x5621b370c010, contents="beiou"
aeiou, aeiou, aeiou, aeiou
aeiou, beiou, beiou, beiou
beiou
aeiou, beiou, aeiou, aeiou
aeiou, ceiou, beiou, beiou
ceiou
*/
• Um ponteiro constante significa um valor constante para o endereço que ele indica, o que não necessariamente significa que o dado contido nesse endereço será constante: o tipo do dado apontado só vai ser considerado constante se você disser que ele o é.
char some_chars[]="abcde";
const char vowels[]="aeiou";
void f(void){
// Todas as atribuições dos ponteiros abixo são válidas.
char *const p1=some_chars;
char *p2=some_chars;
const char *p3=some_chars;
const char *const p4=some_chars;
++some_chars; // ERRO: neste contexto, some_chars decai para um ponteiro constante para o primeiro elemento; sendo constante, não pode ser alterado.
++*some_chars; // OK: posso alterar o elemento referido pelo ponteiro constante, pois o tipo do elemento não é constante.
++p1; // ERRO: o ponteiro é constante, logo não pode ser alterado.
++*p1; // OK: dado apontado não é constante.
++p2; // OK: ponteiro não-constante passa a apontar para o segundo elemento do array.
++*p2; // OK: altera o segundo elemento do array (dado apontado não é constante).
++p3; // OK: ponteiro não-constante passa a apontar para o segundo elemento do array.
++*p3; // ERRO: dado apontado é inidicado como constante, logo não pode ser alterado.
++p4; // ERRO: o ponteiro é constante, logo não pode ser alterado.
++*p4; // ERRO: o dado apontado é indicado como constante, logo não pode ser alterado.
}
void g(void){
// Não vou nem tentar usar ponteiros para dados não-constantes, que seria uma violação de tipo (ver programa exemplo mostrado no exemplo anterior).
const char *p1=vowels;
const char *const p2=vowels;
++vowels; // ERRO: Neste contexto, vowels decais para um ponteiro constante para o primeiro elemento; sendo constante, não pode ser alterado.
++*vowels; // ERRO: não posso alterar o elemento referido pelo ponteiro constante porque o tipo do elemento também é constante.
++p1; // OK: ponteiro não-constante passa a apontar para o segundo elemento do array.
++*p1; // ERRO: dado apontado é indicado como constante, logo não pode ser alterado.
++p2; // ERRO: o ponteiro é constante, logo não pode ser alterado.
++*p2; // ERRO: dado apontado é indicado como constante, logo não pode ser alterado.
}
paulo1205 escreveu: Ponteiros funcionam como referências ou indireções, isto é, formas indiretas de chegar a um dado ou de referir-se a eles.
Como acessos indiretos frequentemente são mais lentos do que acessos diretos, provavelmente não convém chamar de atalho.
Então entendi que em muitos casos, não é o ideal usar ponteiros, o ideal mesmo então seria ter um tipo declarado e referenciado como é o caso das variáveis normais.
Como praticamente todos os recursos de uma linguagem de programação, você deve dar preferência a uma determinada funcionalidade quando ela for a melhor (ou, às vezes, a única) maneira de fazer aquilo que você precisa que seja feito.
Esses exemplos toscos em que se uma uma variável ponteiro para referir-se a uma variável escalar que foi declara numa linha logo acima ou num contexto muito semelhante não são a principal razão de ser de ponteiros. Eis os principais motivos pelos quais ponteiros são muito usados em C:
• Para evitar cópias de dados, sobretudo quando os dados envolvidos são muito grandes.
• Isso é particularmente verdadeiro quando você tem dados que são
arrays ou estruturas com vários campos e você quer passar tais dados como argumentos para funções. Em lugar de copiar dezenas, centenas ou mesmo milhares de bytes para cada argumento de um tipo composto ao chamar a função, você copia apenas o endereço de onde esses dados residem, e a função usa o endereço recebido para ter acesso aos dados que nele estão armazenados.
• Também no momento de retornar dados que ocupam muitos bytes, pode ser melhor retornar um ponteiro para uma estrutura,
array ou bloco de memória do que fazer uma cópia de um centenas ou milhares de bytes de dentro da função para o destino que reside fora dela.
• Porque não existe cópia direta de
arrays em C.
• Isso implica que sempre que um
array tiver de ser passado como argumento para uma função, não existe alternativa ao uso de ponteiros para receber esse parâmetro. Mesmo que você declare um parâmetro de função com aspecto de
array , até mesmo colocando um valor numéro entre colchetes para especificar a suporta quantidade de elementos). o compilador silenciosamente vai converter aquele parâmetro em ponteiro, e vai converter também o argumento que você usar quando invocar a função.
O que me fez pensar neste caso específico em C++, pois todas as janelas e objetos, usam ponteiros para acessá-las ao menos parece ser o padrão, mas também no caso da FLTK que estou estudando e vi exemplos aqui no VOL, ela pode usar as janelas sem serem ponteiros.
O C++ tem um recurso chamado referências, que não existe em C, e que são uma forma disfarçada de ponteiro na qual só é possível apontar objetos válidos, correspondentes a alguma variável. Quando você chama uma função cujos parâmetros são referências, a forma escrever os argumentos pode dar a entender que você os está passando por (cópia de) valor, mas o compilador automaticamente obtém os endereços dos argumentos e passa tais endereços.
Eu não conheço o FLTK, mas pode ser que esses objetos que você diz que não são ponteiros sejam justamente referências.
O que me fez pensar também em funções onde em seu cabeçalho muitas vezes usamos ponteiros, então seria melhor se possível não usar ponteiros e usar variáveis normais no lugar, isso seria o mais ideal, tipo:
void funcao(int vector[]) // Melhor assim
void funcao(int *vector) // Do que assim?
Ambos são absolutamente sinônimos e implicam que o parâmetro é um ponteiro para
int .
Há quem prefira usar a sintaxe com colchetes quando semanticamente fizer mais sentido chamar com um objeto que seja um
array , e com asterisco num caso mais geral, em que a relação dos eventuais argumentos com algum
array declarado como tal não for garantida ou garantidamente não existir. Mas isso é mera convenção, e nem tudo mundo usa (eu mesmo não uso: sempre uso apenas a sintaxe com asteriscos).
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)