Ir para conteúdo

Ponteiros em C


Eduardo Bittencourt

Posts Recomendados

                                                                                                                                                                      Ponteiros em C

 

Olá, vou fazer um breve resumo do que eu aprendi até agora sobre ponteiros,  lembrando que eu não fiz nenhuma formatação,

e nem vou me aprofundar neste assunto, até mesmo porque tenho muito o que treinar ainda,  se alguém quiser incrementar algo é só falar.

 

1->Todo ponteiro, pode aponta para algum lugar, e guardar o endereço de memória do dado apontado. O ponteiro  tem o tamanho de um inteiro, e ao apontar para um dado, tem o tamanho deste dado. Vale ressaltar, que o tamanho pode variar de acordo o sistema operacional ou arquitetura.

 

#include <stdio.h>
  
  int main(void) {
  
       char *ptr;
  
       printf("%d", sizeof(ptr)); //tamanho de um unsigned inteiro
  
       printf("%d", sizeof(*ptr));  //tamanho de um char, porque neste caso ptr está apontando para este tipo de dado
  
       return 0;
  
  }

 

2-Pode se dizer que todo array, faz referência a um ponteiro e vice versa. Internamente o compilador considera, que todo nome de um array é um ponteiro para o primeiro índice deste array,

e seu tamanho é fixo e linear na memória, que é incrementado em bytes de acordo com o seu tipo.

#include <stdio.h> 
#include <string.h>
  
int main(void) {
       
      char nome[] = {'t','e','s','t','e','\0'};

      int i;

      for(i=0; i < strlen(nome); i++) 
         printf("Letra: %c Endereço de memória: %p\n", nome[i], nome[i]); // percorre todo o array e retorna o valor que está em cada posição, e o endereço de mem´´oria
                                                                                                                                                 // note que só incrementa de 1 byte na memória de forma linear
        return 0;
 }
  

3-Um ponteiro pode apontar diretamente para um array, sem precisar usar o operador de referência &, isso ocorre porque todo nome de um array é um ponteiro, e a primeira posição tem o mesmo endereço de memória do nome da variável, obs: Só tem o mesmo endereço de memória para arrays, e não para ponteiros.

 

¨#include <stdio.h> 
  
int main(void) {
  
     char *ptr = "Ponteiro"; // Isso aqui é uma constante estática, o compilador coloca essa variável na seção .rodata, que apenas permite leitura, por isso é uma constante
  
     //Obviamente todo array começa no 1 indice, ou seja, para temos todos dados ou lista, como quiser chamar, é necessaria informar apenas o 1 indice
  
     printf("%x", &ptr); // Exibe o endereço de memória do ponteiro em hexadecimal
     printf("%p", ptr); //Exibe o  endereço de memória para onde o ponteiro aponta
  
    //Nota: O correto seria usar a conversão de formato %p, e não %x. para obter o endereço de memória de um ponteiro. Porque independente de arquitetura vai mostrar de maneira adequada
  
    char string[] = "Isso é um array"; //Array de chars, o compilador já faz o trabalho de contar os caracteres, e colocar nullbyte no final
  
    printf("%x", &string); // Endereço de memória de string em hexadecimal
    printf("%p", string);  // Endereço de memória para onde string aponta, justamente o mesmo endereço do nome do array
  
    ptr = string; // não preciso usar o operador ¨&, porque o nome string é um ponteiro para &string[0];
   
    /*ptr é um atalho para &ptr[0], quando usamos a função printf, ou demais funções, o compilador está na verdade lendo o endereço de memória da variável, isso ocorre quando a váriavel  é um array*/
  
    printf("%s", &ptr[0]);
  
   return 0;
 
} 

4-Operadores:

  •  *->Dereferência 
  • ¨&->Referência
  • Atalho->*ptr == ptr[0] ambos são a mesma coisa
  • O operador & comercial, retorna o endereço de memória de uma variável, ou até mesma uma função
  • O operador * asterisco, retorna o conteúdo para onde o ponteiro está apontando

 

#include <stdio.h>
#include <stdlib.h>

 /*A função func, nada mais é do que uma tentativa de explicar
    que não podemos modificar o valor que está no endereço da variável
    numero[1]
    
    Porque não apontamos para o endereço de memória, e sim
    colocamos o valor que está em numero[1], em x[0]*/
    
  
void func(void) {
   
       int *x = malloc(sizeof(int)); // aloco 4 bytes do tipo inteiro
  
       int numero[5] = {1,2,3,4,5};
  
       *x = numero[1]; 
                                          
       printf("Soma do valor do ptr: %d\n", *x+5);
  
       numero[1] = 10;
  
       printf("%d\n", *x);
                                             
       free(x);
      
} 
  

int main(void) {
  
     int *x ; 
  
     int numero[5] = {1,2,3,4,5};
       
     x = &numero[1]; // recebe o endereço de memória de numero[1] , vou ter todo o conteúdo a partir do elemento numero[1] em diante
    
     printf("%d\n", *x);  /*Ponteiro inicialmente sempre aponta para o começo de um dado, 
                                                neste caso retorna o contéudo para onde o ponteiro está apontando */
 
     printf("%d\n", *++x); // Incremento, e depois exibe o número 3
     numero[1] = 10;
  
     printf("%d\n", *(x-1));  /*Estou decrementando 1 na posição, equivalente ao decremento de 4 bytes, não é recomendado usar valores negativos, porque
                                                       pode trazer problemas de segurança e warnings na compilação.*/
  
    func(); 
  
    return 0;
} 

5-Ao criamos um ponteiro, sempre é bom inicializa-lo, pois o ponteiro pode apontar para um endereço de memória inválida ou sem permissão de leitura, causando lixo de memória,  e encerrando com o sinal SIGSEGV, para isso não ocorrer, podemos inicializamos com um valor, especificamente 0, que limpa.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
  
int main() {

      int *ptr = 0; //  Ponteiro aponta para 0, logo temos uma area de memória válida
     
      char *pointer = NULL; // Aponta para a constante null
  
                                                // As duas declarações fazem a mesma coisa, aponta para o 0
  
      char *ptr;
  
     char nome[256];
  
    memset(nome, 0, sizeof(nome));  / * Escreve na memória o valor 0, para a variável nome, simplificando, inicializei todos os dados da variável nome
                                                                            que é um array*/

} 

6-A função memset escreve no endereço de memória um valor, serve para inicializar uma array, ponteiro ou estrutura, enfim. Por isso que é muito utilizado para limpar um buffer, porque pode simplesmente inicializar todos os elementos do array com 0.

 

Parte corrigida graças ao Mercês, no programa abaixo utilizo a função memset pra escrever o byte x no endereço de memória, fiz isso para demonstrar que podemos usar a função memset pra escrever um byte em memória, por em não é a melhor forma, a não ser, se quisermos inicializar um array, nos comentários o Mercês explica a maneira correta

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
	char *ptr = malloc(11);  // Aloca 11 bytes, 10 bytes para a string, e 1 byte a mais para o nullbyte

	strcpy(ptr, "aaaaaaaaab");
	char *aux = ptr;

	while (*ptr != 'b') { // percorre o loop até achar a letra b
		memset(ptr, 'x', sizeof(char)); // escreve o byte 'x' no endereço de memória apontado por ptr (a escrita rola sizeof(char) vezes - no caso aqui é 1)
		ptr++; // incrementa sizeof(char) bytes no endereço para o qual o ponteiro aponta (seu valor)
	}

	puts(aux);
	free(aux);

	return 0;
}

7-Podemos manipular o endereço de memória de várias formas, uma dessas é quando passamos um argumento do tipo ponteiro na função. Na verdade estamos pegando o seu endereço de memória, e passando para uma função genérica, e com isso mudamos o que tem dentro do endereço de memória, essa função dá se o nome de passagem por referência.

 

Obs: Ao utilizar-se uma função que pede como argumento um ponteiro, não nos obriga a passar uma variável que seja ponteiro, pois o que importa realmente é o endereço de memória, lembre-se que, o nome do ponteiro sempre aponta para o próximo elemento e pega o seu endereço de memória, logo assim, simplesmente podemos passar uma variável que não é ponteiro apenas informando o seu endereço de memória com o operador ¨¨& de referência.

 

#include <stdio.h>
#include <stdlib.h>

char *string(char *ptr) {
    
     *ptr = 'b'; //colocar o b no endereço de memória do ptr

     return ptr; // retorna um ptr
}
  
int main(void) {
  
     char caracter = 'a';

     printf("%c", caracter);

     string(&caracter); //pega o endereço de memória da variável caracter

     printf("%c", caracter);
  
} 

 

8-Utilizar-se ponteiros no código, pode garantir performance, e otimização com código genéricos, alocar memória dinamicamente, manipular endereços de memória,

fazer callbacks, e um monte afim, além de ser mais visível achar erros. Segue um exemplo bem básico de ponteiro para função

 

#include <stdio.h>
#include <stdlib.h>


int func(int a, int b) {
     
     return a + b;     

}
  
int main(void) {
  
     int x = 5, y = 5;

     int (*ptr)(int x, int y);

     ptr = 0;

     printf("Ponteiro aponta para  0 : %p\n", ptr); 

     ptr = func;

     printf("Ponteiro aponta para func: %p\nValor: %d", ptr, ptr(x,y));
  
} 

 

9-O uso de ponteiros é bem extenso, no código abaixo vou deixar um exemplo de um array de ponteiros, de antemão, já digo que talvez não tenha explicado de maneira correta,  pois simplifiquei, para ficar mais fácil o entendimento.  Fiquei sem falar sobre alguns usos de ponteiros, tais como ponteiro para ponteiro, ponteiro para um array de um tipo de dado, entre outros, mas nestes casos, não vejo tanta usabilidade nos códigos.

 

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
  
int main(void) {
  
        char *ptr[] = {"Ponteiro", "is", "legal", NULL}; // Array de ponteiros
  
        puts(*ptr); // Exibe a string para onde o primeiro ponteiro aponta
        printf("%s\n%s\n", *(ptr+1), *(ptr+2)); // Exibe as strings avançado os bytes dos ponteiros
  
        putchar(ptr[0][5]);  /* No primeiro [], informo q meu primeiro ponteiro aponta para a String Ponteiros,
                                                    e ao usar [] novamente, informo para avançar  5 bytes da onde o primeiro ponteiro aponta, o resultado é a letra i*/
        putchar('\n');
  
       // Um array bidimensional é diferente de um array de ponteiros, internamente eles se comportam de maneira diferente na memória

       return 0;     
  
} 

 

Obs: Quando falam em performance ao usar ponteiros, quer dizer na verdade que são mais velozes, devido ao código que o compilador gcc gera, por isso logo vai

ter menos ciclos e o processador vai executar mais rápido o programa.

 

Bom, vou ficando por aqui, isso foi um resumo bem simples sobre ponteiros, fiquei na dúvida se postava ou não, porque sou bem noob em C, mas acredito que o fórum seja pra isso mesmo, compartilhar, independente do nível de conhecimento de cada um, espero que um dia eu fique bom nesse treco, haha. Agradecimentos ao Frederico e ao Mercês que me explicou sobre uso de ponteiros, lembrando, se tiver algum erro me avisem, ou se ficou ruim o texto desse jeito, seguido de código e mais código.

 

Bye bye ao som de Vanessa Carlton - A thousand Miles

Link para o comentário
Compartilhar em outros sites

2 horas atrás, hanczjeeel disse:

Muito boa sua iniciativa, parabéns!
fiquei com uma dúvida no tópico 8 na seguinte linha: int(*ptr)(int x, int y);

qual seria a lógica desta linha?
 

É um ponteiro para função, int quer dizer que o ponteiro vai retornar um inteiro, *ptr estou declarando o ponteiro, (int x. int y), informo que a função tem que passar dois parâmetros inteiros, entendeu?

Link para o comentário
Compartilhar em outros sites

1 hora atrás, Eduardo Bittencourt disse:

É um ponteiro para função, int quer dizer que o ponteiro vai retornar um inteiro, *ptr estou declarando o ponteiro, (int x. int y), informo que a função tem que passar dois parâmetros inteiros, entendeu?

Entendi, obrigado.
fiz uns testes aqui, por exemplo ali, pode-se chamar a função func por:
int soma1 = func(1,3);
ou
int soma2 = ptr(1,4);
bem louco.

Link para o comentário
Compartilhar em outros sites

Que legal, @Eduardo Bittencourt! O fórum é pra isso mesmo! Acho fantástico quando alguém perde o medo e posta! haha

Olha, eu não sou especialista no assunto mas, a meu ver, tem coisas que podem ser elevadas de nível, exatamente como o Frederico fez com as minhas aulas no canal. Mas são detalhes, tipo quando você fala que o ponteiro aponta pro endereço de uma variável, mas na verdade pode apontar pra qualquer lugar. Claro, normalmente tem um dado lá. rsrs Mas enfim, como falei, são pequenos detalhes que a revisão do Frederico me ajudou a ficar mais esperto sobre! Como você pediu feedback, vou listar aqui:

  • Ponteiros são variáveis que contém um endereço de memória.
  • É melhor usar o %p ao imprimir endereços de memória, porque ele já adapta pra arquitetura (%#x ou %#lx). Olha a confusão (em 64-bits):
$ echo 'main(){ int x; printf("%x\n%p\n", &x, &x); }' | tcc -run -
4fce29ac
0x7fff4fce29ac
  • Pro size_t, usa o %zu por conta do tamanho (o Frederico fala sobre isso nos comentários no canal)
  • memset() é pra escrever em memória.
  • No código do item 6, aquele free() não vai rolar porque você incrementou o endereço do ponteiro. Uma saída pra isso seria usando um ponteiro auxiliar, pra guardar o primeiro endereço antes de incrementar (já que você usou isso pra iterar por ele):
int main(void) {
	char *ptr = malloc(11);  // Aloca 11 bytes, 10 bytes para a string, e 1 byte a mais para o nullbyte

	strcpy(ptr, "aaaaaaaaab");
	char *aux = ptr;

	while (*ptr != 'b') { // percorre o loop até achar a letra b
		memset(ptr, 'x', sizeof(char)); // escreve o byte 'x' no endereço de memória apontado por ptr (a escrita rola sizeof(char) vezes - no caso aqui é 1)
		ptr++; // incrementa sizeof(char) bytes no endereço para o qual o ponteiro aponta (seu valor)
	}

	puts(aux);
	free(aux);

	return 0;
}

Ou fazer o loop com for. Mas no caso dessa função (colocar 'x' até encontrar 'b'), como é um caractere só, não precisa da memset():

while (*ptr != 'b') {
	*ptr = 'x';  // coloca o 'x' no endereço de memória apontado por ptr
	ptr++;       // incrementa ptr, então ele vai agora apontar para a próxima letra
}

Dá pra fazer tudo numa linha só também, graças à magia negra precedência de operadores:

while (*ptr != 'b')
	*ptr++ = 'x';

Mas de novo, tá muito bom o texto. O assunto parece complicado e assusta a galera, mas você tratou de boa. Isso é muito maneiro!

Grande abraço!

Link para o comentário
Compartilhar em outros sites

1 hora atrás, Fernando Mercês disse:

Que legal, @Eduardo Bittencourt! O fórum é pra isso mesmo! Acho fantástico quando alguém perde o medo e posta! haha

Olha, eu não sou especialista no assunto mas, a meu ver, tem coisas que podem ser elevadas de nível, exatamente como o Frederico fez com as minhas aulas no canal. Mas são detalhes, tipo quando você fala que o ponteiro aponta pro endereço de uma variável, mas na verdade pode apontar pra qualquer lugar. Claro, normalmente tem um dado lá. rsrs Mas enfim, como falei, são pequenos detalhes que a revisão do Frederico me ajudou a ficar mais esperto sobre! Como você pediu feedback, vou listar aqui:

  • Ponteiros são variáveis que contém um endereço de memória.
  • É melhor usar o %p ao imprimir endereços de memória, porque ele já adapta pra arquitetura (%#x ou %#lx). Olha a confusão (em 64-bits):

$ echo 'main(){ int x; printf("%x\n%p\n", &x, &x); }' | tcc -run -
4fce29ac
0x7fff4fce29ac
  • Pro size_t, usa o %zu por conta do tamanho (o Frederico fala sobre isso nos comentários no canal)
  • memset() é pra escrever em memória.
  • No código do item 6, aquele free() não vai rolar porque você incrementou o endereço do ponteiro. Uma saída pra isso seria usando um ponteiro auxiliar, pra guardar o primeiro endereço antes de incrementar (já que você usou isso pra iterar por ele):

int main(void) {
	char *ptr = malloc(11);  // Aloca 11 bytes, 10 bytes para a string, e 1 byte a mais para o nullbyte

	strcpy(ptr, "aaaaaaaaab");
	char *aux = ptr;

	while (*ptr != 'b') { // percorre o loop até achar a letra b
		memset(ptr, 'x', sizeof(char)); // escreve o byte 'x' no endereço de memória apontado por ptr (a escrita rola sizeof(char) vezes - no caso aqui é 1)
		ptr++; // incrementa sizeof(char) bytes no endereço para o qual o ponteiro aponta (seu valor)
	}

	puts(aux);
	free(aux);

	return 0;
}

Ou fazer o loop com for. Mas no caso dessa função (colocar 'x' até encontrar 'b'), como é um caractere só, não precisa da memset():


while (*ptr != 'b') {
	*ptr = 'x';  // coloca o 'x' no endereço de memória apontado por ptr
	ptr++;       // incrementa ptr, então ele vai agora apontar para a próxima letra
}

Dá pra fazer tudo numa linha só também, graças à magia negra precedência de operadores:


while (*ptr != 'b')
	*ptr++ = 'x';

Mas de novo, tá muito bom o texto. O assunto parece complicado e assusta a galera, mas você tratou de boa. Isso é muito maneiro!

Grande abraço!

Vlw pelo feedback, ah propósito da função que uso while, não me toquei que aquela forma estava errada, fiz o teste com o valgrind e deu erro invalid free, enfim. Ah respeito de não usar o %p, na verdade estava até ciente, porém não coloquei porque podia confundir pra quem estava tendo o primeiro contato,  mas pensando bem compensava muito ter colocado,era a melhor forma. Agradeço novamente pelo feedback e pelas suas correções, foram de grande ajuda, abraços :) 

Link para o comentário
Compartilhar em outros sites

É, reconheço que certas decisões são difíceis ao escrever, porque realmente pode confundir. Entendo perfeitamente. Foi assim que fiz o curso também, e as sugestões de melhorias ficaram nos comentários do vídeo, como estão ficando aqui nos comentários do seu tópico. :-)

Valeu!

Link para o comentário
Compartilhar em outros sites

  • 1 mês depois...
  • 4 semanas depois...

E aí, cara? Gostei muito do que escreveu. Parabéns pela iniciativa de passar um assunto bem chato de maneira divertida. Se me permite, gostaria de te dar uns toques sobre o que você escreveu.

Em 19/08/2017 em 10:58, Eduardo Bittencourt disse:

1->Todo ponteiro, pode aponta para algum lugar, e guardar o endereço de memória do dado apontado. O ponteiro  tem o tamanho de um int, e ao apontar para um dado, tem o tamanho deste dado. Vale ressaltar, que o tamanho pode variar de acordo o sistema operacional ou arquitetura.

Cuidado com a diferença sutil entre endereço de memória e conteúdo da memória naquele endereço. O ponteiro não tem o tamanho de um int; na verdade, o ponteiro tem o tamanho de uma palavra (normalmente está relacionada ao tamanho do registrador). Na arquitetura x86, o tamanho da palavra é 32 bits  e o tamanho de um int também, porém isso não é verdade para a arquitetura x86_64. O tamanho da palavra é 64 bits e o tamanho de um int é 32 bits. :)

Cuidado também com esse lance de "ao apontar para um dado, o tamanho do ponteiro terá o mesmo tamanho do dado". O tamanho de um ponteiro é o mesmo, esteja ele apontando para um char, short, int, long, etc. Em todos os casos, o ponteiro terá o tamanho da palavra.

#include <stdio.h>

int main(void)
{
        printf("%zu\n", sizeof(char *));
        printf("%zu\n", sizeof(short *));
        printf("%zu\n", sizeof(int *));
        printf("%zu\n", sizeof(long *));
        printf("%zu\n", sizeof(long long *));
	return 0;
}

Se você compilar e executar esse código no x86_64, vai ver que todos os resultados vão ser 8, mas se você executar no x86, os resultados vão ser 4.

Aí você pode estar se perguntando sobre a relevância dos tipos nos ponteiros. Pra responder isso, ser liga nesse trecho de código:

int main(void)
{
	char num1; short num2; int num3;
	void *ptr = &num1;
  	*(char *) ptr = 100;
  	ptr = &num2;
   	*(short *) ptr = 100;
   	ptr = &num3;
   	*(int *) ptr = 100;
   	return 0;
}

Agora se liga no código Assembly correspondente:

main:
  push ebp
  mov ebp, esp
  sub esp, 16
  lea eax, [ebp-5]
  mov DWORD PTR [ebp-4], eax
  mov eax, DWORD PTR [ebp-4]
  mov BYTE PTR [eax], 100
  lea eax, [ebp-8]
  mov DWORD PTR [ebp-4], eax
  mov eax, DWORD PTR [ebp-4]
  mov WORD PTR [eax], 100
  lea eax, [ebp-12]
  mov DWORD PTR [ebp-4], eax
  mov eax, DWORD PTR [ebp-4]
  mov DWORD PTR [eax], 100
  mov eax, 0
  leave
  ret

Tá vendo esses "mov BYTE PTR [eax], 100",  "mov WORD PTR [eax], 100" e "mov DWORD PTR [eax], 100"? Tá ligado nesses prefixos BYTE, WORD e DWORD? Um char tem o tamanho de 1 byte (BYTE), um short tem o tamaho de 2 bytes (ou uma WORD) e um int tem o tamanho de 4 bytes (ou uma DWORD). A questão do tipo só serve pra informar pra o compilador a quantidade de memória em bytes que será carregada em um registrador ou a quantidade de bytes de um registrador que será armazenada em um determinado endereço de memória. Observe que na parte onde eu faço a atribuição dos endereços de memória para a variável ptr, os prefixos são DWORD (veja todos os  mov DWORD PTR [ebp-4], eax). Isso confirma o que eu te falei sobre o tamanho do ponteiro ser o mesmo para qualquer tipo. No caso, o tamanho do ponteiro é DWORD, logo podemos concluir que o tamanho da palavra é 32 bits. :)

Espero que eu não tenha complicado demais.

Abraços!

 

Link para o comentário
Compartilhar em outros sites

7 horas atrás, thiago disse:

E aí, cara? Gostei muito do que escreveu. Parabéns pela iniciativa de passar um assunto bem chato de maneira divertida. Se me permite, gostaria de te dar uns toques sobre o que você escreveu.

Cuidado com a diferença sutil entre endereço de memória e conteúdo da memória naquele endereço. O ponteiro não tem o tamanho de um int; na verdade, o ponteiro tem o tamanho de uma palavra (normalmente está relacionada ao tamanho do registrador). Na arquitetura x86, o tamanho da palavra é 32 bits  e o tamanho de um int também, porém isso não é verdade para a arquitetura x86_64. O tamanho da palavra é 64 bits e o tamanho de um int é 32 bits. :)

Cuidado também com esse lance de "ao apontar para um dado, o tamanho do ponteiro terá o mesmo tamanho do dado". O tamanho de um ponteiro é o mesmo, esteja ele apontando para um char, short, int, long, etc. Em todos os casos, o ponteiro terá o tamanho da palavra.

Creio eu, que um endereço de memória de um ponteiro tem tamanho de um inteiro, no qual o tamanho pode ser variado. Mas não entendi, o que você quis dizer com tamanho de uma palavra, como assim palavra? E a respeito sobre apontar para char, short, int, long, etc, o tamanho pode variar sim, quando você aponta para um char, você estará reservando apenas 1 byte para aquele endereço de memória. Exemplo:

char *teste = "array";

printf("%d", sizeof(*teste)); // Tamanho de 1 byte

 

Ah respeito do código em assembly, eu entendi o que você quis dizer, mt obrigado pelo feedback :D

Link para o comentário
Compartilhar em outros sites

1 hora atrás, Eduardo Bittencourt disse:

Creio eu, que um endereço de memória de um ponteiro tem tamanho de um inteiro, no qual o tamanho pode ser variado. Mas não entendi, o que você quis dizer com tamanho de uma palavra, como assim palavra? E a respeito sobre apontar para char, short, int, long, etc, o tamanho pode variar sim, quando você aponta para um char, você estará reservando apenas 1 byte para aquele endereço de memória. Exemplo:

char *teste = "array";

printf("%d", sizeof(*teste)); // Tamanho de 1 byte

 

Ah respeito do código em assembly, eu entendi o que você quis dizer, mt obrigado pelo feedback :D

Não entendi qual foi sua dúvida sobre a palavra. A dúvida é sobre o conceito de palavra?

 Eu rodei seu código aqui na minha máquina, modifiquei e aproveitei pra montar este esquema pra simplificar. O tamanho do ponteiro (tamanho do endereço) é o mesmo pra qualquer que seja o tipo (char, short, int, long, etc.). Você simplesmente obteve o tamanho de *teste, que é o mesmo tamanho de teste[0], que é o mesmo tamanho de um char, que possui o tamanho de 1 byte. Se ligue que são duas coisas diferentes: tamanho do endereço é uma coisa e tamanho do conteúdo apontado por aquele endereço é outra. :)

Abraços!

esquema.png

Link para o comentário
Compartilhar em outros sites

5 horas atrás, thiago disse:

Não entendi qual foi sua dúvida sobre a palavra. A dúvida é sobre o conceito de palavra?

 Eu rodei seu código aqui na minha máquina, modifiquei e aproveitei pra montar este esquema pra simplificar. O tamanho do ponteiro (tamanho do endereço) é o mesmo pra qualquer que seja o tipo (char, short, int, long, etc.). Você simplesmente obteve o tamanho de *teste, que é o mesmo tamanho de teste[0], que é o mesmo tamanho de um char, que possui o tamanho de 1 byte. Se ligue que são duas coisas diferentes: tamanho do endereço é uma coisa e tamanho do conteúdo apontado por aquele endereço é outra. :)

Abraços!

 

É isso que estou dizendo, o endereço de memória de um ponteiro sempre vai ter o tamanho de um inteiro. Mas quando você aponta para outro dado, o endereço do dado vai ter o tamanho do tipo apontado, abraços :)

Link para o comentário
Compartilhar em outros sites

3 horas atrás, Fernando Mercês disse:

Fala galera,

Uma maneira de lembrar é pensar no processador. O tamanho da palavra é o tamanho de um endereço de memória. Num programa de 32-bits um endereço tem 32-bits. Num de 64 tem 64-bits e assim vai.

Ou imprimir sizeof(void*) pra tirar a dúvida. :)

Abraços!

Excelente dica, cara. :)

Link para o comentário
Compartilhar em outros sites

20 horas atrás, Eduardo Bittencourt disse:

É isso que estou dizendo, o endereço de memória de um ponteiro sempre vai ter o tamanho de um inteiro. Mas quando você aponta para outro dado, o endereço do dado vai ter o tamanho do tipo apontado, abraços :)

Acho que a gente não tá conseguindo se entender, cara. :/

Por exemplo, a sua definição de que o tamanho do endereço vai ter o tamanho de um inteiro é inválida no caso do x86_64. Como o @Fernando Mercês disse, o tamanho de um endereço numa arquitetura de 64 bits é 64 bits. Compila e testa isso (no x86_64):

#include <stdio.h>

int main()
{
    printf("%zu %zu\n", sizeof(void *), sizeof(int));
    return 0;
}

Você vai ver que sizeof(void *) é 8 e sizeof(int) é 4.

Acho que você está confundindo um pouco as coisas. O que você está dizendo sobre o endereço do dado ter o tamanho do tipo apontado não é verdade. Numa arquitetura de 64 bits, se um ponteiro aponta pra um char, o tamanho do ponteiro é 64 bits; se ele aponta pra um short, int, long, float, double, o tamanho do ponteiro continua sendo 64 bits.  A melhor maneira de ver isso é testando no próprio código.  

Link para o comentário
Compartilhar em outros sites

14 horas atrás, thiago disse:

Acho que a gente não tá conseguindo se entender, cara. :/

Por exemplo, a sua definição de que o tamanho do endereço vai ter o tamanho de um inteiro é inválida no caso do x86_64. Como o @Fernando Mercês disse, o tamanho de um endereço numa arquitetura de 64 bits é 64 bits. Compila e testa isso (no x86_64):


#include <stdio.h>

int main()
{
    printf("%zu %zu\n", sizeof(void *), sizeof(int));
    return 0;
}

Você vai ver que sizeof(void *) é 8 e sizeof(int) é 4.

Acho que você está confundindo um pouco as coisas. O que você está dizendo sobre o endereço do dado ter o tamanho do tipo apontado não é verdade. Numa arquitetura de 64 bits, se um ponteiro aponta pra um char, o tamanho do ponteiro é 64 bits; se ele aponta pra um short, int, long, float, double, o tamanho do ponteiro continua sendo 64 bits.  A melhor maneira de ver isso é testando no próprio código.  

Acho que na verdade está havendo uma falha de comunicação entre nós. Quando digo inteiro, não quero dizer que o ponteiro tem o tamanho do tipo int, mas sim que o tamanho não é um ponto flutuante, entendeu? Eu sei que em arquiteturas diferentes, tem tamanho distintos.

Um ponteiro em arquitetura x86_x64 tem tamanho de um inteiro com modificador de tamanho long long 8 bytes, já em ambientes i386 o ponteiro geralmente tem tamanho de 4 bytes, ambos são inteiros, concorda comigo?

 

Ao utilizar sizeof(*ptr); 

Está retornando o tamanho do conteúdo para onde o ponteiro está apontando ptr, abraços.

Link para o comentário
Compartilhar em outros sites

8 horas atrás, Eduardo Bittencourt disse:

Acho que na verdade está havendo uma falha de comunicação entre nós. Quando digo inteiro, não quero dizer que o ponteiro tem o tamanho do tipo int, mas sim que o tamanho não é um ponto flutuante, entendeu? Eu sei que em arquiteturas diferentes, tem tamanho distintos.

Um ponteiro em arquitetura x86_x64 tem tamanho de um inteiro com modificador de tamanho long long 8 bytes, já em ambientes i386 o ponteiro geralmente tem tamanho de 4 bytes, ambos são inteiros, concorda comigo?

Huahuahuahauhauhauha! Nossa, cara! Que confusão! Mas isso é natural, estamos falando de ponteiros, né?

Sim, sim, esse lance de falar que não é ponto flutuante me confundiu todo. Em x86_64, um ponteiro tem o tamanho de um long ou long long (no x86_64) e long long (no x86).

Abração!

Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois...

Só para acrescentar um ponto relevante relacionado à aritmética de ponteiros:

Ao nos referenciar-mo-nos a ptr[3] estamos a referir-nos ao endereço de ponteiro como base mais um offset(deslocamento) entre [], quando falamos em endereços, para obter-mos o endereço correto de uma referência como a de cima teremos de multiplicar o offset pelo sizeof(tipo_de_ponteiro) que está a ser usado. Exemplo:

ptr[3] é igual a *(ptr + 3), o que na realidade vai fazer referência a *(ptr + 3 * sizeof(ptr)), como referi acima, se ptr apontar por exemplo para 0x11 e usarmos ptr[3] na realidade vamos nos referir ao endereço 0x1d, pois foi somado a ponteiro 3 * sizeof(ptr).

Um outro detalhe interessante, é o uso da propriedade comutativa na aritmética de ponteiros... Exemplo:

Ter ptr[3] ou 3[ptr] é exatamente a mesma coisa, pois ptr[3] significa em termos de programação *(ptr + 3) e 3[ptr] significa *(3 + ptr), e seguindo a propriedade comutativa da matemática a + b = b + a, querendo assim dizer que ptr + 3 <=> 3 + ptr.

Resumindo ptr[3] e 3[ptr] são iguais, a unica consideração a se levar é que por vezes o compilador pode gerar um aviso em relação à segunda durante o processo de compilação.

Por fim, este uso esquisito de aritmética de ponteiros também pode ser usado com arrays, considerado que o símbolo pelo qual a array é representado, por exemplo "a", onde "a" é igual a "&a[0]"  (ou seja "a" é um ponteiro para o endereço de memória do primeiro "item" do array),  assim qualquer deslocamento numa array, por exemplo a[5], pode também ser representado por *(a + 5) ou *(5 + a) devido à propriedade de comutativa que demonstrei acima.

Foi apenas um acréscimo ao tópico.

### SEM QUERER MULTICITEI O CONTEÚDO ###

Agora, Rick Santos disse:

Só para acrescentar um ponto relevante relacionado à aritmética de ponteiros:

Ao nos referenciar-mo-nos a ptr[3] estamos a referir-nos ao endereço de ponteiro como base mais um offset(deslocamento) entre [], quando falamos em endereços, para obter-mos o endereço correto de uma referência como a de cima teremos de multiplicar o offset pelo sizeof(tipo_de_ponteiro) que está a ser usado. Exemplo:

ptr[3] é igual a *(ptr + 3), o que na realidade vai fazer referência a *(ptr + 3 * sizeof(ptr)), como referi acima, se ptr apontar por exemplo para 0x11 e usarmos ptr[3] na realidade vamos nos referir ao endereço 0x1d, pois foi somado a ponteiro 3 * sizeof(ptr).

Um outro detalhe interessante, é o uso da propriedade comutativa na aritmética de ponteiros... Exemplo:

Ter ptr[3] ou 3[ptr] é exatamente a mesma coisa, pois ptr[3] significa em termos de programação *(ptr + 3) e 3[ptr] significa *(3 + ptr), e seguindo a propriedade comutativa da matemática a + b = b + a, querendo assim dizer que ptr + 3 <=> 3 + ptr.

Resumindo ptr[3] e 3[ptr] são iguais, a unica consideração a se levar é que por vezes o compilador pode gerar um aviso em relação à segunda durante o processo de compilação.

Por fim, este uso esquisito de aritmética de ponteiros também pode ser usado com arrays, considerado que o símbolo pelo qual a array é representado, por exemplo "a", onde "a" é igual a "&a[0]"  (ou seja "a" é um ponteiro para o endereço de memória do primeiro "item" do array),  assim qualquer deslocamento numa array, por exemplo a[5], pode também ser representado por *(a + 5) ou *(5 + a) devido à propriedade de comutativa que demonstrei acima.

Foi apenas um acréscimo ao tópico.

 

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...