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.

Você vai aprender
  • Como uma função devolve um valor com return e como usar esse valor.
  • Que o return também termina a função na hora (inclusive numa função void).
  • O tipo de retorno, os erros de return com 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 sucoEm C
As laranjas que você colocaOs argumentos (aula Parâmetros)
O copo de suco que saiO 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 nadaUma 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 int antes do nome é o tipo de retorno: esta função entrega um número inteiro para quem a chamou. Nas aulas anteriores, aqui estava void ("não entrega nada").
return resultado;
"Devolva o valor de resultado e termine a função." O return pode 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 total recebe 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
  1. maior(8, 3): 8 > 3 é verdade, então return a; devolve 8 e a função acaba ali. O return b; nem é visto.
  2. maior(2, 9): 2 > 9 é falso, o if é pulado e return b; devolve 9.
  3. O printf depois do return 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, quando a não é maior que b) que passa reto pelo if e bate no } sem nenhum return.
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 mostra 8388576. 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").

Trate o -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.

E o 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:

CaminhoComo funcionaAula
Devolver uma structJunte os valores numa "ficha" com vários campos e devolva a ficha inteiraStructs
Receber ponteirosQuem 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 &nota;
}

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 &nota;
      |            ^~~~~
$ ./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 de int (um ponteiro), não um int. Ponteiros têm aula própria: Ponteiros.
return &nota;
O & pega o endereço de nota (o mesmo & do scanf). Mas nota some 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: um printf("%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 resultado veio sem as casas decimais

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

EscritaO 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(…) * 2Usa 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 returnUm 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:

int triplo(int n) { }

2. O tipo certo

Complete o tipo de retorno de uma função que devolve uma média com casas decimais:

media(int a, int b) {

3. Sair cedo

Complete a linha que encerra uma função void antes do fim:

if (estoque == 0) { }

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): devolve true se m for 6 ou mais, e false se não (bool, true e false sã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. return com valor em void e return; vazio em int são erros (-Wreturn-mismatch).
  • Valor de outro tipo é convertido em silêncio: um int que recebe 3.5 devolve 3.
  • Faltou return num caminho: aviso control reaches end of non-void function [-Wreturn-type] e resultado lixo. Trate como erro. Só o main tem return 0; automático.
  • Um return devolve um valor só; para vários, use struct ou ponteiros. Nunca devolva o endereço de uma variável local.