HOWTO · C++
Como corrigir o erro «control reaches end of non-void function» em C++
Aprenda por que uma função C++ não void pode chegar à sua chave de fecho, como cobrir todos os caminhos de retorno e quando um valor alternativo ou um aviso do compilador ajuda.
Nesta página
Uma função C++ cujo tipo de retorno não é void tem de produzir um valor em todos os caminhos alcançáveis, exceto em main, cujo fim equivale a um retorno implícito de 0. Se um caminho alcançável de qualquer outra função não void chegar à chaveta de fecho sem um valor de return, o comportamento é indefinido; dependendo do código e das opções de aviso, o compilador também poderá assinalar esse caminho. Para corrigir o problema, percorra cada ramo e faça com que o valor devolvido corresponda à finalidade da função.
Compreender o erro
Uma função não void promete devolver um valor do tipo declarado. O diagnóstico do compilador conhecido como «control reaches end of non-void function» indica um caminho que pode chegar à chaveta de fecho sem devolver esse valor. O problema não é apenas faltar uma instrução no fim: uma condição anterior pode deixar alguns casos sem resultado. Por exemplo, int add(int a, int b) { if (a > b) return a + b; } não devolve um valor quando a <= b. Se uma função não void, que não seja main, chegar ao fim desta forma, o comportamento é indefinido; não há garantia de um erro ou resultado específico.
Execute este exemplo:
#include <iostream>
int add(int a, int b) {
return a + b;
}
int main() {
std::cout << add(5, 3) << '\n';
}
8
O programa devolve a soma para esta entrada. A versão inline acima mostra a causa do diagnóstico; a versão executável corrige-a ao devolver um valor para todas as entradas.
Garantir que todos os caminhos devolvem um valor
Analise as condições e determine o que cada caso deve significar. Numa adição, os dois operandos devem produzir a sua soma, independentemente da ordem. Se a condição for necessária para outro processamento, um return no fim pode cobrir o caminho restante; se ambos os ramos fizerem o mesmo, elimine a condição:
#include <iostream>
int add(int a, int b) {
if (a > b) {
return a + b;
}
return a + b;
}
int main() {
std::cout << add(3, 5) << '\n';
}
Uma estrutura de ramos completa é útil quando os casos têm significados diferentes. Este exemplo devolve o maior operando e cobre ambos os caminhos:
#include <iostream>
int larger(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
int main() {
std::cout << larger(5, 3) << '\n';
}
Verifique também as condições aninhadas e as instruções switch: cada percurso por uma função não void tem de devolver um valor ou evitar chegar à chaveta de fecho.
Usar valores de retorno predefinidos
Um return no fim pode servir de alternativa para os casos que não correspondem a uma condição anterior. Use essa alternativa apenas quando o valor respeitar o contrato da função. Por exemplo, esta consulta devolve uma pontuação conhecida quando encontra um nome correspondente e 0 quando o nome não existe; essa alternativa só é adequada se a aplicação definir que a ausência de uma pontuação significa zero.
#include <iostream>
#include <string>
int scoreFor(const std::string& name) {
if (name == "Ada") {
return 10;
}
return 0;
}
int main() {
std::cout << scoreFor("Ada") << '\n';
std::cout << scoreFor("Lin") << '\n';
}
Se a ausência representar um resultado distinto, devolver 0 pode ocultar essa diferença. Escolha um tipo de retorno e um resultado que expressem o contrato real, como um valor opcional ou um estado explícito. Não acrescente um valor alternativo arbitrário apenas para silenciar um diagnóstico.
Depurar com avisos do compilador
Ative os avisos durante a compilação para que o compilador possa ajudar a localizar um caminho que deva ser analisado. Por exemplo, g++ -Wall -Wextra -Wreturn-type your_code.cpp -o your_program pede ao GCC vários avisos, incluindo os relevantes para caminhos de retorno quando aplicável. A disponibilidade e o texto dos avisos variam consoante o compilador, a versão, as opções e o código; um aviso é uma pista para analisar o fluxo de controlo, não uma garantia de que todos os compiladores tratem o código da mesma forma.
Inspecione cada ramo, incluindo as instruções if aninhadas e todos os casos de switch. Depois, determine o que a função deve fazer para cada entrada e devolva um valor que respeite esse comportamento. Mantenha os avisos ativados durante o desenvolvimento para detetar mais facilmente caminhos semelhantes.
Conclusão
Quando uma função C++ não void pode chegar à chaveta de fecho sem um valor, analise o percurso e corrija a lógica da função. Cubra cada caso com um return que corresponda à finalidade da função. Use um valor alternativo apenas quando tiver um significado definido e encare os avisos do compilador como uma ajuda para encontrar caminhos que precisem de revisão.
Perguntas frequentes
O que causa o diagnóstico «control reaches end of non-void function»?
Um caminho alcançável numa função que devolve um valor chega à chaveta de fecho sem devolver um valor. Os compiladores podem assinalar esta situação, embora o diagnóstico e o comportamento da compilação dependam do compilador e das opções.
Todos os ramos de um if precisam de um return?
Não. Um return depois da condição pode tratar o caminho restante. O importante é que nenhum percurso alcançável chegue ao fim sem devolver um valor.
Devo acrescentar return 0; para eliminar o aviso?
Só se zero for um resultado válido segundo o contrato da função. Caso contrário, escolha um resultado ou tipo de retorno que represente corretamente o caso em falta.
Um aviso do compilador consegue identificar o caminho em falta?
Pode indicar uma função que talvez chegue ao fim sem devolver um valor. Use-o para analisar o fluxo de controlo; o texto e o comportamento dos avisos variam consoante o compilador e as definições.