Imprimir endereço de cada valor num vetor [RESOLVIDO]

1. Imprimir endereço de cada valor num vetor [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 13/12/2019 - 20:37h

Olá,
Estou me confundindo um pouco aqui com ponteiros...

Foi me proposto o seguinte exercício:
/*

8. Crie um programa que contenha um array de float contendo 10 elementos. Imprima o
endereço de cada posição desse array.

*/

Elaborei a seguinte solução:

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

int main (void) {
int i;
float vetor[10];
float *p;
p = vetor;

srand(time(NULL));
for (i = 0; i < 10; i++) {
vetor[i] = rand() % 30;
}

for (i = 0; i < 10; i++) {
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
/*
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], p);
p++;
*/
}

return 0;
}


Porém este trecho do código:

...
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
...

Está me deixando um pouco confuso...
Achei soluções onde quando se iria imprimir o endereço de um valor que está em um vetor, primeiro atribuia ao ponteiro o primeiro endereço do vetor, como eu fiz no código: float *p; p = vetor;
Porém a varável p recebe apenas o endereço da primeira posição do vetor, correto?

Porém ao imprimir o endereço das posições subsequentes eu devo, imprimir "p", e após isso, incrementar "p" -> p++; para que eu vá para o próximo índice do vetor, ou se eu fizer o seguinte: ... <%p>", &p[i]); //No caso foi o que eu fiz no meu programa...
Mas fiquei confuso, nesse caso, estou imprimindo o endereço de p, ou eu estou fazendo p apontar para cada índice do vetor? quando faço &p[i], p "pega" o endereçamento das posições do vetor?

Ou eu realmente tenho que imprimir e depois incrementar (fazendo, no caso, meu ponteiro andar 4 bytes), indo para próxima posição do vetor?


printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
/*
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], p);
p++;
*/


A minha solução está correta? Ou a solução que coloquei dentro de comentários é a mais correta?
Ou estão todas erradas?!

Por que ponteiros são tão confusos????????????? kkkkk 


Obrigado


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 14/12/2019 - 04:20h

princknoby escreveu:

Olá,
Estou me confundindo um pouco aqui com ponteiros...

Foi me proposto o seguinte exercício:
/*

8. Crie um programa que contenha um array de float contendo 10 elementos. Imprima o
endereço de cada posição desse array.

*/

[bloco suprimido]

Porém este trecho do código:

...
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
...

Está me deixando um pouco confuso...
Achei soluções onde quando se iria imprimir o endereço de um valor que está em um vetor, primeiro atribuia ao ponteiro o primeiro endereço do vetor, como eu fiz no código: float *p; p = vetor;
Porém a varável p recebe apenas o endereço da primeira posição do vetor, correto?


Sim.

Porém ao imprimir o endereço das posições subsequentes eu devo, imprimir "p", e após isso, incrementar "p" -> p++; para que eu vá para o próximo índice do vetor, ou se eu fizer o seguinte: ... <%p>", &p[i]); //No caso foi o que eu fiz no meu programa...
Mas fiquei confuso, nesse caso, estou imprimindo o endereço de p, ou eu estou fazendo p apontar para cada índice do vetor? quando faço &p[i], p "pega" o endereçamento das posições do vetor?


Acho que ajuda você conhecer a precedência de operadores do C. Numa expressão como “&p[i]”, o operador de indexação tem precedência maior do que o operador de obtenção de endereço. Desse modo, o valor de p, que, no caso da versão não comentada do código acima, é uma cópia do endereço do primeiro elemento de vetor, é combinado com o valor de i (e com a ciência mantida internamente pelo compilador do tamanho de cada elemento para o qual o ponteiro pode apontar), de modo a obter o lvalue do i+1-ésimo elemento do vetor. Depois disso é que o operador de obtenção de endereço é aplicado sobre esse lvalue a fim de, como o nome sugere, obter o endereço de onde tal lvalue é armazenado.

Eu frisei que p é uma cópia porque você não precisaria ter usado um ponteiro para um laço de repetição utilizando a sintaxe de vetor. Poderia ter usado o próprio vetor original vetor.

Ou eu realmente tenho que imprimir e depois incrementar (fazendo, no caso, meu ponteiro andar 4 bytes), indo para próxima posição do vetor?

                        printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
/*
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], p);
p++;
*/


São duas formas de obter o mesmo resultado. E existe uma terceira. E as três se relacionam pelo que se chama “aritmética de ponteiros.”

Suponha que você tem três variáveis, a saber: p, que é um ponteiro para um dado de um tipo X qualquer, n, que é um valor inteiro qualquer, e pabs, que é uma variável de um tipo inteiro que consiga armazenar o mesmo valor absoluto que um endereço que possa ser usado por um ponteiro. Em C, essas variáveis seria declaradas da seguinte maneira.
#include <stdint.h>  // Para definir o tipo “intptr_t”.

typedef struct { /* campos que você quiser definir */ } X;

X* p;
int n;
intptr_t pabs;


Dadas a definição de valores iniciais adequados, como, por exemplo, no código seguinte,
int main(void){
static X arr[50]; // Array com 50 elementos do tipo X (apenas para que p possa apontar para um lugar válido e seguro).

p=arr; // ‘p’ aponta para primeiro elemento de ‘arr’,
pabs=(intptr_t)p; // ‘pabs’ receber um valor inteiro que corresponde exatamente ao endereço armazenado em ‘p’.

então os valores de p e pabs podem ser manipulados dos seguintes, a fim de asseverar (assert()) que o endereço e seu valor inteiro continuem correspondendo.
	assert(p==&p[0] && p==p+0 && (intptr_t)p==pabs+0*(sizeof *p));	// Teste de sanidade: deslocamento nulo não tem efeito nenhum

for(n=0; n<50; n++)
assert(p+n==&p[n] && (intptr_t)(p+n)==pabs+n*(sizeof *p));

for(n=0; n<50; n++){
p++;
pabs+=sizeof *p;
assert((intptr_t)p==pabs);
}

while(n-- > 0)
assert(p-n==&p[-n] && (intptr_t)(p-n)==pabs-n*(sizeof *p));

for(n=0; n<50; n++){
p--;
pabs-=sizeof *p;
assert((intptr_t)p==pabs);
}

assert(p==arr && (intptr_t)arr==pabs);
}


A minha solução está correta? Ou a solução que coloquei dentro de comentários é a mais correta?
Ou estão todas erradas?!


Nenhuma das duas está errada.

Para garantir, tente o seguinte código.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void){
int n;
float vetor[10];
float *p, *q;
intptr_t pabs, qabs;

srand(time(NULL));
for(n= 0; n<10; n++)
vetor[n]=rand()%30;

p=q=vetor;
pabs=(intptr_t)p;
qabs=(intptr_t)q;
for (n=0; n<10; n++){
printf(
"n=%d: &vetor[n]=%p, vetor+n=%p, &p[n]=%p, p+n=%p, pabs+n*sizeof(*p)=%"PRIxPTR", q=%p, qabs=%"PRIxPTR"\n"
" vetor[n]=%f, *(vetor+n)=%f, p[n]=%f, *(p+n)=%f, *(float *)(pabs+n*sizeof(*p))=%f, *q=%f, *(float *)qabs=%f\n",
n, &vetor[n], vetor+n, &p[n], p+n, pabs+n*sizeof(*p), q, qabs,
vetor[n], *(vetor+n), p[n], *(p+n), *(float *)(pabs+n*sizeof(*p)), *q, *(float *)qabs
);
q++;
qabs+=sizeof *p;
}
}


Por que ponteiros são tão confusos????????????? kkkkk 


Não sei como ponteiros lhe foram ensinados. Em que contexto eles começaram a aparecer para você?

Assim como o que me parece ser com a maioria dos estudante, pelo menos aqui no Brasil, eu aprendi ponteiros da forma errada. Embora eu use C desde 1988, houve pontos que eu não tinha muito claros até bem recentemente (2015 ou 2016), quando uma pessoa (EnzoFerber) nesta comunidade de C e C++ do Fórum do VoL me abriu os olhos para um erro conceitual que eu trazia comigo desde o começo da minha vida de programador em C.

Por conta de casos como esse que eu vivi, eu procuro facilitar a vida alheia, procurando ajudar a eliminar lacunas e esclarecendo mal-entendidos em fóruns como este.

Quais são suas dúvidas quanto ao assunto?


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)

3. Re: Imprimir endereço de cada valor num vetor [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 14/12/2019 - 12:30h


paulo1205 escreveu:

princknoby escreveu:

Olá,
Estou me confundindo um pouco aqui com ponteiros...

Foi me proposto o seguinte exercício:
/*

8. Crie um programa que contenha um array de float contendo 10 elementos. Imprima o
endereço de cada posição desse array.

*/

[bloco suprimido]

Porém este trecho do código:

...
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], &p[i]);
...

Está me deixando um pouco confuso...
Achei soluções onde quando se iria imprimir o endereço de um valor que está em um vetor, primeiro atribuia ao ponteiro o primeiro endereço do vetor, como eu fiz no código: float *p; p = vetor;
Porém a varável p recebe apenas o endereço da primeira posição do vetor, correto?


Sim.

Porém ao imprimir o endereço das posições subsequentes eu devo, imprimir "p", e após isso, incrementar "p" -> p++; para que eu vá para o próximo índice do vetor, ou se eu fizer o seguinte: ... <%p>", &p[i]); //No caso foi o que eu fiz no meu programa...
Mas fiquei confuso, nesse caso, estou imprimindo o endereço de p, ou eu estou fazendo p apontar para cada índice do vetor? quando faço &p[i], p "pega" o endereçamento das posições do vetor?


Acho que ajuda você conhecer a precedência de operadores do C. Numa expressão como “&p[i]”, o operador de indexação tem precedência maior do que o operador de obtenção de endereço. Desse modo, o valor de p, que, no caso da versão não comentada do código acima, é uma cópia do endereço do primeiro elemento de vetor, é combinado com o valor de i (e com a ciência mantida internamente pelo compilador do tamanho de cada elemento para o qual o ponteiro pode apontar), de modo a obter o lvalue do i+1-ésimo elemento do vetor. Depois disso é que o operador de obtenção de endereço é aplicado sobre esse lvalue a fim de, como o nome sugere, obter o endereço de onde tal lvalue é armazenado.

Eu frisei que p é uma cópia porque você não precisaria ter usado um ponteiro para um laço de repetição utilizando a sintaxe de vetor. Poderia ter usado o próprio vetor original vetor.

Ou eu realmente tenho que imprimir e depois incrementar (fazendo, no caso, meu ponteiro andar 4 bytes), indo para próxima posição do vetor?

                        printf ("Valor [%.2f] -> Endereco <%p>\n", vetor, &p[i]);
/*
printf ("Valor [%.2f] -> Endereco <%p>\n", vetor[i], p);
p++;
*/


São duas formas de obter o mesmo resultado. E existe uma terceira. E as três se relacionam pelo que se chama “aritmética de ponteiros.”

Suponha que você tem três variáveis, a saber: [i]p
, que é um ponteiro para um dado de um tipo X qualquer, n, que é um valor inteiro qualquer, e pabs, que é uma variável de um tipo inteiro que consiga armazenar o mesmo valor absoluto que um endereço que possa ser usado por um ponteiro. Em C, essas variáveis seria declaradas da seguinte maneira.
#include <stdint.h>  // Para definir o tipo “intptr_t”.

typedef struct { /* campos que você quiser definir */ } X;

X* p;
int n;
intptr_t pabs;


Dadas a definição de valores iniciais adequados, como, por exemplo, no código seguinte,
int main(void){
static X arr[50]; // Array com 50 elementos do tipo X (apenas para que p possa apontar para um lugar válido e seguro).

p=arr; // ‘p’ aponta para primeiro elemento de ‘arr’,
pabs=(intptr_t)p; // ‘pabs’ receber um valor inteiro que corresponde exatamente ao endereço armazenado em ‘p’.

então os valores de p e pabs podem ser manipulados dos seguintes, a fim de asseverar (assert()) que o endereço e seu valor inteiro continuem correspondendo.
	assert(p==&p[0] && p==p+0 && (intptr_t)p==pabs+0*(sizeof *p));	// Teste de sanidade: deslocamento nulo não tem efeito nenhum

for(n=0; n<50; n++)
assert(p+n==&p[n] && (intptr_t)(p+n)==pabs+n*(sizeof *p));

for(n=0; n<50; n++){
p++;
pabs+=sizeof *p;
assert((intptr_t)p==pabs);
}

while(n-- > 0)
assert(p-n==&p[-n] && (intptr_t)(p-n)==pabs-n*(sizeof *p));

for(n=0; n<50; n++){
p--;
pabs-=sizeof *p;
assert((intptr_t)p==pabs);
}

assert(p==arr && (intptr_t)arr==pabs);
}


A minha solução está correta? Ou a solução que coloquei dentro de comentários é a mais correta?
Ou estão todas erradas?!


Nenhuma das duas está errada.

Para garantir, tente o seguinte código.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void){
int n;
float vetor[10];
float *p, *q;
intptr_t pabs, qabs;

srand(time(NULL));
for(n= 0; n<10; n++)
vetor[n]=rand()%30;

p=q=vetor;
pabs=(intptr_t)p;
qabs=(intptr_t)q;
for (n=0; n<10; n++){
printf(
"n=%d: &vetor[n]=%p, vetor+n=%p, &p[n]=%p, p+n=%p, pabs+n*sizeof(*p)=%"PRIxPTR", q=%p, qabs=%"PRIxPTR"\n"
" vetor[n]=%f, *(vetor+n)=%f, p[n]=%f, *(p+n)=%f, *(float *)(pabs+n*sizeof(*p))=%f, *q=%f, *(float *)qabs=%f\n",
n, &vetor[n], vetor+n, &p[n], p+n, pabs+n*sizeof(*p), q, qabs,
vetor[n], *(vetor+n), p[n], *(p+n), *(float *)(pabs+n*sizeof(*p)), *q, *(float *)qabs
);
q++;
qabs+=sizeof *p;
}
}


Por que ponteiros são tão confusos????????????? kkkkk 


Não sei como ponteiros lhe foram ensinados. Em que contexto eles começaram a aparecer para você?

Assim como o que me parece ser com a maioria dos estudante, pelo menos aqui no Brasil, eu aprendi ponteiros da forma errada. Embora eu use C desde 1988, houve pontos que eu não tinha muito claros até bem recentemente (2015 ou 2016), quando uma pessoa (EnzoFerber) nesta comunidade de C e C++ do Fórum do VoL me abriu os olhos para um erro conceitual que eu trazia comigo desde o começo da minha vida de programador em C.

Por conta de casos como esse que eu vivi, eu procuro facilitar a vida alheia, procurando ajudar a eliminar lacunas e esclarecendo mal-entendidos em fóruns como este.

Quais são suas dúvidas quanto ao assunto?


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


CARAMBA, isso me ajudou a compreender bastante coisa que não estavam muito bem resolvidas na minha mente...

Eu estou tendo muita dificuldade para entender passagens por referência ou por valor para uma função, quando devo passar por referência e quando não devo.
Quando devo receber o parâmetro na função como ponteiro ou quando não devo... Essas coisas ainda estão muito confusas na minha mente.

Eu fiz um curso de C na faculdade, porém não aprendi NADA bem a partir de funções, meu professor era muito acelerado e técnico, muitas vezes era muito complicado acompanhar o raciocínio dele.

Sempre fui apaixonado por tentar programar, e meu primeiro contado foi C, porém estava muito difícil pra mim, acabei desistindo e comecei a aprender python (não terminei o básico ainda), entrei pro linux e comecei a aprender um pouco de SHELL também...

Mas vira e meche estou tentando mexer com C. Então me desafiei a fazer um programa para cadastrar clientes (quantos eu queira), primeiro comecei a pesquisar sobre isso, descobri que C não é a linguagem mais "correta" para isso, e sim MySQL, porém não estou com ânimo para aprender outra linguagem (quando coloco algo na minha cabeça, EU PRECISO FAZER!). Então decidi simplificar o programa, apenas para minha diversão mesmo, e confesso que estão sendo bem divertido.

Não quero criar nenhum banco de dados "estático", quero apenas criar um programa que fique em um loop até que eu decida sair, e que me permita cadastrar quantos clientes eu quiser (basta acessar a opção dada em um menu), mesmo que após fechar o programa eu vá perder todos estes dados.

Creio que meu maior desafio foi que precisei usar MUITA das coisas que não aprendi, então quando eu estava precisando, eu começa a pesquisar e pesquisar e pesquisar... Ver aulas, ler algumas apostilas... E então consegui fazer o programa (precisei tentar aprender melhor ponteiros, função, alocação dinâmica...) E confesso que ainda não estou apto a dizer que sei estas coisas. Pois algumas coisas neste programa, consegui fazer na "tentativa e erro".

Vou deixar o meu código aqui pra você ver:


/*
Programa que cadastra N clientes
Apenas Nome e CPF
*/
#include <stdio.h>
#include <stdlib.h>

struct dadosClientes {
char nome[100], CPF[30];
};
typedef struct dadosClientes dados;

//prototipo das funções
int menu(int *option); //Funcao com o menu
void bufferClear(); //Limpeza de buffer
void cadastrarCliente(dados *ponteiroDados); //Cadastro dos clientes
void mostrarClientes(dados *DADOS, int n); //Listagem dos clientes cadastrados

int main(void) {
int option = 0, n = 1; // N é o tamanho(inicial) do array da struct
dados *pD; //Ponteiro para a struct dados

pD = (dados *) malloc(n*sizeof(dados)); //Agora temos um array para a struct de tamanho 1

for (;;) {
menu(&option);
if (option == 1) {
cadastrarCliente(&pD[n - 1]); //Passo o array de struct na posicao [0]

printf ("Cliente '%s' cadastrado!\n", pD[n-1].nome);

n++; //aumentados o tamanho da variavel N
pD = (dados *) realloc(pD, n*sizeof(dados));//retorno agora, precisamos aumentar o array (realocar o tamanho)
} else if (option == 2) {
mostrarClientes(pD,n);
} else if (option == 0) {
break;
} else {
printf("Opcao invalida!\n");
option = 0;
menu(&option);
}
}

free(pD);
return 0;
}

int menu(int *option) {

printf("\tBEM VINDO\n\n");
printf("----------------------------------------------\n");
printf("Digite 1 para cadastrar algum cliente\n");
printf("Digite 2 para mostrar os clientes cadastrados\n");
printf("Digite 0 para encerrar o programa\n");
printf("----------------------------------------------\n");
printf("\nDigite a opcao escolhida: ");
scanf("%d", &*option);
bufferClear();

return *option;
}

void bufferClear() {
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF);
}

void cadastrarCliente(dados *ponteiroDados) {
system("clear");
printf("--------------------------------------------\n");
printf("|\tCADASTRO CLINTE\t\t\t |\n");
printf("--------------------------------------------\n\n\n");
printf(">Nome do cliente: ");
scanf("%99[^\n]", ponteiroDados->nome);
bufferClear();

printf(">FORMATO: ***.***.***-**\n");
printf(">CPF: ");
scanf("%29[^\n]", ponteiroDados->CPF);
bufferClear();
}

void mostrarClientes(dados *DADOS, int n) {
system("clear");
printf("--------------------------------------------\n");
printf("|\tCLIENTES CADASTRADOS\t\t\t |\n");
printf("--------------------------------------------\n\n\n");
for (int i = 0; i < n - 1; i++) {
printf("--------------------------------------------\n");
printf(">Nome: %s\n\n", DADOS[i].nome);
printf(">CPF: %s\n", DADOS[i].CPF);
printf("--------------------------------------------\n\n");
}
scanf("%*c");
}


Eu ainda não consigo entender o porque que nesse if () {...} eu preciso passar o meu array como referência(&)
E o porque no meu else if () {...} seguinte, eu não conseguir fazer funcionar passando por referência, e ambas as funções foram necessárias receber os parâmetros usando ponteiros!!!


if (option == 1) {
cadastrarCliente(&pD[n - 1]); //Passo o array de struct na posicao [0]

printf ("Cliente '%s' cadastrado!\n", pD[n-1].nome);

n++; //aumentados o tamanho da variavel N
pD = (dados *) realloc(pD, n*sizeof(dados));//retorno agora, precisamos aumentar o array (realocar o tamanho)
} else if (option == 2) {
mostrarClientes(pD,n);
} else if (option == 0) {
break;
}


As funções chamadas estão assim:

void cadastrarCliente(dados *ponteiroDados) {
system("clear");
printf("--------------------------------------------\n");
printf("|\tCADASTRO CLINTE\t\t\t |\n");
printf("--------------------------------------------\n\n\n");
printf(">Nome do cliente: ");
scanf("%99[^\n]", ponteiroDados->nome);
bufferClear();

printf(">FORMATO: ***.***.***-**\n");
printf(">CPF: ");
scanf("%29[^\n]", ponteiroDados->CPF);
bufferClear();
}

void mostrarClientes(dados *DADOS, int n) {
system("clear");
printf("--------------------------------------------\n");
printf("|\tCLIENTES CADASTRADOS\t\t\t |\n");
printf("--------------------------------------------\n\n\n");
for (int i = 0; i < n - 1; i++) {
printf("--------------------------------------------\n");
printf(">Nome: %s\n\n", DADOS[i].nome);
printf(">CPF: %s\n", DADOS[i].CPF);
printf("--------------------------------------------\n\n");
}
scanf("%*c");
}


Ambas as funções precisei receber o parâmetro usando ponteiro, mas em uma eu passei por referência(&) e outra eu passei por valor!!!!
Porque na função cadastrar clientes, para armazenar os dados, eu precisei também usar ponteiros, mas para mostrar os clientes, eu não pude usar ponteiros???

Isso tudo está muito confuso para mim, o programa funcionou, mas se eu precisar explicar o porquê de cada ponteiro, o porquê de usar (&) ou não usar, eu não irei conseguir explicar isso pra absolutamente ninguém!

Eu estou assistindo aulas do canal Linguagem C Descomplicada do professor Backes
Está me ajudando bastante, mas ainda sim estou com muitas dificuldades na compreensão de muitas coisas. O senhor poderia me indicar algum material de estudo que realmente vá me fazer aprender tudo como realmente é??? Nem que eu tenha que voltar a estudar o printf e scanf!

Obrigado!


4. Re: Imprimir endereço de cada valor num vetor [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 14/12/2019 - 20:22h

Acho que entendi o uso de & e *

Corrija-me se eu estiver errado:

Eu uso passagem por referência quando quero alterar o valor de uma variável na função que está chamando outra função.
Por exemplo tenho uma variável x, na main
Vou chamar uma função void alterar() {} que vai alterar o valor de x na função main, portanto devo passar a variável x por referência.
alterar(&x);
E devo colocar ela como ponteiro na função (pois quero alterar o que tem no endereço que me foi passado). Então a função ficaria:
void alterar (int *x) {

}

Até agora está correto?
Agora para alterar o valor, porque devo ou não usar ponteiro, e porque devo ou não usar '&'.

Por exemplo:
scanf ("%d", &x);
scanf ("%d", &*x);
scanf ("%d", x);

O primeiro scanf, pelo que entendi está errado, pois x é um ponteiro, o segundo está correto, porém o uso de "&" não é necessário, já que novamente a variável x é um ponteiro, e eu nao preciso de * pois seria redundância já que a variável x JÁ É um ponteiro!

Portanto o último scanf é o mais correto?

Porém não função que criei para listar os clientes cadastrados, ainda não entendi o porque eu passei a struct por valor, e nos parametros da função eu tive que usar ponteiros... Eu não deveria usar ponteiros apenas quando faço passagem por referência?!
Ainda não entendi sobre quando usar ou não usar ponteiros no parâmetro de funções, mas creio que estou começando a entender...


5. Re: Imprimir endereço de cada valor num vetor

Paulo
paulo1205

(usa Ubuntu)

Enviado em 16/12/2019 - 02:20h

princknoby escreveu:

Acho que entendi o uso de & e *

Corrija-me se eu estiver errado:

Eu uso passagem por referência quando quero alterar o valor de uma variável na função que está chamando outra função.
Por exemplo tenho uma variável x, na main
Vou chamar uma função void alterar() {} que vai alterar o valor de x na função main, portanto devo passar a variável x por referência.
alterar(&x);
E devo colocar ela como ponteiro na função (pois quero alterar o que tem no endereço que me foi passado). Então a função ficaria:
void alterar (int *x) {

}


Até agora está correto?


Basicamente, sim. Permita-me só fazer dois esclarecimentos, o primeiro por causa de terminologia que você usou, e o segundo por ser um outro caso de uso comum.

Em C, todas as passagens de argumentos para funções são sempre e exclusivamente por valor (ou, como eu costumo dizer para ficar bem claro, por “cópia de valor”). Não existe nada parecido com a funcionalidade que algumas outras linguagens, tais como as que existem em Fortran, Pascal, C++ ou Visual BASIC, em que você determina a passagem por referência ou por valor na hora de declarar a função, e o compilador automaticamente decida se, numa invocação tal como “func(x, y)”, os argumentos x e y serão passados por valor ou por referência. Em C, se você quiser um efeito semelhante ao de passagem por referência, tem manualmente de obter uma referência (i.e. um ponteiro) para o objeto e passar, por valor, o ponteiro obtido manualmente como parâmetro da função.

Eu enfatizo isso porque você disse algo na linha de que se você quiser “chamar uma função (...) que vai alterar o valor de x (...), devo passar a variável x por referência”. Eu prefiro dizer, no contexto particular do C (e também no de outras linguagens que trabalham com ponteiros diretamente), que você tem de passar “uma referência/ponteiro para a variável x” por (cópia de) valor (até porque, em C, não existe outra opção).

A segunda observação é que a passagem por referência não é útil apenas para modificar dentro da função um objeto existente fora dela. Ela também pode ser usada para evitar a cópia de valor, caso tal cópia seja muito custosa, como costuma ser no caso de estruturas com muitos campos ou campos que sejam grandes. No caso do C, funções que recebam ponteiros para dados que não serão modificados costumam qualificar a não-alteração por meio do modificador const aplicado ao tipo do dado apontado.

Agora para alterar o valor, porque devo ou não usar ponteiro, e porque devo ou não usar '&'.

Por exemplo:
scanf ("%d", &x);
scanf ("%d", &*x);
scanf ("%d", x);


O primeiro scanf, pelo que entendi está errado, pois x é um ponteiro, o segundo está correto, porém o uso de "&" não é necessário, já que novamente a variável x é um ponteiro, e eu nao preciso de * pois seria redundância já que a variável x JÁ É um ponteiro!

Portanto o último scanf é o mais correto?


Para saber qual o correto, depende de qual o tipo do argumento.

Antes de falar especificamente de scanf(), vamos ver uma outra coisa primeiro. Suponha que você tem a seguinte função, que realiza a função de trocar os valores de dois objetos inteiros (obviamente referidos por meio de ponteiros).

void swap(int *pa, int *pb){
int temp=*pa;
*pa=*pb;
*pb=temp;
}


Como a função vai ser chamada vai depender do contexto. Veja alguns exemplos válidos e inválidos, incluindo explicações breves dos porquês.

int main(void){
int
x=1, y=10,
ax[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
ay[10]={10, 20, 30, 40, 50, 60, 70, 80, 90, 100},
*px, *py // Note que eu deliberadamente não defini os valores iniciais dos ponteiros, e portanto não sei para onde apontam.
;

swap(x, y); // ERRO: tipos dos argumentos (int) não correspondem aos tipos dos parâmetros (int *), e o compilador não infere
// automaticamente a passagem por referência.
swap(&x, &y); // OK: obtém manualmente dois ponteiros que, além de satisfazem os tipos dos parâmetros, correspondem a objetos válidos.

swap(px, py); // PROBLEMA: Sintaticamente OK, pois os tipos dos valores de px e py correspondem aos dos parâmetros; contudo,
// como não se sabe para onde eles apontam, isso pode dar problema.

px=&x; py=&y; // Faço com que os ponteiros apontem para locais válidos (x e y, respectivamente).
swap(px, py); // OK. Aqui fica bem claro que eu pego os valores dos ponteiros para, indiretamente, alterar os valores de x e y.
swap(&px, &py); // ERRO: fazendo isso, eu estaria trocando os valores de px e de py, não de x e y; contudo, os tipos de &px
// e &py (int **) não são compatíveis com os dos parâmetros da função (int *).

swap(ax, ay); // OK: troca o primeiro elemento de ax com o primeiro elemento de ay (quando se usa um array numa expressão que não seja
// sizeof nem & aplicado diretamente sobre ele, “ax” é absolutamente idêntico a “&ax[0]”, ou seja, um ponteiro para
// seu primeiro elemento).
swap(&ax[0], &ay[0]); // OK: absolutamente sinônima da linha anterior.
swap(&ax, &ay); // ERRO: na presença de &, um array tal como ax e ay não tem o sentido de ponteiro para o primeiro
// elemento, mas sim um ponteiro para o array inteiro, tomado como um só elemento de dados, cujo tipo,
// no caso, é “int (*)[10]” (ponteiro para array com 10 elementos inteiros).

swap(&ax[4], &ay[4]); // OK: troca os quintos elementos dos dois vetores (lembrando que o índice 0 indica o primeiro elemento).
swap(ax+4, ay+4); // OK: sinônimo da linha acima.
swap(&ax[0]+4, &ay[0]+4); // OK: outro sinônimo das duas linhas anteriores (&ax[0] e &ay[0] têm o tipo “int *”, e a aritmética de ponteiros
// garante que o deslocamento “+4” vai apontar para o endereço correto de “4 elementos depois daquele para que
// aponto agora“ (quatro após primeiro = quinto)).

px=ax; py=&ay[0]; // Ambos apontam para os primeiros elementos dos respectivos arrays.
swap(px, py); // OK: Por referência indireta, troco os primeiros elementos dos dois arrays.

swap(&x+4, 4+&y); // PROBLEMA: sintaticamente OK, mas embora &x e &y obtenham ponteiros para x e y, respetivamente, a aritmética de
// ponteiros só deveria ser usada para ponteiros que efetivamente se refiram a arrays. O risco aqui é que os valores de
// ponteiros calculados vão apontar para memória que não pertence a nenhuma das duas variáveis envolvidas na operação,
// e que não se pode saber a priori o que está em tais endereços.
swap(&(x+4), &(4+y)); // ERRO: o operador &, de obtenção de endereço/referência/ponteiro, só pode ser aplicado sobre entidades
// que tenham um local bem definido na memória (lvalue); x+4 e 4+y são valores temporários, obtidos por cálculo
// e não vinculados a nenhuma área de memória determinada, de modo que não faz sentido aplicar o operador & sobre eles.

swap(*(&ax+4), (&ay)[4]); // PROBLEMA: sintaticamente OK, mas o que acontece em ambos os argumentos é que eu obtenho o ponteiro
// para o array inteiro e, por meio de abuso da aritmética de ponteiros, me desloco para o quinto “array inteiro”
// e obtenho o que seria o valor de tal “array inteiro”, o qual decai automaticamente para ponteiro para seu
// primeiro elemento, cujo tipo é compatível com o do parâmetro da função, mas que reside em área de memória
// que não pertence a nenhuma das variáveis envolvidas nessas manipulações.

swap(2, 3); // ERRO: tipos inválidos (int para os argumentos, int * para os parâmetros).
swap(&2, &3); // ERRO: não é possível obter referências/ponteiros para constantes literais do programa.
swap(NULL, NULL); // PROBLEMA: sintaticamente OK, mas ponteiros nulos nunca indicam objetos válidos.
swap((int *)2, (int *)3); // PROBLEMA: sintaticamente OK, mas é improvável que os endereços 2 e 3 da memória correspondam a endereços válidos
// para o programa.
}


Note que os usos que eu indiquei com “OK” são aqueles em que os tipos dos argumentos combinam com os tipos dos parâmetros declarados da função e, além disso, se referem a ponteiros que são válidos. Os que estão marcados com “PROBLEMA” são aqueles em que os tipos dos argumentos estão corretos, mas que envolvem algum comportamento temerário, tal como usar um ponteiro não inicializado ou que sabidamente aponta para um endereço inválido e suspeito, e que em alguns casos requerem sintaxes obscuras e operações ou conversões de tipos suspeitas (tais como a que aplica deslocamento sobre endereços de objetos que não são diretamente declarados como arrays).

No caso de algumas coisas que eu marquei com “ERRO”, algumas são erros óbvios, que nenhum compilador deixaria passar (tal como aplicar & sobre constantes literais ou sobre operações com x e y). Outras, no entanto, apesar de produzirem argumentos com tipos incompatíveis com os dos parâmetros, não necessariamente provocam erros de compilação, mas apenas alertas sobre incompatibilidades de tipos, que o compilador, se não tiver sido instruído para deliberadamente proibir, acaba deixando passar, por motivos que, no meu entendimento, são principalmente para compatibilidade com código antigo, quando o C não tinha os mesmos mecanismos que tem hoje para a detecção de código potencialmente problemático, mas que às vezes até funciona (um exemplo desses é “swap(&ax, &ay)”, porque embora os argumentos sejam de um tipo que indica ponteiro para o array inteiro, em lugar de ponteiro para o primeiro elemento, numericamente o array inteiro e seu primeiro elementos estão na mesma posição de memória). Contudo, como eu sempre recomendo que todas as compilações de diagnóstico de código perigoso sejam ativadas e façam com que o compilador interrompa a compilação (no caso do GCC, são as opções “-Wall -Werror -O2 -pedantic-errors”), preferi indicar logo como erro.


E, depois dessa brincadeira, volto a sua pergunta com relação a scanf() e a outros aspectos que não apareceram o exemplo acima.

O problema com scanf() é parecido com o que eu procurei ilustrar, mas com uma diferença importante: scanf() utiliza uma sintaxe do C para funções com parâmetros variáveis. Ela é declarada numa forma parecida com “int scanf(const char *fmt, ...)”, indicando que o tipo de retorno é int e que o primeiro parâmetro, que indica uma string de formatação, tem de ser um ponteiro constante para caracteres, mas aquele “...” indica que os demais parâmetros são opcionais e que, se estiverem presentes, seus tipos não são conhecidos de antemão. O compilador, em princípio, não tem elementos suficientes para verificar se os tipos dos argumentos que porventura venham depois da string de formatação estão corretos ou não.

Alguns compiladores, inclusive o GCC, possuem código especializado para, durante a compilação, tratar de modo diferenciado das demais funções as que são da família de scanf() (que inclui ela própria e também fscanf(), sscanf() e outras variações), no qual aplica uma heurística que procura ver se existe uma correspondência aproximada entre as conversões especificadas na string de formatação e a quantidade e os tipos dos demais argumentos. Tal heurística tem limitações sérias, porque só funciona quando a string de formatação é especificada diretamente como constante literal string, não quando chega através de uma variável (o que é perfeitamente válido), e não consegue enxergar além da quantidade e tipos dos argumentos para ver, por exemplo, se o ponteiro de caracteres correspondente a uma leitura de string é realmente um vetor de tamanho suficiente, e não um vetor muito curto ou o endereço de um único caráter, nem muito menos se tal ponteiro tem um endereço válido.

Mesmo assim, nos exemplos que você mostrou, a heurística a que me referi, se usada, deve ajudar a eliminar os erros em que os tipos dos dados forem completamente estapafúrdios. Voltando ao seu exemplo, copiado abaixo com pequenas modificações, temos o seguinte:

scanf ("%d", &x);  // Caso 1
scanf ("%d", &*x); // Caso 2
scanf ("%d", x); // Caso 3


  • Se o tipo de x for int, o caso 1 é sintaticamente válido, o caso 2 dá erro de compilação porque não se pode aplicar o operador * sobre um inteiro, e o caso 3 seria interceptado pela heurística por causa de tipo de dados incompatível (sem ela, possivelmente o argumento seria silenciosamente aceito, e é praticamente garantido que daria falha de segmentação na hora da execução).

  • Se o tipo de x for um array com elementos dos tipo int, o caso 1 teria um argumento cujo tipo é “ponteiro para o array inteiro”, o que seria um erro de tipo e que a heurística pegaria (sem ela, o compilador provavelmente seguiria em frente, e talvez até o programa funcionasse, apesar do erro conceitual e que deve ser evitado, a despeito dos resultados, por causa da coincidência fortuita entre o ponteiro para o array inteiro e o ponteiro para um único elemento inteiro, que é o que a conversão espera). O caso 3 é perfeitamente válido, por causa do decaimento automático do array em ponteiro para o primeiro elemento. No caso 2, o decaimento automático também acontece, e o operador * aplicado a esse ponteiro faz com que ele seja “derreferenciado”, isto é, com que se obtenha o lvalue ao qual o ponteiro se referia; esse lvalue calculado, por sua vez, é passado como argumento para o operador &, que obtém de novo o endereço do lvalue, produzindo um efeito final como se o * e o & se anulassem mutuamente (o “como se” está em itálicos porque tal efeito é apenas aparente: as expressões envolvidas têm de fazer sentido, e elas são, nesse caso, interpretadas da direita para a esquerda, como explicado; primeiro com * aplicado a um ponteiro válido, e depois com & aplicado a um lvalue).

  • Se o tipo de x for um int *, o caso 1 teria um argumento que seria do tipo int **, incompatível com o que a função espera, o que seria detectado e apontado pela heurística (sem ela, o compilador seguiria em frente, mas a execução do programa não poderia nem ao menos contar com a coincidência fortuita entre endereço de primeiro elemento e endereço do array inteiro, porque simplesmente não existe array vinculado a x). Os casos 2 e 3 são válidos, desde que, durante a execução, x aponte para um endereço válido.

  • Se x for de qualquer outro tipo que não seja ponteiro, tem-se um erro crasso no caso 2, e incompatibilidades de tipos de dados nos casos 1 e 3, os quais a heurística pode apontar. Sem ela, o código terá erros semânticos que deveriam ser corrigidos, mas que não seriam interceptados pelo compilador, e podem produzir executáveis cujo comportamento é indefinido.

  • Se x for um array com elementos de qualquer tipo diferente de int ou ponteiro para qualquer outro tipo que não seja int, a heurística vai alarmar em todos os casos se estiver em uso. Caso contrário, a compilação possivelmente se completaria, e o comportamento do programa seria indefinido em cada um dos casos.

Porém não função que criei para listar os clientes cadastrados, ainda não entendi o porque eu passei a struct por valor, e nos parametros da função eu tive que usar ponteiros... Eu não deveria usar ponteiros apenas quando faço passagem por referência?!
Ainda não entendi sobre quando usar ou não usar ponteiros no parâmetro de funções, mas creio que estou começando a entender...


Estritamente falando, como já mencionei anteriormente, você não passa nada por referência, pois todas as passagens de argumentos em C são por valor.

No caso da sua função mostrarClientes(), aí mesmo é que a passagem não é por referência, mas por valor: o tipo do argumento pD que será usado com a função, declarado logo no começo do bloco de main(), é “dados *”, e você é obrigado a que seja assim porque está usando alocação dinâmica. O que você gostaria de como tipo do argumento de mostrarClientes(), lembrando que os parâmetros receberão meras cópias de valores dos argumentos? Pensemos juntos:

  • Se o tipo do parâmetro fosse simplesmente “dados”, a invocação da função teria de escolher um único elemento do array dinamicamente alocado pD, e passar tal elemento por cópia de valor. Mesmo que o elemento escolhido fosse o primeiro elemento de pD, dentro da função ela receberia uma mera cópia desse elemento, de modo que ela não conseguiria voltar ao array original mesmo que aplicasse o operador & à cópia, porque taj cópia, como sugere o sentido mesmo da palavra, não é exatamente a mesma coisa que o original: ainda que os valores sejam iguais, é outro objeto, que reside em outra posição de memória.

  • Fazer como você fez, usando o mesmo tipo para o parâmetro e para o argumento que lhe corresponde. Como esse tipo já é um tipo ponteiro, ele necessariamente já tem a função de guardar o endereço do objeto que realmente interessa. Quando você passa por valor (até porque não tem outro jeito) um endereço para uma função, o valor copiado será o mesmo endereço e pode, portanto, ser usado para chegar ao mesmo objeto, com os mesmos direitos de acesso, isto é, se o ponteiro original podia ser usado tanto para ler quanto para alterar o objeto, sua cópia também poderá ser usada tanto para ler quanto para alterar o objeto.

  • Usar um parâmetro que seja um ponteiro para objeto constante (i.e. “const dados *“) , qualificando que a função não irá modificar o objeto que lhe for passado, mesmo que o ponteiro original pudesse ser usado pudesse ser usado de modo a alterar o objeto apontado. Continua sendo uma passagem por valor (você copia o valor do ponteiro), mas a função fica restrita quanto à operações que pode fazer, e quem a usa tem a garantia de não ocorrerá nenhuma mutação nos elementos do array dentro da função.

  • Passar uma referência para o array dinâmico a ser manipulado como parâmetro da função (e.g. “dados **”), mas isso só seria realmente útil se a função precisasse realizar operações que tivessem não apenas de modificar os elementos do array, mas também o próprio array (por exemplo, para realocá-lo ou liberá-lo).

  • Passar uma referência constante para o array dinâmico (e.g. “dados *const *”) faria menos sentido ainda do que a anterior, pois eu não poderia modificar o array dinâmico como um todo (realocando-o ou desalocando-o), mas poderia alterar livremente seus elementos.

  • Passar uma referência constante para um array com elementos constantes (e.g. “const dados *const *”) poderia fazer um pouco mais de sentido, mas além de não acrescentar nada em termos de funcionalidade em relação à terceira opção (além do custo de ter de obter manualmente a referência ao argumento e de ter de “derreferenciar” o parâmetro para usá-lo dentro da função), ainda demandaria uma conversão explícita do tipo resultante da expressão “&pD”, porque em C a conversão de X * para const X *const * não é automática, pois os dois tipos são considerados incompatíveis entre si (uma infelicidade que eu espero que seja remediada numa próxima versão do C, porque já o foi em C++ desde o padrão de 2011 dessa linguagem).

A terceira opção, portanto, me parece a melhor.


Não sei se eu ajudei ou atrapalhei com todo esse falatório e exemplos. Fique à vontade para perguntar qualquer coisa que você porventura não tenha entendido (mas tente pensar um pouquinho antes de perguntar, pois muita coisa se esclarece quando você consegue pensar nos tipos resultantes das operações que envolvam os operadores [], * e &).


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


6. Re: Imprimir endereço de cada valor num vetor

João Paulo
princknoby

(usa Arch Linux)

Enviado em 17/12/2019 - 22:07h

Olá Paulo,

Demorei um pouco para responder a sua reposta rsrs

Por várias vezes li suas palavras, tentei assimilar e entender o máximo que pude! Por em prática o que eu estava entendendo para ver se estava correto, escrevendo códigos pequenos apenas para analisar as saídas... Usei seus parametros de compilação para compilar todos os códigos: gcc main.c -std=c11 -Wall -O2 -pedantic-errors -o main e tentava sempre fazer o meu programa chegar a 0 warnings e 0 erros!

E poxa vida, esses parametros me trouxeram muito aprendizado!!! Me vizeram ler muitas coisas no stack overflow, e acrescentaram muito aprendizado pra mim.
Maratonei o curso de C moderno do canal papo binário até a parte de alocação dinâmica(vou assistir até o fim do curso, mas agora vou mais devagar, estava terminando o dia completamente mentalmente esgotado), e isso também me trouxe muita coisa de aprendizado!!! Entendi muitas coisas que antes estavam obscuras em minha mente!

Ahhhh... Como eu queria que na faculdade tivesse me ensinado ponteiros e arrays de forma "mais correta", mas precisa!!! Pois foi me ensinado de uma maneira muito simples, e graças a esse desse desafio que fiz a mim mesmo, tive a oportunidade de conhecer ponteiros e funções, parametros, declarações de uma forma muito melhor, e muito mais verdadeira!

Apaguei todo o meu código e o reescrevi, removi algumas coisas, escrevi algumas outras de uma forma "mais inteligente" e correta, agora consigo entender bem mais do meu código do que eu estava entendendo antes!
Ao olhar para os dois códigos percebo uma diferença brutal neles. Meu código não é profissional eu estou ciente disso, sou um belo de um iniciante, mas me orgulho quando vejo o quanto eu consegui amadurecer meu código! Mesmo que ainda seja um código totalmente iniciante e amador!

Foi uma experiência muito gratificante e divertida pra mim!!! Fiquei muito feliz com meus recentes aprendizados adquiridos, e você Paulo fez parte disso.

Então deixo, meus sinceros MUITO OBRIGADO!

Vou deixar o meu código aqui, caso o senhor se interesse em ver :D
OBS: Não deixei o código "polido" com menu, dizendo o que cada opção faz(1 cadastra clientes, 2 mostra os clientes cadastrados, 0 encerra o program), nem nada, como é só pra mim, eu apenas queria que funcionasse bem! O polimento visual irei acrescentar outro dia, agora irei dar uma diminuida no ritmo, pois esses últimos dias vivi um verdadeiro overflow de informações, agora é ir com calma e organizar eles, coloca-las em ordem, para que eu possa extrair ainda mais aprendizado delas.

Segue o código:

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

typedef struct dados {
char nome[100], CPF[30];
int age;
}
dados;


void fflush_s() {
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF);
}


void dataEntrys(dados * pD) {
printf(">Nome: ");
if (scanf("%99[^\n]", pD->nome) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">CPF: ");
if (scanf("%99[^\n]", pD->CPF) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">Idade: ");
if (scanf("%d", & pD->age) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();
}


void showReady(dados * pD, int n) {
if(system("clear") == -1) {
printf("system clear failed!\n");
}
for (int i = 0; i < n; i++) {
printf(">Name: %s\n", pD[i].nome);
printf(">CPF: %s\n", pD[i].CPF);
printf("Idade: %d\n\n\n", pD[i].age);
}
if(scanf("%*c") == 0) {
printf("scanf falhou!\n");
}
}

int main(int argc, char * argv[]) {
dados * pD;
int n = 0, option;

pD = malloc(n * sizeof(char));
do {
if(system("clear") == -1) return 1;
printf("Chosse option: ");
if (scanf("%d", & option) == 0) return 1;
fflush_s();

if (option == 1) {
dataEntrys( & pD[n]);
n++;
pD = realloc(pD, n * sizeof(char));
} else if (option == 2) {
showReady(pD, n);
} else if (option == 0) {
break;
} else {
printf("Other option: ");
if (scanf("%d", & option) == 0) {
break;
}
}
} while (option != 0);

free(pD);
return 0;
}




7. Re: Imprimir endereço de cada valor num vetor

Paulo
paulo1205

(usa Ubuntu)

Enviado em 19/12/2019 - 02:05h

princknoby escreveu:

Olá Paulo,

Demorei um pouco para responder a sua reposta rsrs

Por várias vezes li suas palavras, tentei assimilar e entender o máximo que pude! Por em prática o que eu estava entendendo para ver se estava correto, escrevendo códigos pequenos apenas para analisar as saídas... Usei seus parametros de compilação para compilar todos os códigos: gcc main.c -std=c11 -Wall -O2 -pedantic-errors -o main e tentava sempre fazer o meu programa chegar a 0 warnings e 0 erros!


Se você usa um GCC recente, ele usa por default uma variante do padrão C11 (isto é C11 mais algumas extensões da GNU). Se você não tiver motivos para evitar as extensões — e pode ter, se você quiser ter certeza de que seu código não vai usar absolutamente nenhum recurso que não seja padronizado —, talvez não precise de especificar a versão do padrão da linguagem, porque o default pode ser suficiente para você.

E poxa vida, esses parametros me trouxeram muito aprendizado!!! Me vizeram ler muitas coisas no stack overflow, e acrescentaram muito aprendizado pra mim.


Eu seriamente sugiro que você os torne padrão (e acrescentaria “-Werror”, porque não estou certo de que “-pedantic-errors” inclua os mesmos casos de erro que ela, mas uma outra categoria, que tem mais a ver com conformidade com padrões), não apenas uma ferramenta transitória para aprendizado imediato. Algumas dessas opções, quando usadas em conjunto, ajudam a diagnosticar problemas que muitas vezes a gente acaba cometendo por acidente, digitação errada ou falta de atenção, tais como os seguinte (entre muitas outras possibilidades):

  • uma variável que pode ficar não-inicializada se você seguir um certo fluxo de execução (pense numa variável que pode assumir valores diferentes se você seguir por ramos diferentes de uma tripa longa de if/else if/.../else ou um switch com muitos cases, e você esquece de setar a variável em apenas um deles);

  • ou usar, sem querer, “=” em vez de “==” numa comparação;

  • numa edição grande de código, alguém esquece de apagar a declaração de algumas variáveis que acabaram ficando sem uso.

Problemas desses tipos não chegam a ser erros, mas diminuem a eficiência do programa ou introduzem silenciosamente comportamentos duvidosos em casos de uso que podem ficar dormentes por muito tempo. Mas, por causa da otimização (“-O2”), o compilador acaba visitando pedaços de código que talvez raramente ocorressem, mas que podem fazer toda a diferença entre um programa que sempre funciona e outro que dá pau de forma aparentemente aleatória de vez em quando. Às vezes, o otimizador até descobre que um pedaço de código que efetivamente nunca será executado, que “-Wall” vai forçar a indicar, e “-Werror” vai obrigá-lo a corrigir, se quiser que o programa possa ser compilado.

Maratonei o curso de C moderno do canal papo binário até a parte de alocação dinâmica(vou assistir até o fim do curso, mas agora vou mais devagar, estava terminando o dia completamente mentalmente esgotado), e isso também me trouxe muita coisa de aprendizado!!! Entendi muitas coisas que antes estavam obscuras em minha mente!


Não conhecia o curso. A primeira parte da aula de ponteiros, em particular, tem vários pequenos problemas. O mais grave é que ele confunde “*p+1” com “*(p+1)”, mas há outras imprecisões relativamente perigosas — e eu digo isso porque eu tinha dificuldades justamente com relação a este assunto —, tais como confusões e usos cruzados confusos entre arrays (particularmente strings) e ponteiros.

Ahhhh... Como eu queria que na faculdade tivesse me ensinado ponteiros e arrays de forma "mais correta", mas precisa!!! Pois foi me ensinado de uma maneira muito simples, e graças a esse desse desafio que fiz a mim mesmo, tive a oportunidade de conhecer ponteiros e funções, parametros, declarações de uma forma muito melhor, e muito mais verdadeira!


É... Cuidado com a informação sobre essa relação entre arrays e ponteiros. Não sei como você recebeu a informação — você pode ter uma clarividência que eu não tenho —, mas a maneira como ele apresentou parece com a confusão mental que eu tinha respeito, e eu sinceramente temo que ele esteja repassando mais confusão.

Outra coisa, lembrando agora, de que eu não gostei muito no vídeo, e que lembra a maneira como eu mesmo aprendi, é a ideia de associar ponteiros com variáveis. Eu acho importante entender que ponteiros são valores. Tais valores podem ser guardados em variáveis de tipos apropriados, e depois obtidos de volta a partir dessas variáveis, mas o fundamental é o valor, o que ele representa e como usar; variáveis de tipos ponteiros são apenas veículos para passar tais valores para outros locais e outros momentos do programa.

Não vou me estender muito sobre esses assuntos por causa da hora (eu estou caindo de sone, e já tinha de estar dormindo faz tempo!). Se você achar que ainda tem dúvidas, pode perguntar. Tentarei responder assim que puder (mas dezembro é um mês complicado...).

Depois eu comento com cuidado a nova versão do seu programa. Já olhei, e já vi algumas oportunidades de melhoria.


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


8. Re: Imprimir endereço de cada valor num vetor

Paulo
paulo1205

(usa Ubuntu)

Enviado em 20/12/2019 - 03:05h

princknoby escreveu:

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

typedef struct dados {
char nome[100], CPF[30];
int age;
}
dados;


Evite misturar Português e Inglês nos nomes de identificadores no mesmo programa. Escolha ou ou escolha o outro, mas procure deixar uniforme.

Procure também dar nomes mais significativos, e mesmo sentidos melhores aos dados que você vai representar. Por exemplo, tudo que um programa manipular são dados; desse modo um tipo de dados chamado “dados” não é muito informativo. E uma informação de idade é uma informação que vai deixando de ser relevante na medida em que o tempo passa. Se você tivesse uma data de nascimento, essa informação não caducaria, e você poderia obter a idade com muita facilidade, por meio de uma simples subtração da data atual.



void fflush_s() {
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF);
}


Parabéns por ter feito ch ser do tipo int. Muita gente erra nisso, colocando o tipo como char, atrapalhando o funcionamento de fgetc().



void dataEntrys(dados * pD) {
printf(">Nome: ");
if (scanf("%99[^\n]", pD->nome) == 0) {


Testar se o resultado de scanf() é igual a zero é insuficiente, pois pode haver erros que a façam retornar EOF (-1). Seria melhor você comparar o resultado com 1, que é o valor retornado quando a conversão é bem sucedida, e considerar que os casos em que o retorno é diferente de 1 como erro.

Adicionalmente, você pode aproveitar essa chamada a scanf() para já consumir a quebra de linha após o nome, acrescentando o seguinte ao final da string de formatação: "%*1[\n]".

    printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">CPF: ");
if (scanf("%99[^\n]", pD->CPF) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">Idade: ");
if (scanf("%d", & pD->age) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();
}


void showReady(dados * pD, int n) {


Aqui você poderia colocar o parâmetro com o tipo const dados *, pois você não vai alterar os dados apontados.

  if(system("clear") == -1) { 


Aqui, de novo, você tem um caso em que o teste do valor de retorno não cobre todos os possíveis erros. -1 é o valor de retorno apenas quando o sistema (POSIX; outros sistemas podem ter outras situações) não consegue criar um processo novo no qual o comando externo será executado (o que seria feito internamente com uma chamada a fork(), vfork() ou equivalente). Existem várias outras possibilidades de falha mesmo após o processo ter sido criado, tais como não conseguir chamar, dentro do processo criado, o interpretador de comandos (no mundo POSIX, /bin/sh) que vai interpretar o comando (nesse caso, a função retorna o valor 127). Dentro do interpretador, pode haver falhas: se for excedido algum limite (nº de processos, nº de descritores de arquivo, quantidade de memória etc.), se alguma variável de ambiente afetar a execução do próprio shell ou do comando que você mandou ele executar (incluindo a variável PATH, as que controlam locales e as que afetam o tipo e as características do terminal), ou mesmo se o comando não existir. Mesmo que o comando exista, pode haver falhas se você não estiver com um terminal ativo, se o o tipo do terminal não for reconhecido ou, de novo, se algum recurso tiver o limite excedido.

Fora isso tudo, chamar um programa externo é muito ineficiente, e é uma forma de garantir que seu programa vai ficar dependente do sistema operacional que você usou para criá-lo, impedindo-o deliberadamente de rodar bem em outros sistemas. Se é para amarrar a um sistema específico, então faça isso de um modo mais eficiente, que não envolva a criação de subprocessos nem execução de comandos remotos. Algo parecido com o seguinte pode ser usado em sistemas POSIX com terminfo/curses.

#include <term.h>
#include <unistd.h>

void release_term(void){
if(cur_term)
putp(exit_ca_mode);
}

void prepare_term(void){
if(!cur_term && isatty(STDOUT_FILENO)){
int errcode;
if(setupterm(NULL, STDOUT_FILENO, &errcode)==OK){
putp(enter_ca_mode);
atexit(release_term); // Chama a rotina de liberação automaticamente quando o programa terminar.
}
}
}

void clear_screen(void){
if(cur_term)
putp(clear_screen);
}

/* ... */

int main(void){
prepare_tty(); // Inicializa terminal, desde que haja um terminal válido.
/* ... */
clear_screen(); // Só limpa realmente a tela se a inicialização do terminal tiver tido sucesso.
}


Mais detalhes e formas de fazer em outros sistemas já foram abordadas aqui mesmo na comunidade do VoL (por exemplo, em https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu..., que fala também sobre por que eu não gosto de usar system()).

    printf("system clear failed!\n"); 
}
for (int i = 0; i < n; i++) {
printf(">Name: %s\n", pD[i].nome);
printf(">CPF: %s\n", pD[i].CPF);
printf("Idade: %d\n\n\n", pD[i].age);
}
if(scanf("%*c") == 0) {
printf("scanf falhou!\n");
}
}

int main(int argc, char * argv[]) {
dados * pD;
int n = 0, option;

pD = malloc(n * sizeof(char));
do {
if(system("clear") == -1) return 1;
printf("Chosse option: ");
if (scanf("%d", & option) == 0) return 1;
fflush_s();

if (option == 1) {
dataEntrys( & pD[n]);
n++;
pD = realloc(pD, n * sizeof(char));


Aqui você cometeu um erro comum, mas potencialmente sério.

A função realloc() pode falhar, caso não consiga encontrar memória suficiente para fazer a realocação que você solicitou. Nesse caso, ela retorna um ponteiro nulo, mas dá garantia de que a memória referida pelo ponteiro original não será perdida.

No entanto, como você sobrescreve o valor do ponteiro original, em caso de erro de realocação você perde a referência à área apontada anteriormente, mesmo que ela continue válida, e passa a apontar para uma memória inválida.

A forma correta de fazer a realocação, portanto, se parece com o seguinte.

	void *new_ptr=realloc(current_ptr, new_size);
if(new_ptr){
current_ptr=new_ptr;
current_size=new_size;
}
else{
/* Erro de realocação. */
/*
Os valores de current_ptr e current_size estão preservados, bem como os
dados relativos a eles. Você pode optar por continuar trabalhando com os
dados antigos, talvez emitindo apenas um alerta para o usuário de que não
foi possível acrescentar novos elementos, ou pode escolher abortar o pro-
grama.
*/
}


    } else if (option == 2) {
showReady(pD, n);
} else if (option == 0) {
break;
} else {
printf("Other option: ");
if (scanf("%d", & option) == 0) {
break;
}
}
} while (option != 0);

free(pD);
return 0;
}




... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


9. Re: Imprimir endereço de cada valor num vetor [RESOLVIDO]

João Paulo
princknoby

(usa Arch Linux)

Enviado em 02/01/2020 - 17:29h


paulo1205 escreveu:

princknoby escreveu:

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

typedef struct dados {
char nome[100], CPF[30];
int age;
}
dados;


Evite misturar Português e Inglês nos nomes de identificadores no mesmo programa. Escolha ou ou escolha o outro, mas procure deixar uniforme.

Procure também dar nomes mais significativos, e mesmo sentidos melhores aos dados que você vai representar. Por exemplo, tudo que um programa manipular são dados; desse modo um tipo de dados chamado “dados” não é muito informativo. E uma informação de idade é uma informação que vai deixando de ser relevante na medida em que o tempo passa. Se você tivesse uma data de nascimento, essa informação não caducaria, e você poderia obter a idade com muita facilidade, por meio de uma simples subtração da data atual.



void fflush_s() {
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF);
}


Parabéns por ter feito ch ser do tipo int. Muita gente erra nisso, colocando o tipo como char, atrapalhando o funcionamento de fgetc().



void dataEntrys(dados * pD) {
printf(">Nome: ");
if (scanf("%99[^\n]", pD->nome) == 0) {


Testar se o resultado de scanf() é igual a zero é insuficiente, pois pode haver erros que a façam retornar EOF (-1). Seria melhor você comparar o resultado com 1, que é o valor retornado quando a conversão é bem sucedida, e considerar que os casos em que o retorno é diferente de 1 como erro.

Adicionalmente, você pode aproveitar essa chamada a scanf() para já consumir a quebra de linha após o nome, acrescentando o seguinte ao final da string de formatação: "%*1[\n]".

    printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">CPF: ");
if (scanf("%99[^\n]", pD->CPF) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();

printf(">Idade: ");
if (scanf("%d", & pD->age) == 0) {
printf("Erro ao ler o nome! Encerrando program...");
exit(1);
}
fflush_s();
}


void showReady(dados * pD, int n) {


Aqui você poderia colocar o parâmetro com o tipo const dados *, pois você não vai alterar os dados apontados.

  if(system("clear") == -1) { 


Aqui, de novo, você tem um caso em que o teste do valor de retorno não cobre todos os possíveis erros. -1 é o valor de retorno apenas quando o sistema (POSIX; outros sistemas podem ter outras situações) não consegue criar um processo novo no qual o comando externo será executado (o que seria feito internamente com uma chamada a fork(), vfork() ou equivalente). Existem várias outras possibilidades de falha mesmo após o processo ter sido criado, tais como não conseguir chamar, dentro do processo criado, o interpretador de comandos (no mundo POSIX, /bin/sh) que vai interpretar o comando (nesse caso, a função retorna o valor 127). Dentro do interpretador, pode haver falhas: se for excedido algum limite (nº de processos, nº de descritores de arquivo, quantidade de memória etc.), se alguma variável de ambiente afetar a execução do próprio shell ou do comando que você mandou ele executar (incluindo a variável PATH, as que controlam locales e as que afetam o tipo e as características do terminal), ou mesmo se o comando não existir. Mesmo que o comando exista, pode haver falhas se você não estiver com um terminal ativo, se o o tipo do terminal não for reconhecido ou, de novo, se algum recurso tiver o limite excedido.

Fora isso tudo, chamar um programa externo é muito ineficiente, e é uma forma de garantir que seu programa vai ficar dependente do sistema operacional que você usou para criá-lo, impedindo-o deliberadamente de rodar bem em outros sistemas. Se é para amarrar a um sistema específico, então faça isso de um modo mais eficiente, que não envolva a criação de subprocessos nem execução de comandos remotos. Algo parecido com o seguinte pode ser usado em sistemas POSIX com terminfo/curses.

#include <term.h>
#include <unistd.h>

void release_term(void){
if(cur_term)
putp(exit_ca_mode);
}

void prepare_term(void){
if(!cur_term && isatty(STDOUT_FILENO)){
int errcode;
if(setupterm(NULL, STDOUT_FILENO, &errcode)==OK){
putp(enter_ca_mode);
atexit(release_term); // Chama a rotina de liberação automaticamente quando o programa terminar.
}
}
}

void clear_screen(void){
if(cur_term)
putp(clear_screen);
}

/* ... */

int main(void){
prepare_tty(); // Inicializa terminal, desde que haja um terminal válido.
/* ... */
clear_screen(); // Só limpa realmente a tela se a inicialização do terminal tiver tido sucesso.
}


Mais detalhes e formas de fazer em outros sistemas já foram abordadas aqui mesmo na comunidade do VoL (por exemplo, em https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu..., que fala também sobre por que eu não gosto de usar system()).

    printf("system clear failed!\n"); 
}
for (int i = 0; i < n; i++) {
printf(">Name: %s\n", pD[i].nome);
printf(">CPF: %s\n", pD[i].CPF);
printf("Idade: %d\n\n\n", pD[i].age);
}
if(scanf("%*c") == 0) {
printf("scanf falhou!\n");
}
}

int main(int argc, char * argv[]) {
dados * pD;
int n = 0, option;

pD = malloc(n * sizeof(char));
do {
if(system("clear") == -1) return 1;
printf("Chosse option: ");
if (scanf("%d", & option) == 0) return 1;
fflush_s();

if (option == 1) {
dataEntrys( & pD[n]);
n++;
pD = realloc(pD, n * sizeof(char));


Aqui você cometeu um erro comum, mas potencialmente sério.

A função realloc() pode falhar, caso não consiga encontrar memória suficiente para fazer a realocação que você solicitou. Nesse caso, ela retorna um ponteiro nulo, mas dá garantia de que a memória referida pelo ponteiro original não será perdida.

No entanto, como você sobrescreve o valor do ponteiro original, em caso de erro de realocação você perde a referência à área apontada anteriormente, mesmo que ela continue válida, e passa a apontar para uma memória inválida.

A forma correta de fazer a realocação, portanto, se parece com o seguinte.

	void *new_ptr=realloc(current_ptr, new_size);
if(new_ptr){
current_ptr=new_ptr;
current_size=new_size;
}
else{
/* Erro de realocação. */
/*
Os valores de current_ptr e current_size estão preservados, bem como os
dados relativos a eles. Você pode optar por continuar trabalhando com os
dados antigos, talvez emitindo apenas um alerta para o usuário de que não
foi possível acrescentar novos elementos, ou pode escolher abortar o pro-
grama.
*/
}


    } else if (option == 2) {
showReady(pD, n);
} else if (option == 0) {
break;
} else {
printf("Other option: ");
if (scanf("%d", & option) == 0) {
break;
}
}
} while (option != 0);

free(pD);
return 0;
}




... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


Obrigado pela resposta PaulO!
Peço perdão pela demora em dar andamento ao tópico, esse fim de ano foi bem corrido rsrs' E falando nisso, um feliz ano novo para o senhor!

Em relação as testes de return's ainda não estou conseguindo entender bem. Até onde me foi ensinado na faculdade, eu nem sabia que eu deveria testar essas "coisas". E sobre o que as funções retornam em caso de erro, estou tentando usar o comando man <scanf> (por exemplo) e lendo.. Mas confesso que os textos ainda são bem confusos pra mim, é uma linguagem que ainda não estou acostumado, com termos que para mim ainda são muito técnicos. Muito obrigado sobre sua explicação de alguns retornos foram muito úteis para mim!!!

A respeito do realloc, realmente eu não me toquei que ele poderia dar errado, imaginei que se faltasse memória ele daria erro automaticamente me retornando um stack overflow ou algum outro erro... Obrigado pela informação e pelos exemplos de funções que me foram passadas.

Sobre o comando system quando migrei para o linux e o comando system("pause") não funcionava, quando fui pesquisar, acabei descobrindo que o comando system não é muito seguro mesmo de se usar para fazer coisas simples demais, que podemos fazer sem chamar o sistema. Mas não conseguia imaginar como limpar a tela do terminal ou do cmd sem chamar o system("cls")/system("clear), então, novamente obrigado pela função que me passou!

Acho que é isso então, muito obrigado pelo conhecimento passado, vou encerrar o tópico aqui, mas deixo apenas mais um última pergunta. O que o senhor me recomenda estudar? Poderia me indicar algum livro ou algum curso no youtube? Achei que estava sendo bem ensinado no papo binário (mesmo com os erros, ainda consegui aprender mais que com o meu professor(doutor) na universidade). Então, em um pedido de socorro kkkkk o senhor pode me recomendar algum material no qual o senhor confie??

Desde de já,
Muito obrigado.

E um feliz ano novo!


10. Re: Imprimir endereço de cada valor num vetor [RESOLVIDO]

Adriano Siqueira
adrisiq

(usa KDE Neon)

Enviado em 02/01/2020 - 22:18h

De uma forma bem simplista, acho que poderia ser feito assim:
#include <stdio.h>
#include <stdlib.h>

int main() {
const int quantidade = 10;

float vetor[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

for(int i = 0; i < quantidade; i++) {
printf("Valor: %.2f \t Endereço: %p \n", vetor[i], &vetor[i]);
}

return 0;
}







Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts