Ir ao conteúdo
  • Cadastre-se

C Alocação de memória em C


Carolmarton

Posts recomendados

"Quando passamos um vetor para uma função em C, esta alocará um ponteiro para
o elemento zero do vetor. Por que a Linguagem C simplesmente não cria uma nova cópia local
do vetor, como fazer com os números inteiros?"

 

Essa é uma questão de um livro de algoritmo, mas não consigo achar uma resposta pra isso.

Tudo que acho é sobre o tamanho.

Talvez eu não esteja conseguindo entender de fato a questão.

Alguém sabe me responder?

Desde já agradeço.

Link para o comentário
Compartilhar em outros sites

  • Moderador

Caro usuário,

 

Seja bem-vindo ao Clube do Hardware.

 

No intuito de servir como fonte de pesquisa no caso de instituições de ensino, informamos que incorrer no resolvimento por completo de questões relacionadas a disciplinas escolares de cursos técnicos e faculdades podem ser revistas e removidas pela Equipe de Moderação do Clube do Hardware.

 

Para sanar dúvidas sobre esse tipo de problema, por gentileza, publique o passo a passo do desenvolvimento da questão, projeto, monografia ou conteúdo em dúvida para que possamos analisar se a resposta está correta ou não, ou para que possa ser auxiliado com a dúvida no desenvolvimento do exercício.

 

Infelizmente, não há como resolver os trabalhos pelos usuários. O objetivo do Clube do Hardware é auxiliar seus usuários a encontrar soluções para que possam sanar suas dúvidas, e não de trazer soluções prontas para seus usuários. Além disso, copiar e colar respostas que não são de autoria própria do qualquer usuário é considerado plágio, o que é ilegal.

 

Esperamos que compreenda.

 

Atenciosamente,

Equipe Clube do Hardware

  • Triste 1
Link para o comentário
Compartilhar em outros sites

16 horas atrás, Carolmarton disse:

Por que a Linguagem C simplesmente não cria uma nova cópia local
do vetor, como fazer com os números inteiros?

 

Imagino que pretendia escrever "faz".  

 

Um ponteiro tem 4 ou 8 bytes. Um vetor ou uma estrutura pode ter qualquer tamanho, e esse é um motivo. 

 

Criar uma cópia local não seria "simples" e muitas vezes não seria esperto. Alguém teria que pagar a conta pelo tempo e memória gastos pela cópia.

 

Talvez em linguagens como Pascal, que foi escrita para ensinar a programar (e do jeito que o prof. Wirth achava certo)  faça mais sentido ter as opções de passar parâmetros por valor (cópia) ou referência (ponteiro).

 

C foi escrita para escrever um sistema e é bem diferente. O normal é ter uma função recebendo um endereço de um conjunto de dados e fazendo sua "função" :) retornando um resultado.

 

Se for preciso em C passar uma cópia de um vetor você sempre pode colocar o cidadão dentro de uma struct e passar a estrutura por valor. Ou receber o endereço e retornar o endereço de algo que contenha a cópia do vetor. Ou passar já a cópia para a função, claro.

 

Em C tem esse lance de array to pointer decay. Um vetor é sempre substituído pelo seu endereço. Todo vetor é um ponteiro, mas nem todo ponteiro é um vetor. Essa é uma das razões de C ter 50 anos de sucesso em systems programming: menos palavras-chave, mais flexibilidade, mais velocidade. Praticamente tudo que se usa hoje foi escrito em C. Isso Inclui todos os sistemas operacionais.

 

No geral tudo em C é base+deslocamento, bem próximo da óbvia representação das coisas na memória. Então passar o endereço base sempre dá certo. Passar uma cópia inteira de seja lá o que for já envolveria o espaço, o tempo e saber sempre o tamanho. Criaria muito mais problemas do que soluções. 

 

 

  • Obrigado 2
Link para o comentário
Compartilhar em outros sites

17 horas atrás, Carolmarton disse:

"Quando passamos um vetor para uma função em C, esta alocará um ponteiro para
o elemento zero do vetor. Por que a Linguagem C simplesmente não cria uma nova cópia local do vetor, como fazer com os números inteiros?"


Essa parecia com aquelas perguntas que eu fazia pra minha mãe quando eu era pequeno... 
-mãe, porque as girafas tem o pescoço comprido?


Parece brincadeira, mas é que quem fez realmente poderia ter escolhido que funcionasse de n outras formas. Mas provavelmente já deve ter lido sobre maquina de turing e como seria difícil pra nos hoje se acontecesse um cataclisma e tivessemos que refazer tudo de novo do zero.

então, pensando por esse aspecto, imagina um tempo no passado em que não existia quase nada pra pra inicar... não tinha java, não tinha orientação a objetos... só endereços, pilhas e instruções a se realizar sobre esses enrereços e pilhas...

No caso do C ele reflete ainda um pouco dessa realidade. A função quando recebem o ponterio não faz ideia do que está recebendo além de um endereço na memoria. O compilador até dá uma ajuda na hora de compilar evitando que a gente erre dizendo que estamos enviando um ponteiro de tipo errado para uma função. Se declarar a função pra receber um ponteiro de um tipo de dado, ele vai reclamar. Mas mesmo assim, se você quiser não manter a coerencia, pode declarar a função para receber um (valor de enereço como) inteiro no lugar de um ponteiro.

A função que recebe um ponteiro recebe um endereço de memoria que é o começo dos dados que está passando por parametro. Não sabe onde termina o dado que deseja trabalhar. Por isso ela não tem nem como fazer essa cópia dentro da função. Mas se você enviar o tamanho junto como parametro a função, se você quiser, a partir desse tamanho, tem como saber onde começa e onde termina o dado que enviou e assim poderia criar uma cópia se quisesse. Mas pense... seria realmente necessário?

 

  • Curtir 1
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

5 horas atrás, Carolmarton disse:

@DiF era uma questão do livro de algoritmo que estou estudando por conta, não compreendi e por isso pedi ajudar aqui.

 

 

 

@CarolmartonVocê entendeu o que eu expliquei? Qual o livro de está consultando?

 

22 horas atrás, codigo rápido disse:

então, pensando por esse aspecto, imagina um tempo no passado em que não existia quase nada pra pra inicar... não tinha java, não tinha orientação a objetos... só endereços, pilhas e instruções a se realizar sobre esses enrereços e pilhas...

No caso do C ele reflete ainda um pouco dessa realidade

 

Essa ainda é a realidade hoje. A frequência mudou, o número de instruções mudou, tem as GPU, tem multi-core, tem multi-threading, branch prediction, varios níveis de cache,  mas o mundo de C nos anos 70 nos PDP11 --- possivelmente a máquina em que C de fato tomou forma --- ainda é bem similar.

 

Note que naquela época já havia orientação a objetos, não tinha Java mas tinha um precursor, uma tal de p-machine que gerava p-code numa máquina virtual, quem diria. java não foi original nisso. Nem C++.

 

E nessa época um dos sistemas operacionais da IBM era o VM/370. E o VM significa exatamente isso, virtual machine. 

 

22 horas atrás, codigo rápido disse:

A função quando recebem o ponterio não faz ideia do que está recebendo além de um endereço na memoria

 

22 horas atrás, codigo rápido disse:

A função que recebe um ponteiro recebe um endereço de memoria que é o começo dos dados que está passando por parametro. Não sabe onde termina o dado que deseja trabalhar. Por isso ela não tem nem como fazer essa cópia dentro da função

 

Existe uma convenção de chamada, e sem ela não teria função alguma. A função sabe exatamente o que está recebendo. Não dá pra explicar isso aqui, mas em resumo ANTES de chamar uma função os argumentos da função são emplilhados, em geral da direita para a esquerda, e estou simplificando um pouco. A convenção de chamada mais comum hoje é cdecl, praticamernte aquela do C dos anos 70. Até hoje.

 

Depois dos argumentos é emplihado o endereço de retorno, que faz o programa retornar para a linha seguinte a aquela em que a função foi chamada.

 

Pode ser esse o único argumento, mas sempre vai estar lá, ou vai ter um clássico erro de segmentação ou um stack overflow :) 

 

Ao encerrar o código a função limpa a pilha e executa um retorno para esse preciso endereço e a vida segue, no caso o programa segue.

 

A função sabe exatamente o que fazer, e quais são e como são os argumentos. O linker ao gerar o executável preparou tudo, a partir do código compilado de todas as muitas partes que compõe um programa.

 

E afinal porque não "copiar simplesmente"  os argumentos?

 

EXEMPLO em C, copiando um vetor

 

typedef struct { char vetor[80]; } Pacote;
int f1(Pacote p) { return p.vetor[32]; }

int main(void)
{
    Pacote pct = { 0 };
    int i = f1(pct);
    return 0;
}

 

Esse programa nada complexo copia o parâmetro. Veja o código gerado:
 

f1:
        push    rbp
        mov     rbp, rsp
        movzx   eax, BYTE PTR [rbp+48]
        movsx   eax, al
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 96
        mov     QWORD PTR [rbp-96], 0
        mov     QWORD PTR [rbp-88], 0
        mov     QWORD PTR [rbp-80], 0
        mov     QWORD PTR [rbp-72], 0
        mov     QWORD PTR [rbp-64], 0
        mov     QWORD PTR [rbp-56], 0
        mov     QWORD PTR [rbp-48], 0
        mov     QWORD PTR [rbp-40], 0
        mov     QWORD PTR [rbp-32], 0
        mov     QWORD PTR [rbp-24], 0
        push    QWORD PTR [rbp-24]
        push    QWORD PTR [rbp-32]
        push    QWORD PTR [rbp-40]
        push    QWORD PTR [rbp-48]
        push    QWORD PTR [rbp-56]
        push    QWORD PTR [rbp-64]
        push    QWORD PTR [rbp-72]
        push    QWORD PTR [rbp-80]
        push    QWORD PTR [rbp-88]
        push    QWORD PTR [rbp-96]
        call    f1
        add     rsp, 80
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

 

Sem entrar no mérito do que significa cada coisa, apenas para referência, já que esse é um tópico introdutório e não faz sentido escrever demais. E provavelmente não sou a pessoa para explicar isso...

 

Todos esses mov e push são a função main fazendo a simples cópia. É código, instruções a rodar, e espaço na pilha sendo gasto para copiar todos os 80 bytes. main empilha todos os dados antes de chamar a função, e ela se vira com a pilha e depois retorna para a linha seguinte de main, ou nesse caso para a atribuição, onde copia o valor de retorno de f1 para i

 

E se fosse por referência?

 

typedef struct { char vetor[80]; } Pacote;
int f1(Pacote*  p) { return p->vetor[32]; }

int main(void)
{
    Pacote pct = { 0 };
    int i = f1(&pct);
    return 0;
}

 

Compare

 

f1:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        movzx   eax, BYTE PTR [rax+32]
        movsx   eax, al
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 96
        mov     QWORD PTR [rbp-96], 0
        mov     QWORD PTR [rbp-88], 0
        mov     QWORD PTR [rbp-80], 0
        mov     QWORD PTR [rbp-72], 0
        mov     QWORD PTR [rbp-64], 0
        mov     QWORD PTR [rbp-56], 0
        mov     QWORD PTR [rbp-48], 0
        mov     QWORD PTR [rbp-40], 0
        mov     QWORD PTR [rbp-32], 0
        mov     QWORD PTR [rbp-24], 0
        lea     rax, [rbp-96]
        mov     rdi, rax
        call    f1
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

 

Qual a diferença?

 

Os mov estão todos lá ainda, e alguém vai pagar a conta. Mas são da inicialização de pct, essa linha:

 

    Pacote pct = { 0 };

 

E por isso é bom pensar antes de usar calloc() ao invés de malloc() por exemplo. Nem sempre é preciso inicializar uma área. E por isso linguagens que fazem isso por padrão são odiadas por alguns: alguém sempre paga a conta. Pra que inicializar um buffer de recepção por exemplo?

 

De volta ao programa:

 

Todos os push foram substituídos por um só... Imagine se essa função for recursiva... E se alocar uns megabytes ou mais. Stack Overflow está na esquina já...

 

E se passar apenas o ponteiro e não inicializar nada?

 

typedef struct { char vetor[80]; } Pacote;
int f1(Pacote*  p) { return p->vetor[32]; }

int main(void)
{
    Pacote pct;
    int i = f1(&pct);
    return 0;
}

 

Gera

 

f1:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        movzx   eax, BYTE PTR [rax+32]
        movsx   eax, al
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 96
        lea     rax, [rbp-96]
        mov     rdi, rax
        call    f1
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

 

e dá pra imaginar quantas vezes mais rápido isso via ser, só contando as instruções. E o tamanho do programa diminui. E o uso do caro e lento stack caiu para apenas 4 ou 8 bytes...

 

Essa é a realidade. E por isso se usa C para quase tudo: é fácil escolher como e quando fazer o que. A linguagem não fica no seu caminho. E não atrasa o percurso. ;) 

 

a bit off-topic: Todo pacote importante de Python por exemplo é escrito em C. E a maior demanda para o desenvolvimento de Python é um exército de grandes desenvolvedores. Em C. 

 

Na prática é muito comum uma função operar sobre dados e devolver os dados modificados, e isso iria envolver uma nova cópia no retorno, tornando as coisas ainda piores se considerar o que já expliquei.

Link para o comentário
Compartilhar em outros sites

Eu cometi um engano ai.
Me referi apenas a passagem de ponteiros por parametro, sendo que a questão é sobre a passagem de vetor.

Em 16/09/2021 às 06:51, Carolmarton disse:

Quando passamos um vetor para uma função em C


Provavelmente porque prefiro usar apenas ponteiros como parametros.
Ponteiros, quando quero que os dados sejam modificados e cópia quando não tem necessidade de alterar o dado original.

 

Citação

Existe uma convenção de chamada, e sem ela não teria função alguma. A função sabe exatamente o que está recebendo. Não dá pra explicar isso aqui, mas em resumo ANTES de chamar uma função os argumentos da função são emplilhados, em geral da direita para a esquerda, e estou simplificando um pouco. A convenção de chamada mais comum hoje é cdecl, praticamernte aquela do C dos anos 70. Até hoje.


Quando é ponteiros sendo passados... tudo pode acontecer. A função não sabe que tipo de dados é do endereço passado por parametro. O compilador só reclama porque existe o analisador semantico (atualmente e provavelmente nos primeiros Cs não devia ser tão bom assim... não sei... mas). Antes de compilar o analisador semantico tenta encontrar passagens de parametros de tipos diferentes. Mas nada impede de mandar o tipo que quiser para uma função que recebe o ponteiro de qualuqer coisa.
Mas existe formas de contornar esse analisador semantico...pra demonstrar, fiz um exemplo que não da erro. Muitas vezes a gente tem problemas de serialização na web porque o analisador semantico não consegue olhar tão profundamente. Podem compilar e executar que vai funcionar sem medo:
 


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

typedef struct
{
	int A;
	char B[10];
}A;

typedef struct
{
	int A;
	char B[10];
}B;

int lascaTudo(long unsigned int c)
{
	A *a = (A*)c;
	printf("%i - %s\n", a->A, a->B);
	return 1;
}

int main()
{
	A a;
	a.A=101;
	strcpy(a.B, "opa!");
	lascaTudo((long unsigned int)&a);

	B b;
	b.A=100;
	strcpy(b.B, "eita!");
	lascaTudo((long unsigned int)&b);

	return 0;
}







 

Link para o comentário
Compartilhar em outros sites

10 horas atrás, codigo rápido disse:

Quando é ponteiros sendo passados... tudo pode acontecer. A função não sabe que tipo de dados é do endereço passado por parametro. O compilador só reclama porque existe o analisador semantico (atualmente e provavelmente nos primeiros Cs não devia ser tão bom assim... não sei... mas). 

 

Convenção de chamada --- calling convention --- nada tem a ver com o compilador. Isto é uma coisa de tempo de execução e só vai ter alguma importância na compilação se estiver programando em um cenário que tenha mais de um compilador e/ou mais de uma linguagem, como código C para ser usado em java ou C++ ou Python por exemplo.

 

A convenção dominante é a CDECL e provavelmente seu compilador aceita isso em seu exemplo, o especificador _cdecl. E não teria nenhum efeito visível nesse contexto pela razão que eu expliquei acima.
 

10 horas atrás, codigo rápido disse:

Antes de compilar o analisador semantico tenta encontrar passagens de parametros de tipos diferentes


Note que o analisador semântico não roda "antes de compilar". O que roda antes de compilar é o pré-processador, que trata coisas como include, pragma, macros e define 

 

10 horas atrás, codigo rápido disse:

A função não sabe que tipo de dados é do endereço passado por parametro

 

Sim. Apenas valores são passados na pilha antes do endereço de retorno. O que eles são e como tratar é problema do código, do programa. A convenção de chamada apenas diz a ordem e o modo como os argumentos são empilhados, e são valores.

 

No PDP-11 original que não chegava a 20Mhz de clock e com muita sorte uns 32K de RAM era praticamente só isso. Mas hoje em dia os processadores tem legiões de registradores enormes e então alguns argumentos são passados em registradores e o resto no stack, para uma maior performance.

 

De volta ao exemplo

 

typedef struct { char vetor[80]; } Pacote;
int f1(Pacote*  p) { return p->vetor[32]; }

int main(void)
{
    Pacote pct;
    int i = f1(&pct);
    return 0;
}

 

Esse programa por exemplo passa o vetor todo como argumento. No gcc 12 eis o que acontece

 

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 96
        lea     rax, [rbp-96]
        mov     rdi, rax
        call    f1
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

 

Note que o parâmetro não vai na pilha, apenas o endereço de retorno.

 

E na função
 

f1:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        movzx   eax, BYTE PTR [rax+32]
        movsx   eax, al
        pop     rbp
        ret

 

E você vê que o endereço do vetor veio no registrador AX, e usar isso é muito mais rápido que usar o stack, a popular pilha.

 

Sobre seu exemplo

 

Se ver os exemplos com o código gerado pelas funções que eu mostrei vai entender que

 

int lascaTudo(long unsigned int c)
{
	A *a = (A*)c;
	printf("%i - %s\n", a->A, a->B);
	return 1;
}

 

Essa função recebe o argumento, o único argumento, e é um long int. Só isso vai estar lá. E o comando

        A*        a = (A*) c;

 

apenas passa a ver o VALOR recebido em  c e que era um long int, como um ponteiro para uma struct. 

 

Isso está ok desde que essas coisas tenham o mesmo tamanho... Cuidado. Se estiver compilando em 64 bits os ponteiros terão 64 bytes e o long int 32 bits então estará numa zona de risco.

 

 

 

 

 

Link para o comentário
Compartilhar em outros sites

Citação

Isso está ok desde que essas coisas tenham o mesmo tamanho... Cuidado. Se estiver compilando em 64 bits os ponteiros terão 64 bytes e o long int 32 bits então estará numa zona de risco.


Sim. O tamanho dos segmentos importa. As vezes nem se os tipos de dados dentro das structs importam tanto, já que o cast será realizado de qualquer jeito, não importando o tipo do dado. Mas ai o risco é maior ainda.

Pra contornar o problema da diferença entre os tamanhos em bits dos endereços de uma maquina para outra segue exemplo de captura e fonte demonstrando com usar:


 


// por JaredPar e como mostrado por Contango
// fonte:
// https://stackoverflow.com/questions/1505582/determining-32-vs-64-bit-in-c


#if _WIN32 || _WIN64
#if _WIN64
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif

// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif



Quem trabalha com C e queira extrapolar não pode ser ignorante sobre isso.

Link para o comentário
Compartilhar em outros sites

4 horas atrás, codigo rápido disse:

Pra contornar o problema da diferença entre os tamanhos em bits dos endereços de uma maquina para outra segue exemplo de captura e fonte demonstrando com usar:

 

Essa suposta demonstração em tempo de execução não mostra muito. Sugiro esquecer.

 

O código como escreveu está apenas errado porque não funciona em 64 bits e não testa pelo ambiente. E usar esses #define como no exemplo do stack overflow que mostrou não vai ajudar muito. O programa vai cancelar apenas...

 

veja o simples no seu código:

 

int lascaTudo(long unsigned int c)
{
	printf("Tamanho do 'long unsigned int' %zu\n", sizeof(long unsigned int));
	printf("Tamanho do 'struct A*' %zu\n", sizeof (struct A*));
	A* a = (A*)c;
	printf("%i - %s\n", a->A, a->B);
	return 1;
}

 

Mudei duas linhas para mostrar o simples: basta um sizeof para impedir seu programa de cancelar... Não precisa de defines obscuros que podem nem estar presentes em todas as plataformas.

 

Rodando seu código em 32 bits

 

Tamanho do 'long unsigned int' 4
Tamanho do 'struct A*' 4
101 - opa!
Tamanho do 'long unsigned int' 4
Tamanho do 'struct A*' 4
100 - eita!

 

E em 64 bits

 

Tamanho do 'long unsigned int' 4
Tamanho do 'struct A*' 8

 

E o programa cancela, claro, no terceiro printf().
 

image.png.57f871c477e66f3ee1f524dac35c027b.png


Porque estou explicando isso de novo? Porque esse é um forum frequentado por iniciantes e o programa vai simplesmente cancelar sem aviso porque o ambiente está configurado para 64 bits e o usuário nem sabe. 

 

Use assim, por exemplo:

 

int lascaTudo(long unsigned int c)
{
	if (sizeof(long unsigned int) == sizeof(struct A*))
	{
		A* a = (A*)c;
		printf("%i - %s\n", a->A, a->B);
		return 0;
	}
	printf("x64: não pode passar o ponteiro em um long int!\n");
	return 1;
}

 

E o programa não vai cancelar e não precisa da "solução" do tópico do stack overflow que mostrou. Como estou mostrando é mais simples.

 

De todo modo considere que 
 

	printf("%zd\n", sizeof(void*));

 

é o simples. Há muitos anos os ponteiros tem o mesmo tamanho. No começo do uso de C era um inferno, com vários modelos de compilação e memória e os ponteiros tinham diferentes tamanhos. Note que há máquinas com arquiteturas distintas, como 128 bits ou 24 bits. Então o que importa se tem uma razão séria para passar um ponteiro como outra coisa é testar os tamanhos usando sizeof

 

De volta ao tema de passagem de parâmetros: acho que deu para ver com o que eu mostrei que os argumentos da função são passados por valor no stack ou em registradores. SEMPRE. Como interpretar esses valores depende do código. veja as versões de f1() no meu exemplo, e isso é o que acaba no nível da linguagem sendo passagem por valor ou por referência: como interpretar os argumentos que foram passados

 

1>cr.c
(22,15): warning C4312: 'type cast': 
conversion from 'unsigned long' to 'A *' of greater size
(35,35): warning C4311:
'type cast': pointer truncation from 'A *' to 'unsigned long'
(40,35): warning C4311:
'type cast': pointer truncation from 'B *' to 'unsigned long'

 

@codigo rápidoSempre habilite todos os avisos de seu compilador, usando -Wall no gcc ou clang ou /W3 ou superior no compilador da Microsoft, ou procure pela opção correta se usa outro compilador.

Link para o comentário
Compartilhar em outros sites

Citação

Essa suposta demonstração em tempo de execução não mostra muito. Sugiro esquecer.

O código como escreveu está apenas errado porque não funciona em 64 bits e não testa pelo ambiente. E usar esses #define como no exemplo do stack overflow que mostrou não vai ajudar muito. O programa vai cancelar apenas...


Lógico que não vai funcionar. É só um exemplo de como se poderia contornar o problema do tamanho dos endereços. Mas será que isso não é lógico nem para os usuários mais iniciais? Eu devo esperar que o usuário queira um sistema inteiro e explicado em 10 linhas? Isso seria doentio. Tanto da parte de quem espera quanto da de quem cobra.

Não escrevo sistemas completos de graça. So posso dar exemplos contando que o usuário vá pesquisar mais sobre. Tirando quem está no começo... é assim que vou fazer e é assim que vou continuar fazendo. Isso ai é pra usuários mais avançados ou que queira ir mais longe. Eu não tenho que pensar em tudo ou dar algo de mão beijada. Se o forum é pra aprender, os que quiserem irão aprender por vontadfe propria. Os outros, como deve ter percebido, podem ficar sem resposta ou você responde como já tem feito. O segredo pra mim é o que eu mesmo guardo pra mim: Se vira.
 

Citação

Sempre habilite todos os avisos de seu compilador, usando -Wall no gcc ou clang ou /W3 ou superior no compilador da Microsoft, ou procure pela opção correta se usa outro compilador.


Pode ficar despreocupado... aqui no meu está tudo habilitado.
O meu objetivo não é perfeição. O meu objetivo foi demonstrar como funciona uma função que recebe ponteiros.

 

Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...

Redes-Wi-Fi-capa-3d-newsletter.png

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!