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.