¿Cuál es el punto de noreturn?

189

[dcl.attr.noreturn] proporciona el siguiente ejemplo:

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

pero no entiendo de qué sirve [[noreturn]], porque el tipo de retorno de la función ya lo es void.

Entonces, ¿cuál es el punto del noreturnatributo? ¿Cómo se supone que debe usarse?

BЈовић
fuente
1
¿Qué es tan importante acerca de este tipo de función (que probablemente sucederá una vez en la ejecución de un programa) que merece tanta atención? ¿No es esta una situación fácilmente detectable?
user666412
1
@MrLister Los OP combinan los conceptos de "retorno" y "valor de retorno". Dado que casi siempre se usan en tándem, creo que la confusión está justificada.
Slipp D. Thompson

Respuestas:

209

Se supone que el atributo noreturn se usa para funciones que no regresan a la persona que llama. Eso no significa funciones nulas (que regresan a la persona que llama, simplemente no devuelven un valor), sino funciones donde el flujo de control no volverá a la función de llamada después de que la función finalice (por ejemplo, funciones que salen de la aplicación, bucle para siempre o lanzar excepciones como en su ejemplo).

Los compiladores pueden usar esto para hacer algunas optimizaciones y generar mejores advertencias. Por ejemplo, si ftiene el atributo noreturn, el compilador podría advertirle sobre el g()código muerto cuando escribe f(); g();. Del mismo modo, el compilador sabrá que no debe advertirle sobre las declaraciones de retorno faltantes después de las llamadas a f().

sepp2k
fuente
55
¿Qué pasa con una función como execveesa no debería regresar pero podría ? ¿Debería tener el atributo noreturn ?
Kalrish
22
No, no debería; si existe la posibilidad de que el flujo de control regrese a la persona que llama, no debe tener el noreturnatributo. noreturnsolo se puede usar si se garantiza que su función haga algo que finalice el programa antes de que el flujo de control pueda volver a la persona que llama, por ejemplo porque llama a exit (), abort (), afirmar (0), etc.
RavuAlHemio
77
@ SlippD.Thompson Si una llamada a una función noreturn está envuelta en un bloque try, cualquier código del bloque catch contará como accesible nuevamente.
sepp2k
2
@ sepp2k Genial. Entonces no es imposible regresar, solo anormal. Eso es útil Salud.
Slipp D. Thompson
77
@ SlippD.Thompson no, es imposible regresar. Lanzar una excepción no está regresando, por lo que si cada ruta arroja, entonces lo es noreturn. Manejar esa excepción no es lo mismo que haber regresado. Cualquier código dentro de trydespués de la llamada sigue siendo inalcanzable, y si no void, no se realizará ninguna asignación o uso del valor de retorno.
Jon Hanna
63

noreturnno le dice al compilador que la función no devuelve ningún valor. Le dice al compilador que el flujo de control no volverá a la persona que llama . Esto permite que el compilador realice una variedad de optimizaciones: no necesita guardar y restaurar ningún estado volátil alrededor de la llamada, puede eliminar el código muerto de cualquier código que de otro modo seguiría a la llamada, etc.

Stephen Canon
fuente
29

Significa que la función no se completará. El flujo de control nunca llegará a la declaración después de la llamada a f():

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

El compilador / optimizador puede utilizar la información de diferentes maneras. El compilador puede agregar una advertencia de que el código anterior es inalcanzable, y puede modificar el código real de g()diferentes maneras, por ejemplo, para admitir continuaciones.

David Rodríguez - dribeas
fuente
3
gcc / clang no dan advertencias
TemplateRex
44
@TemplateRex: compile con -Wno-returny recibirá una advertencia. Probablemente no sea el que esperaba, pero probablemente sea suficiente para decirle que el compilador tiene conocimiento de lo que [[noreturn]]es y puede aprovecharlo. (Estoy un poco sorprendido de que -Wunreachable-codeno haya pateado ...)
David Rodríguez - dribeas
3
@TemplateRex: Lo sentimos -Wmissing-noreturn, la advertencia implica que el análisis de flujo determinó que std::coutno es accesible. No tengo un nuevo gcc lo suficientemente cerca para mirar a la Asamblea generado, pero no me sorprendería si la llamada a operator<<se cayó
David Rodríguez - dribeas
1
Aquí hay un volcado de ensamblado (-S -o - flags en coliru), de hecho, elimina el código "inalcanzable". Curiosamente, -O1ya es suficiente para soltar ese código inalcanzable sin la [[noreturn]]pista.
TemplateRex
2
@TemplateRex: todo el código está en la misma unidad de traducción y es visible, por lo que el compilador puede inferirlo a [[noreturn]]partir del código. Si esta unidad de traducción solo tuviera una declaración de la función que se definió en otro lugar, el compilador no podría eliminar ese código, ya que no sabe que la función no regresa. Ahí es donde el atributo debería ayudar al compilador.
David Rodríguez - dribeas
18

Las respuestas anteriores explicaban correctamente qué es noreturn, pero no por qué existe. No creo que los comentarios de "optimización" sean el propósito principal: las funciones que no regresan son raras y generalmente no necesitan ser optimizadas. Más bien creo que la razón principal de noreturn es evitar las advertencias falsas positivas. Por ejemplo, considere este código:

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

Si abort () no se hubiera marcado como "noreturn", el compilador podría haber advertido que este código tiene una ruta donde f no devuelve un entero como se esperaba. Pero debido a que abort () está marcado sin retorno, sabe que el código es correcto.

Nadav Har'El
fuente
Todos los demás ejemplos enumerados usan funciones nulas: ¿cómo funciona cuando tiene tanto la directiva [[no return]] como un tipo de devolución no nula? ¿La directiva [[no return]] solo entra en juego cuando el compilador se prepara para advertir sobre la posibilidad de no regresar e ignorar la advertencia? Por ejemplo, el compilador dice: "Bien, aquí hay una función no nula". * continúa compilando * "¡Oh, mierda, este código podría no volver! ¿Debo advertir al usuario? *" No importa, veo la directiva de no retorno. Continuar "
Raleigh L.
2
La función noreturn en mi ejemplo no es f (), es abortar (). No tiene sentido marcar una función no nula noreturn. Una función que a veces devuelve un valor y a veces devuelve (un buen ejemplo es execve ()) no se puede marcar como noreturn.
Nadav Har'El
1
implícito-fallthrough es otro ejemplo: stackoverflow.com/questions/45129741/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
12

Tipo teóricamente hablando, voides lo que se llama en otros idiomas unito top. Su equivalente lógico es Verdadero . Cualquier valor se puede emitir legítimamente void(cada tipo es un subtipo de void). Piense en ello como un conjunto de "universo"; no hay operaciones en común con todos los valores del mundo, por lo que no hay operaciones válidas para un valor de tipo void. Dicho de otra manera, diciéndole que algo pertenece al conjunto del universo no le proporciona información alguna, ya lo sabe. Entonces lo siguiente es sólido:

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

Pero la tarea a continuación no es:

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 otra parte, a veces se llama empty, Nothing, Bottomo Boty es el equivalente lógico de Falso . No tiene ningún valor en absoluto, y una expresión de este tipo se puede convertir a (es decir, es un subtipo de) cualquier tipo. Este es el conjunto vacío. Tenga en cuenta que si alguien le dice que "el valor de la expresión foo () pertenece al conjunto vacío" es muy informativo: le dice que esta expresión nunca completará su ejecución normal; abortará, arrojará o colgará. Es exactamente lo contrario de void.

Entonces, lo siguiente no tiene sentido (pseudo-C ++, ya noreturnque no es un tipo de C ++ de primera clase)

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

Pero la tarea a continuación es perfectamente legítima, ya que throwel compilador entiende que no se devuelve:

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

En un mundo perfecto, podría usar noreturncomo valor de retorno para la función raise()anterior:

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

Lamentablemente, C ++ no lo permite, probablemente por razones prácticas. En cambio, le brinda la capacidad de utilizar [[ noreturn ]]atributos que ayudan a guiar las optimizaciones y advertencias del compilador.

Elazar
fuente
55
Nada se puede moldear a voidy voidnunca se evalúa a trueo falseo cualquier otra cosa.
Más claro
66
Cuando digo true, no me refiero al "valor truedel tipo bool" sino al sentido lógico, ver correspondencia de Curry-Howard
Elazar
8
La teoría abstracta de tipos que no se ajusta al sistema de tipos de un idioma específico es irrelevante cuando se analiza el sistema de tipos de ese idioma. La pregunta, en cuestión :-), es sobre C ++, no sobre la teoría de tipos.
Más claro
8
(void)true;es perfectamente válido, como sugiere la respuesta. void(true)es algo completamente diferente, sintácticamente. Es un intento de crear un nuevo objeto de tipo voidllamando a un constructor truecomo argumento; esto falla, entre otras razones, porque voidno es de primera clase.
Elazar
2
Así es. Estoy acostumbrado a usar la conversión de tipo (valor), no la conversión de estilo c.
Más claro