C Valor de Retorno
As funções das aulas anteriores faziam coisas: mostravam uma linha, um recibo, uma média. Mas muitas vezes você não quer que a função mostre o resultado: quer que ela o entregue de volta, para você guardar numa variável, usar numa conta ou decidir o que fazer com ele. É para isso que serve o return. Nesta aula você vai escrever funções que devolvem valores, ver o que acontece quando o return é esquecido (o compilador avisa, e o resultado vira lixo), e aprender as duas regras de ouro: o tipo de retorno tem de bater, e nunca se devolve o endereço de uma variável local.
- Como uma função devolve um valor com
returne como usar esse valor. - Que o
returntambém termina a função na hora (inclusive numa funçãovoid). - O tipo de retorno, os erros de
returncom e sem valor e a conversão silenciosa. - O aviso
-Wreturn-type(control reaches end of non-void function) e por que ele nunca deve ser ignorado. - Como "devolver mais de um valor" em C (struct ou ponteiro).
- Por que devolver o endereço de uma variável local derruba o programa.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana, na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: a máquina de suco
Numa máquina de suco você coloca as laranjas (a entrada) e ela devolve um copo de suco (a saída). O que você faz com o suco é problema seu: beber, guardar na geladeira, misturar com outra coisa. A máquina não decide isso; ela só entrega.
| Na máquina de suco | Em C |
|---|---|
| As laranjas que você coloca | Os argumentos (aula Parâmetros) |
| O copo de suco que sai | O valor de retorno, entregue pelo return |
| A etiqueta "faz suco de laranja" | O tipo de retorno, escrito antes do nome: int, double… |
| Uma máquina que só lava as laranjas e não entrega nada | Uma função void |
Você já usou funções que devolvem valores sem perceber: o main devolve um int para o sistema (o return 0;), e funções como strlen devolvem o tamanho de um texto (aula Funções de String).
1. Devolver um valor com return
#include <stdio.h>
int somar(int a, int b) {
int resultado = a + b;
return resultado;
}
int main(void) {
int total = somar(3, 4);
printf("3 + 4 = %d\n", total);
printf("10 + 20 = %d\n", somar(10, 20));
printf("Dobro de 3 + 4 = %d\n", somar(3, 4) * 2);
return 0;
}
- int somar(…)
- O
intantes do nome é o tipo de retorno: esta função entrega um número inteiro para quem a chamou. Nas aulas anteriores, aqui estavavoid("não entrega nada"). - return resultado;
- "Devolva o valor de
resultadoe termine a função." Oreturnpode levar qualquer expressão:return a + b;faria o mesmo numa linha só. - int total = somar(3, 4);
- A chamada vira o valor devolvido. Primeiro o C roda a função; o resultado (7) toma o lugar da chamada; então
totalrecebe 7. - printf(…, somar(10, 20))
- O valor devolvido pode ir direto para outra função, sem passar por uma variável.
- somar(3, 4) * 2
- E pode entrar numa conta, como qualquer número.
$ gcc -std=c23 -Wall -Wextra soma.c -o soma $ ./soma 3 + 4 = 7 10 + 20 = 30 Dobro de 3 + 4 = 14
Repare que somar não tem nenhum printf. Ela só calcula. Quem decide mostrar, guardar ou multiplicar é o main. Separar "calcular" de "mostrar" deixa a função útil em mais lugares: a mesma somar serve para um recibo, um placar ou uma planilha.
2. O return termina a função
Assim que o return roda, a função acaba, mesmo que haja mais linhas abaixo dele. Por isso, uma função pode ter vários return, um em cada caminho:
#include <stdio.h>
int maior(int a, int b) {
if (a > b) {
return a;
}
return b;
printf("Esta linha nunca roda.\n");
}
int main(void) {
printf("Maior entre 8 e 3: %d\n", maior(8, 3));
printf("Maior entre 2 e 9: %d\n", maior(2, 9));
return 0;
}
$ gcc -std=c23 -Wall -Wextra maior.c -o maior $ ./maior Maior entre 8 e 3: 8 Maior entre 2 e 9: 9
maior(8, 3): 8 > 3 é verdade, entãoreturn a;devolve 8 e a função acaba ali. Oreturn b;nem é visto.maior(2, 9): 2 > 9 é falso, oifé pulado ereturn b;devolve 9.- O
printfdepois doreturn b;nunca roda, em nenhum caso. O gcc não avisou; é código morto, e você deve apagá-lo.
return; sem valor numa função void
Uma função void não devolve nada, mas pode usar return; (sem valor) para sair mais cedo:
#include <stdio.h>
void vender(int estoque) {
if (estoque == 0) {
printf("Sem estoque, venda cancelada.\n");
return;
}
printf("Venda feita! Restam %d.\n", estoque - 1);
}
int main(void) {
vender(5);
vender(0);
return 0;
}
$ gcc -std=c23 -Wall -Wextra cedo.c -o cedo $ ./cedo Venda feita! Restam 4. Sem estoque, venda cancelada.
Com estoque zero, o return; encerra a função antes do "Venda feita!". Esse estilo, de tratar o caso problemático primeiro e sair, deixa o resto da função mais limpo.
Experimente: a função maior está pronta. Acrescente uma linha no main que guarde maior(15, 42) numa variável e mostre-a. Depois tente maior(maior(3, 8), 5): o resultado de uma chamada pode ser argumento de outra.
#include <stdio.h>
int maior(int a, int b) {
if (a > b) {
return a;
}
return b;
}
int main(void) {
printf("Maior entre 8 e 3: %d\n", maior(8, 3));
return 0;
}
3. O tipo de retorno tem de bater
O tipo antes do nome é uma promessa. O compilador confere se o return cumpre a promessa. Uma função void não pode devolver valor:
#include <stdio.h>
void avisar(int estoque) {
if (estoque > 0) {
return 1;
}
printf("Acabou o estoque!\n");
}
int main(void) {
avisar(0);
return 0;
}
$ gcc -std=c23 -Wall -Wextra voidret.c -o voidret
voidret.c: In function ‘avisar’:
voidret.c:5:16: error: ‘return’ with a value, in function returning void [-Wreturn-mismatch]
5 | return 1;
| ^
voidret.c:3:6: note: declared here
3 | void avisar(int estoque) {
| ^~~~~~
‘return’ with a value, in function returning void = "return com valor, numa função que devolve void". E o contrário, return; vazio numa função int, também é erro:
#include <stdio.h>
int ler_idade(void) {
return;
}
int main(void) {
printf("%d\n", ler_idade());
return 0;
}
$ gcc -std=c23 -Wall -Wextra retvazio.c -o retvazio
retvazio.c: In function ‘ler_idade’:
retvazio.c:4:5: error: ‘return’ with no value, in function returning non-void [-Wreturn-mismatch]
4 | return;
| ^~~~~~
retvazio.c:3:5: note: declared here
3 | int ler_idade(void) {
| ^~~~~~~~~
‘return’ with no value, in function returning non-void = "return sem valor, numa função que não é void". Os dois viraram erro no gcc 14; antes eram só avisos. No editor, o Clang escreve void function 'avisar' should not return a value e non-void function 'ler_idade' should return a value.
Tipo parecido: conversão em silêncio
Se o return devolve um número de outro tipo, o C converte para o tipo prometido, sem avisar:
#include <stdio.h>
int metade(int n) {
return n / 2.0;
}
int main(void) {
printf("Metade de 7: %d\n", metade(7));
return 0;
}
$ gcc -std=c23 -Wall -Wextra metade.c -o metade $ ./metade Metade de 7: 3 « 7 / 2.0 = 3.5, mas a função promete int: o .5 sumiu
A conta n / 2.0 deu 3.5 (um double), mas a função promete devolver int, então o C cortou a parte decimal. Nenhum aviso, nem no gcc nem no Clang. O conserto é escolher o tipo certo: double metade(int n) e %.1f no printf. (A opção -Wconversion do gcc avisaria; veja Avisos.)
4. Esquecer o return: o aviso -Wreturn-type
Este é o erro mais perigoso da aula, porque é só um aviso, e o programa é gerado mesmo assim. A função promete um int, mas há um caminho em que ela chega ao fim sem return:
#include <stdio.h>
int maior(int a, int b) {
if (a > b) {
return a;
}
}
int main(void) {
printf("Maior entre 8 e 3: %d\n", maior(8, 3));
printf("Maior entre 2 e 9: %d\n", maior(2, 9));
return 0;
}
$ gcc -std=c23 -Wall -Wextra semreturn.c -o semreturn
semreturn.c: In function ‘maior’:
semreturn.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
7 | }
| ^
$ ./semreturn
Maior entre 8 e 3: 8
Maior entre 2 e 9: 2 « errado: o maior é 9
$ gcc -std=c23 -Wall -Wextra -O2 semreturn.c -o semreturn
semreturn.c: In function ‘maior’:
semreturn.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
7 | }
| ^
$ ./semreturn
Maior entre 8 e 3: 8
Maior entre 2 e 9: 0 « com otimização (-O2), outro resultado errado
- control reaches end of non-void function
- "A execução chega ao fim de uma função não-
void." Quer dizer: existe um caminho (aqui, quandoanão é maior queb) que passa reto peloife bate no}sem nenhumreturn. - 7:1
- O gcc aponta a chave que fecha a função, onde o valor deveria ter sido devolvido.
- [-Wreturn-type]
- A regra de aviso, ligada pelo
-Wall. - 2, depois 0
- Sem
return, quem chamou lê "o que estiver lá" no lugar onde o resultado deveria estar: lixo. Sem otimização, sobrou o 2; com-O2, 0. No editor do guia, a mesma linha mostra8388576. Nenhum deles é 9.
Usar o valor de uma função que terminou sem return é comportamento indefinido: a linguagem não promete nada (aula Comportamento Indefinido). Pode dar o número certo nos seus testes e o errado no computador de outra pessoa. O conserto é garantir um return em todo caminho; aqui, um return b; antes do }. O Clang do editor avisa do mesmo jeito: non-void function does not return a value in all control paths [-Wreturn-type] ("função não-void não devolve valor em todos os caminhos").
-Wreturn-type como erro
Muitos projetos compilam com -Werror=return-type, que transforma esse aviso em erro e impede o programa de ser gerado. Um resultado lixo que "parece certo" nos testes é dos erros mais difíceis de achar depois.
main?
O main é a única exceção: desde o C99, se ele chega ao } sem return, é como se terminasse com return 0; (o gcc não avisa). O guia escreve o return 0; mesmo assim, para ficar claro. Veja Olá, Mundo e Encerrar o Programa.
Missão: escreva a função int quadrado(int n), que devolve (sem mostrar nada) o número vezes ele mesmo. O main já usa o valor devolvido. A saída tem de ser esta, sem avisos:
3 ao quadrado = 9 12 ao quadrado = 144
#include <stdio.h>
// escreva aqui a função quadrado
int main(void) {
printf("3 ao quadrado = %d\n", quadrado(3));
int lado = 12;
int area = quadrado(lado);
printf("%d ao quadrado = %d\n", lado, area);
return 0;
}
#include <stdio.h>
int quadrado(int n) {
return n * n;
}
int main(void) {
printf("3 ao quadrado = %d\n", quadrado(3));
int lado = 12;
int area = quadrado(lado);
printf("%d ao quadrado = %d\n", lado, area);
return 0;
}
5. Ignorar o valor devolvido
Você não é obrigado a usar o valor que uma função devolve. Chamar somar(3, 4); sozinho numa linha compila sem aviso; o 7 é simplesmente jogado fora. Isso acontece o tempo todo com o printf, que também devolve um número (quantos caracteres escreveu) e quase ninguém olha:
#include <stdio.h>
int somar(int a, int b) {
return a + b;
}
int main(void) {
somar(3, 4);
int n = printf("Ana\n");
printf("O printf anterior escreveu %d caracteres\n", n);
return 0;
}
$ gcc -std=c23 -Wall -Wextra ignora.c -o ignora $ ./ignora Ana O printf anterior escreveu 4 caracteres « A, n, a e o \n
Ignorar o resultado de somar é inofensivo (só desperdício). Mas ignorar o retorno de algumas funções é um erro de verdade: o scanf devolve quantos valores conseguiu ler, o fopen devolve NULL quando o arquivo não abre, o malloc devolve NULL quando falta memória. Por isso o guia sempre confere esses retornos (aulas scanf, Arquivos e malloc e free). No C23, você pode marcar a sua função com [[nodiscard]] para o compilador avisar quem ignorar o valor (aula Atributos).
6. E para devolver mais de um valor?
Um return devolve um valor só. return a, b; não devolve dois: a vírgula ali é um operador que joga o a fora e devolve só o b. Para entregar mais de um resultado, o C tem dois caminhos, que você vai aprender no Nível 2:
| Caminho | Como funciona | Aula |
|---|---|---|
| Devolver uma struct | Junte os valores numa "ficha" com vários campos e devolva a ficha inteira | Structs |
| Receber ponteiros | Quem chama entrega os endereços de suas variáveis, e a função escreve nelas (como o scanf faz) | Passar por Ponteiro |
Só para você ter uma ideia (não precisa entender cada linha agora), uma divisão que devolve o quociente e o resto numa struct:
#include <stdio.h>
struct resultado {
int quociente;
int resto;
};
struct resultado dividir(int a, int b) {
struct resultado r = { a / b, a % b };
return r;
}
int main(void) {
struct resultado d = dividir(17, 5);
printf("17 / 5 = %d, sobra %d\n", d.quociente, d.resto);
return 0;
}
$ gcc -std=c23 -Wall -Wextra dois.c -o dois $ ./dois 17 / 5 = 3, sobra 2
7. Nunca devolva o endereço de uma variável local
Este aviso aparece mais adiante no curso, quando você já usar ponteiros, mas vale conhecê-lo desde já. As variáveis criadas dentro de uma função (as locais) deixam de existir quando a função termina (aula Escopo). Devolver o endereço de uma delas é entregar o endereço de uma casa que acabou de ser demolida:
#include <stdio.h>
int *criar_nota(void) {
int nota = 10;
return ¬a;
}
int main(void) {
int *p = criar_nota();
printf("Nota: %d\n", *p);
return 0;
}
$ gcc -std=c23 -Wall -Wextra local.c -o local
local.c: In function ‘criar_nota’:
local.c:5:12: warning: function returns address of local variable [-Wreturn-local-addr]
5 | return ¬a;
| ^~~~~
$ ./local
Falha de segmentação (imagem do núcleo gravada) ./local
$ echo $?
139
- int *criar_nota(void)
- O
*quer dizer que a função devolve um endereço deint(um ponteiro), não umint. Ponteiros têm aula própria: Ponteiros. - return ¬a;
- O
&pega o endereço denota(o mesmo&doscanf). Masnotasome quando a função termina. - function returns address of local variable
- "A função devolve o endereço de uma variável local". É um aviso, mas trate como erro.
- Falha de segmentação
- O programa morreu ao ler
*p(código 139). O gcc sabe que esse endereço não vale nada e, nesse caso, devolve um endereço nulo no lugar (conferido no laboratório: umprintf("%p")do endereço mostra(nil)). Ler um endereço nulo derruba o programa (aula Ponteiro Nulo).
No editor do guia, o mesmo programa mostra Nota: 10 e termina normalmente, com o aviso do Clang address of stack memory associated with local variable 'nota' returned [-Wreturn-stack-address]. Parece funcionar, mas é sorte: o valor ainda estava lá porque nada o sobrescreveu. É comportamento indefinido, e cada compilador fez uma coisa. O jeito certo é devolver o valor (int criar_nota(void) { return 10; }) ou, quando for preciso devolver um endereço, usar memória que continua existindo depois da função (malloc e free).
Quando dá errado
warning: control reaches end of non-void function [-Wreturn-type]
O que significa: algum caminho da função chega ao } sem return. Quem chamou recebe lixo. O Clang diz non-void function does not return a value in all control paths.
Como consertar: percorra cada if/else e garanta um return em todos os caminhos. Um return no fim da função, fora dos if, resolve a maioria dos casos.
error: ‘return’ with a value, in function returning void
O que significa: a função é void, mas tem return 1;.
Como consertar: se a função deve devolver o valor, troque void pelo tipo certo (int); se não, use return; sozinho.
error: ‘return’ with no value, in function returning non-void
O que significa: a função promete um valor (int, double…), mas tem um return; vazio.
Como consertar: devolva um valor (return 0;, return idade;) ou, se a função não precisa devolver nada, troque o tipo por void.
error: void value not ignored as it ought to be
O que significa: você guardou numa variável o "resultado" de uma função void.
Como consertar: a função precisa de um tipo de retorno e de um return com valor. Veja Funções.
warning: function returns address of local variable [-Wreturn-local-addr]
O que significa: a função devolve o endereço de algo que deixa de existir quando ela termina. No gcc, o programa cai com Falha de segmentação; em outros compiladores, pode mostrar lixo ou até o valor "certo", por acaso.
Como consertar: devolva o valor em vez do endereço. Se precisa mesmo de um endereço, use malloc ou deixe quem chama fornecer a variável (passagem por ponteiro).
O que significa: o tipo de retorno é int e o return devolveu uma conta com vírgula: o C cortou a parte decimal, sem aviso.
Como consertar: declare a função como double e mostre com %f (ou %.1f, %.2f).
Referência rápida
| Escrita | O que faz |
|---|---|
int f(…) { …; return x; } | Devolve o valor de x (convertido para int) e termina |
double f(…) | Promete devolver um número com vírgula |
int r = f(…); | Guarda o valor devolvido |
printf("%d", f(…)); / f(…) * 2 | Usa o valor direto, sem variável |
f(…); | Chama e ignora o valor devolvido |
return; | Sai de uma função void antes do fim |
Vários return | Um por caminho; o primeiro que rodar termina a função |
struct t f(…) | Devolve vários valores juntos numa struct (Structs) |
Exercícios
1. Devolver
Complete a linha que devolve o triplo de n:
2. O tipo certo
Complete o tipo de retorno de uma função que devolve uma média com casas decimais:
3. Sair cedo
Complete a linha que encerra uma função void antes do fim:
4. O que int f(int x) { if (x > 0) { return 1; } } devolve com f(-5)?
5. Quantas vezes "fim" aparece? int g(void) { return 5; printf("fim\n"); } chamada duas vezes.
6. Qual é o problema de int *f(void) { int x = 3; return &x; }?
Missão final: média e aprovação
Missão: escreva duas funções, acima do main:
double media(double n1, double n2): devolve a média das duas notas;bool aprovada(double m): devolvetruesemfor 6 ou mais, efalsese não (bool,trueefalsesão palavras do C23; veja Booleanos).
O main já está pronto e usa os valores devolvidos. Nenhuma das funções deve usar printf.
Ana: média 7.5 - aprovada Bia: média 5.0 - reprovada
#include <stdio.h>
// escreva aqui as funções media e aprovada
int main(void) {
double ana = media(7.0, 8.0);
double bia = media(4.0, 6.0);
printf("Ana: média %.1f - %s\n", ana, aprovada(ana) ? "aprovada" : "reprovada");
printf("Bia: média %.1f - %s\n", bia, aprovada(bia) ? "aprovada" : "reprovada");
return 0;
}
#include <stdio.h>
double media(double n1, double n2) {
return (n1 + n2) / 2;
}
bool aprovada(double m) {
return m >= 6;
}
int main(void) {
double ana = media(7.0, 8.0);
double bia = media(4.0, 6.0);
printf("Ana: média %.1f - %s\n", ana, aprovada(ana) ? "aprovada" : "reprovada");
printf("Bia: média %.1f - %s\n", bia, aprovada(bia) ? "aprovada" : "reprovada");
return 0;
}
Na solução, return m >= 6; devolve direto o resultado da comparação, que já é verdadeiro ou falso: não precisa de if. O ? : no main é o operador ternário (aula Operador Ternário).
Resumo
- O tipo antes do nome (
int,double,bool…) diz o que a função devolve;return valor;devolve e termina a função. - A chamada vira o valor devolvido: guarde numa variável, passe para outra função ou use numa conta. Também pode ignorá-lo (mas confira sempre o retorno de
scanf,fopen,malloc). - Numa função
void,return;sai mais cedo.returncom valor emvoidereturn;vazio emintsão erros (-Wreturn-mismatch). - Valor de outro tipo é convertido em silêncio: um
intque recebe 3.5 devolve 3. - Faltou
returnnum caminho: avisocontrol reaches end of non-void function [-Wreturn-type]e resultado lixo. Trate como erro. Só omaintemreturn 0;automático. - Um
returndevolve um valor só; para vários, use struct ou ponteiros. Nunca devolva o endereço de uma variável local.