Ir ao conteúdo
  • Cadastre-se
HebertCL

C++ Chamar uma função dentro de outra função usa mais memoria?

Recommended Posts

void funcao(){
	cout << "bla bla bla";
}

void cfuncao1(){
	funcao();
}

int main(){
    cfuncao1();
    funcao();
    return 0;
}

Se por exemplo, eu chamar a função "cfuncao1()" pra chamar a função "função()" ao invés de chamar "funcao()" direto, vai ocupar mais memoria ou a função "cfuncao1()" sai da memoria ate "funcao()" acabar?

  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sim, cfuncao1() aguardará na memoria por funcao() acabar.

Na wikipédia se encontra muito sobre recursividade.

  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

@HebertCL   não, pois quando o processador encontra o comando para ir para uma função ele apenas pega o endereço de memória em que ele está e guarda ele no stack do sistema e começa a executar os comandos que estão na função e quando terminar a função ele pega o endereço que está no stack e retorna para esse endereço e continua a executar os comandos dali para frente, assim não há consumo de memória a mais, então pode usar quantas funções quiser, uma dentro da outra sem problemas .

  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

Se usa mais memória eu não sei, sei que existe o preço de uma chamada de função, que é o tempo para uma função ser chamada, esse preço pode ser melhorado se a função for inlinearizada(inline/__inline/__forceinline), quando é inlinearizada ganha velocidade e é pago com o custo de um executável maior, como os processadores de hoje em dia são extremamente rápidos, um custo de chamada de função é mínimo.

 

Caso tenha ficado curioso sobre funções inlines: 

http://br.ccm.net/faq/10121-os-inlines-em-c

http://www.tiexpert.net/programacao/c/funcoes-inline.php

 

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites
14 horas atrás, devair1010 disse:

guarda ele no stack do sistema

Se guarda existe uso de lugares na stack, e a pilha não é dinâmica ou ilimitada os fatores que depende da linguagem de programação dizem quando e se haverá estouro, salvo os casos em que o compilador inteligente transcreve a recursividade para interação, é bem possível atingir o limite na maioria dos casos. Em C'ANSI isso acontece em poucos segundos, observe quantas chamadas no argumento da função presente na saída do prompt.

 

PS. Tudo que acontece gasta ou ocupa espaços na memória de alguma forma quando não liberados dela, mesmo que seja pequena sue número.

Capturar001.PNG

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Mauro Britivaldo  sim, haverá um consumo de memória, mas será bem pouca ou quase nenhuma, pois para cada função ele irá usar dois bytes de memória, e isso é nada se comparado ao total de bytes que tem um pente de memória de 1GB ( 1000.000.000 de bytes  ) e então não causará nenhum problema mesmo que seja várias funções .

  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

Como dito anteriormente, consome memória, mas a quantidade é irrelevante. Vou te explicar como funciona a chamada de uma função.

 

Todo programa tem uma pilha estática que serve pra armazenar certos dados.

Spoiler

Uma pilha é uma estrutura de dados que tem duas operações básicas:

- push(x) -> põe x no topo da pilha

- pop() -> remove o elemento do topo da pilha e o retorna

Pode ter outras funções, dependendo da implementação, como top() (ver elemento do topo sem remover), top(n) (ver n-ésimo elemento - começando do topo e indo pra baixo - sem remover), size() (quantidade de elementos na pilha) etc, mas as essenciais são apenas essas duas. Apesar disso, a pilha estática dos programas é bem livre e você consegue ver elementos do meio da pilha, como se fosse um top(n) (o que é essencial neste caso - vamos chegar lá), então é uma pilha no sentido de que novos elementos vão no final da pilha, remoção de elementos apenas do final - mas você pode acessar/editar elementos do meio.

Spoiler

É estática no sentido de que não é dinâmica :P, a memória pra ela é alocada uma vez, quando inicia o programa, e depois não muda mais durante toda a execução. A quantidade de memória alocada é determinada pelo programa, e no caso de C/C++ (e da maioria - se não todas - das linguagens compiladas), esse valor é determinado na hora de linkar (uma das etapas da compilação). Existe um valor padrão que varia de compilador pra compilador, mas todos eles permitem você mudar esse valor caso você precise. Por exemplo, no caso do gcc, se você quisesse alterar o tamanho da pilha para 128MB, na hora de compilar seria só passar os seguintes parâmetros pra ele:


-Wl,--stack=128000000

Então note que isso é algo "padrão", que existe independente de você fazer algo ou não. Essa pilha é necessária.

Spoiler

Foque na seguinte implementação da pilha estática (considerarei ela nas explicações abaixo):


// Note que uma pilha nada mais é do que um array bem grande, e um inteiro que guarda
// o index do topo desse array.

int stack[128000000]; // pilha com 128MB
int stackSize = 0; // quantidade de elementos na pilha

-> push(x) seria: stack[stackSize++] = x;
-> push() seria: stackSize++;
-> pop() seria: stack[--stackSize]
-> top(n) seria: stack[stackSize - n]

O que essa pilha armazena? Todos os dados do seu programa que forem alocados estaticamente! Quando você declara uma variável, ela vai na pilha! Quando você chama uma função, os dados dessa chamada vão na pilha!

 

Digamos que eu tenha o seguinte código:

int f(int p1, int p2) {
	int v1 = 7, v2;
	return v1 + v2;
}

int main() {
	int x; // vamos ignorar esta linha nas explicações
	x = f(5, 3);
}

Vamos agora ver o que acontece na chamada da função. Vou ignorar alguns passos pra não confundir, mas bem simplificadamente o que acontece é isto:

Ao chamar f(5, 3):

// no main()

1. Armazena espaço na pilha estática pro valor de retorno da chamada de f().
--> push()

2. Põe todos os parâmetros na pilha, da direita pra esquerda:
--> push(3) // p2
--> push(5) // p1

3. Põe na pilha qual o endereço da próxima instrução a ser executada quando voltar de f
(assim, quando acabar a execução de f, a gente tem como saber pra que parte do código ir!)
--> push(endereço da próxima instrução a ser executada quando voltar de f)

4. pula pra função f (em outras palavras, vai pro local do código onde está o começo de f)

// em f()

1. Aloca espaço na pilha para v1 e v2 (variáveis locais da função).
--> push(7) // int v1 = 7;
--> push()  // int v2;

2. Roda o código da função. Digamos que ele guarda o resultado de v1 + v2 num registrador,
que chamarei de R1. Então, R1 = v1 + v2

3. Desaloca o espaço de v1 e v2:
--> pop() // v2
--> pop() // v1

4. Armazena o retorno na pilha (lembra-se daquela parte da pilha que a gente alocou pra esse
retorno lá no início? vamos preenche-lo agora). É fácil calcular a posição dessa memória na
pilha: basta ver o que foi alocado após ele (o endereço de retorno e os parâmetros p1 e p2).
--> top(1 /*(endereço de retorno)*/ + 2 /*(parâmetros p1, p2)*/ + 1) = R1

5. Pega o endereço de retorno (pra que parte do código devemos pular agora que acabou a função):
--> R1 = pop() // poderia ser outro registrador, mas não importa muito pra nós

6. Pula pra esse endereço (R1).

// de volta no main()

1. Retira todos os parâmetros da pilha:
--> pop() // p1
--> pop() // p2

2. Pega o valor de retorno da pilha e armazena em x:
--> x = pop()

Pronto, a pilha agora está exatamente como estava antes da chamada!! Removemos exatamente a
mesma quantidade de elementos que inserimos, e não mexemos em nada além disso. Isso é importante,
porque se fizermos uma chamada para uma função f2() dentro de f(), não queremos que ela mexa no
espaço da pilha da chamada do próprio f()!! Então, uma chamada não pode mexer em nada que ela não
criou, e deve limpar tudo o que criou após terminar.

Na prática ele precisa armazenar outras coisas na pilha - como os registradores que serão usados dentro de f(), pra restaurar depois quando voltar pro main().

 

 

Agora, se quiser ir um pouco além...

Spoiler

 

O que essa pilha não armazena? Todos os dados que forem alocados dinamicamente. Talvez você já tenha visto estas funções:


char *minhaString = malloc(1024); // cria uma string com 1024 bytes alocados dinamicamente

... codigo aqui ...

free(minhaString); // libera a memoria alocada dinamicamente

Você usa alocação dinâmica em vários casos:

- Quando precisa de quantidades significativas de memória (que ultrapassam o tamanho reduzido da pilha estática);

- Quando você não sabe exatamente quanta memória vai precisar;
- Quando você não quer que o programa cuide da alocação/desalocação de memória por você (lembre-se que no caso de v1 e v2 la em f() ele aloca espaço na pilha quando entra na função, e desaloca quando sai)

 

Vamos ver o código a seguir:


char* tresPrimeirosCaracteres(char* s) {
	char tpc[4];
	tpc[0] = s[0];
	tpc[1] = s[1];
	tpc[2] = s[2];
	tpc[3] = '\0';
	return tpc;
}

Sabe me dizer por que esse código não funciona corretamente? O que eu estou retornando não é o valor de tpc, e sim o endereço de memória do tpc. Não é possível retornar um array como um valor... o que se retorna é sempre o endereço do seu primeiro elemento, e a partir daí sabemos que o próximo elemento está nesse endereço + 1, e assim vai. O tpc, entretanto, vai ser desalocado ao voltar da função. Então, se eu acessar o endereço dele, não necessariamente vai ter o valor de tpc mais. Pode ter qualquer coisa lá, já que essa pilha está sendo mexida constantemente, sempre que uma função é chamada. Existem duas soluções.


char* tresPrimeirosCaracteres(char* s) {
	static char tpc[4];
	tpc[0] = s[0];
	tpc[1] = s[1];
	tpc[2] = s[2];
	tpc[3] = '\0';
	return tpc;
}

A primeira é usar static. Quando se usa static, a memória ainda é alocada na pilha estática, mas é alocada no começo do programa, e não quando chama a função. Essa memória só é desalocada quando o programa é fechado. Entretanto, essa solução não é viável em todos os casos, e certamente teria um comportamento estranho caso eu fizesse o seguinte:


int main() {
	char *raf = tresPrimeirosCaracteres("Rafael");
	char *mar = tresPrimeirosCaracteres("Marcelo");
	printf("%s", raf);
}

Adivinha o que imprime. "Mar", não "Raf"!! Se entendeu como funciona o static, deve ter entendido o motivo. Quando eu chamo a função de novo, ela está mexendo na mesma memória que na primeira chamada. O endereço de tpc não muda entre chamadas.

 

Então, vamos para a segunda solução, que é o uso de memória dinâmica:


char* tresPrimeirosCaracteres(char* s) {
	char* tpc = malloc(4);
	tpc[0] = s[0];
	tpc[1] = s[1];
	tpc[2] = s[2];
	tpc[3] = '\0';
	return tpc;
}

Agora vai funcionar. Entretanto, quem deve cuidar de desalocar a memória dinâmica é o programador!! Então, JAMAIS esqueça de dar free nessa memória após terminar de usá-la, ou seu programa sofrerá de memory leak.


int main() {
	char *raf = tresPrimeirosCaracteres("Rafael");
	char *mar = tresPrimeirosCaracteres("Marcelo");
	printf("%s", raf); // agora sim, imprime "Raf"!!

	... resto do código que usa raf e mar aqui ...

	free(raf);
	free(mar);

	... resto do código
}

 

Acho que é basicamente isso que você precisa saber sobre a pilha e sobre alocação estática/dinâmica...

 

 

  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

Só complementando:

 

Vamos a pergunta:

Em 06/01/2018 às 19:40, HebertCL disse:

Se por exemplo, eu chamar a função "cfuncao1()" pra chamar a função "função()" ao invés de chamar "funcao()" direto, vai ocupar mais memoria ou a função "cfuncao1()" sai da memoria ate "funcao()" acabar?

A resposta é SIM. Vai aumentar!

 

 

Agora, quanto a vai aumentar? como citado:

6 horas atrás, RafaelCLP disse:

[a] quantidade é irrelevante

 

ou seja, existe um aumento mínimo/irrisório..., mas isso é por um parâmetro técnico... na prática não faz diferença. Se para seu contexto fica melhor usar uma chamada dentro da outra, pode utilizar. Nesse "nosso" nível de programação não precisamos nos preocupar com isso. Acho que o mais importa é a velocidade do processamento... e que mesmo assim deve também ter uma diferença irrisória para programa simples.

O que não pode é utilizar as chamadas em círculo ou recursivas... algo como um procedimento chamar outro para voltar, como um item do menu, chama menu em si... chamar um procedimento NÃO É apenas um "um atalho" para as instruções, envolve outras problemáticas.

 

Se você já viu programação em .bat, verás que lá tem um tal de GOTO. Lá sim funciona como apenas um "jump", ou seja, o código "pula" para determinado trecho, aqui não, envolve essa tal de pilha.

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro 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 publicações 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

×