Qual é o objetivo do retorno normal?


189

[dcl.attr.noreturn] fornece o seguinte exemplo:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

mas eu não entendo qual é o objetivo [[noreturn]], porque o tipo de retorno da função já é void.

Então, qual é o objetivo do noreturnatributo? Como é que deve ser usado?


1
O que há de tão importante nesse tipo de função (que provavelmente acontecerá uma vez na execução de um programa) que merece tanta atenção? Não é uma situação facilmente detectável?
User666412

1
@ Mrrister O OP está combinando os conceitos de "retorno" e "valor de retorno". Dado que quase sempre são usados ​​em conjunto, acho que a confusão é justificada.
precisa

Respostas:


209

O atributo noreturn deve ser usado para funções que não retornam ao chamador. Isso não significa funções nulas (que retornam ao chamador - elas simplesmente não retornam um valor), mas funções em que o fluxo de controle não retornará à função de chamada após o término da função (por exemplo, funções que saem do aplicativo, para sempre ou faça exceções, como no seu exemplo).

Isso pode ser usado pelos compiladores para fazer algumas otimizações e gerar melhores avisos. Por exemplo, se ftiver o atributo noreturn, o compilador poderá avisá-lo sobre o g()código morto quando você escrever f(); g();. Da mesma forma, o compilador saberá não avisá-lo sobre a falta de instruções de retorno após as chamadas para f().


5
Que tal uma função como execveessa não deveria retornar, mas poderia ? Deveria ter o atributo noreturn ?
Kalrish

22
Não, não deveria - se houver a possibilidade de o fluxo de controle retornar ao chamador, ele não deve ter o noreturnatributo noreturnsó pode ser usado se for garantido que sua função execute algo que encerra o programa antes que o fluxo de controle possa retornar ao chamador - por exemplo, porque você chama exit (), abort (), assert (0) etc.)
RavuAlHemio

6
@ SlippD.Thompson Se uma chamada para uma função noreturn estiver agrupada em um bloco try, qualquer código do bloco catch contará como acessível novamente.
precisa saber é o seguinte

2
@ sepp2k Cool. Portanto, não é impossível retornar, apenas anormal. Isso é útil. Felicidades.
precisa

7
@ SlippD.Thompson não, é impossível retornar. Lançar uma exceção não está retornando; portanto, se todo caminho for lançado, será noreturn. Lidar com essa exceção não é o mesmo que ter retornado. Qualquer código tryapós a chamada ainda está inacessível e, caso contrário void, nenhuma atribuição ou uso do valor de retorno não ocorrerá.
911 Jon Hanna

62

noreturnnão informa ao compilador que a função não retorna nenhum valor. Diz ao compilador que o fluxo de controle não retornará ao chamador . Isso permite que o compilador faça uma variedade de otimizações - ele não precisa salvar nem restaurar nenhum estado volátil da chamada, pode eliminar o código morto para eliminar qualquer código que, de outra forma, seguiria a chamada, etc.


29

Isso significa que a função não será concluída. O fluxo de controle nunca atingirá a instrução após a chamada para f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

As informações podem ser usadas pelo compilador / otimizador de diferentes maneiras. O compilador pode adicionar um aviso de que o código acima é inacessível e pode modificar o código real de g()maneiras diferentes, por exemplo, para dar suporte a continuações.


3
gcc / clang não dá avisos
TemplateRex

4
@ TemplateRex: Compile com -Wno-returne você receberá um aviso. Provavelmente não é o que você esperava, mas provavelmente é suficiente dizer que o compilador tem conhecimento do que [[noreturn]]é e pode tirar vantagem disso. (Eu sou um pouco surpreso que -Wunreachable-codenão chutar ...)
David Rodríguez - dribeas

3
@TemplateRex: Desculpe -Wmissing-noreturn, o aviso implica que a análise de fluxo determinou que std::coutnão é alcançável. Eu não tenho um novo gcc suficiente na mão para olhar para o gerado montagem, mas eu não ficaria surpreso se a chamada para operator<<foi abandonada
David Rodríguez - dribeas

1
Aqui está um dump de montagem (-S -o - sinalizadores em coliru), na verdade descarta o código "inacessível". Curiosamente, -O1já é o suficiente para largar esse código inacessível sem a [[noreturn]]dica.
TemplateRex

2
@TemplateRex: todo o código está na mesma unidade de tradução e visível, para que o compilador possa inferir [[noreturn]]o código. Se essa unidade de conversão tivesse apenas uma declaração da função definida em outro lugar, o compilador não seria capaz de descartar esse código, pois não sabe que a função não retorna. É aí que o atributo deve ajudar o compilador.
David Rodríguez - dribeas

17

As respostas anteriores explicaram corretamente o que é noreturn, mas não por que ele existe. Eu não acho que os comentários de "otimização" sejam o principal objetivo: as funções que não retornam são raras e geralmente não precisam ser otimizadas. Antes, acho que a principal razão de ser do noreturn é evitar avisos falso-positivos. Por exemplo, considere este código:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Se abort () não tivesse sido marcado como "noreturn", o compilador pode ter avisado sobre esse código ter um caminho em que f não retorna um número inteiro conforme o esperado. Mas como abort () está marcado como sem retorno, ele sabe que o código está correto.


Todos os outros exemplos listados usam funções void - como funciona quando você tem a diretiva [[no return]] e um tipo de retorno non-void? A diretiva [[no return]] só entra em ação quando o compilador se prepara para avisar sobre a possibilidade de não retornar e ignorar o aviso? Por exemplo, o compilador diz: "Ok, aqui está uma função não vazia". * continua compilando * "Oh merda, esse código pode não retornar! Devo avisar o usuário? *" Deixa pra lá, eu vejo a diretiva de não retorno. Continue "
Raleigh L.

2
A função noreturn no meu exemplo não é f (), é abort (). Não faz sentido marcar uma função não nula nem voltar. Uma função que às vezes retorna um valor e às vezes retorna (um bom exemplo é execve ()) não pode ser marcada como retornada.
Nadav Har'El


11

Digite teoricamente falando, voidé o que é chamado em outros idiomas unitou top. Seu equivalente lógico é True . Qualquer valor pode ser legitimamente convertido para void(todo tipo é um subtipo de void). Pense nisso como um conjunto de "universo"; não há operações em comum a todos os valores no mundo, para que não haja operações válidas em um valor do tipo void. Dito de outra maneira, dizer que algo pertence ao conjunto do universo não fornece nenhuma informação - você já sabe disso. Portanto, o seguinte é válido:

(void)5;
(void)foo(17); // whatever foo(17) does

Mas a tarefa abaixo não é:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]], Por outro lado, é chamado às vezes empty, Nothing, Bottomou Bote é o equivalente lógico de Falso . Ele não possui valores, e uma expressão desse tipo pode ser convertida para (ou seja, é subtipo de) qualquer tipo. Este é o conjunto vazio. Observe que se alguém lhe disser "o valor da expressão foo () pertence ao conjunto vazio" é altamente informativo - informa que essa expressão nunca concluirá sua execução normal; irá abortar, jogar ou travar. É exatamente o oposto de void.

Portanto, o seguinte não faz sentido (pseudo-C ++, pois noreturnnão é do tipo C ++ de primeira classe)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Mas a atribuição abaixo é perfeitamente legítima, pois throwé entendida pelo compilador como não retornando:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

Em um mundo perfeito, você pode usar noreturncomo valor de retorno para a função raise()acima:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

Infelizmente, o C ++ não permite isso, provavelmente por razões práticas. Em vez disso, oferece a capacidade de usar [[ noreturn ]]atributos que ajudam a orientar otimizações e avisos do compilador.


5
Nada pode ser lançado voide voidnunca avaliado para trueou falseou qualquer outra coisa.
Clearer

5
Quando eu digo true, eu não quero dizer "o valor truedo tipo bool" mas o sentido da lógica, ver Curry-Howard correspondência
Elazar

8
A teoria de tipos abstratos que não se encaixa no sistema de tipos de uma linguagem específica é irrelevante quando se discute o sistema de tipos dessa linguagem. A questão, em questão :-), é sobre C ++, não sobre teoria de tipos.
Clearer

8
(void)true;é perfeitamente válido, como a resposta sugere. void(true)é algo completamente diferente, sintaticamente. É uma tentativa de criar um novo objeto do tipo voidchamando um construtor truecomo argumento; isso falha, entre outros motivos, porque voidnão é de primeira classe.
Elazar 24/03

2
Então é. Estou acostumado a usar conversão de tipo (valor), não conversão de estilo c.
Clear 24/03
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.