valores tipos sentencia retorno programacion lenguaje funciones funcion con c++ function object return language-lawyer

c++ - sentencia - tipos de return en c



Qué sucede cuando una función que devuelve un objeto finaliza sin una declaración de retorno (3)

En C ++, ¿qué sucede cuando una función que se supone que devuelve un objeto finaliza sin una declaración de devolución? ¿Qué se devuelve?

p.ej

std::string func() {}


¿Qué se devuelve?

No lo sabemos Según la norma, el comportamiento es indefinido.

§6.6.3 / 2 La declaración de devolución [stmt.return] :

(énfasis mío)

Fluir desde el final de un constructor, un destructor o una función con un tipo de retorno cv void es equivalente a un return sin operando. De lo contrario, si sale del final de una función que no sea main ( basic.start.main ), se basic.start.main un comportamiento indefinido .

De hecho, la mayoría de los compiladores darían una advertencia, como Clang :

advertencia: el control llega al final de la función no nula [- Tipo de retorno]


En C ++, ¿qué sucede cuando una función que se supone que devuelve un objeto finaliza sin una declaración de devolución?

Causa un comportamiento indefinido. Nadie puede decir lo que sucederá exactamente.


Tenía curiosidad, por lo que hice algunas pruebas en Visual C ++ 2015.

int f() { if (false) return 42; // oops } int main() { int i = f(); }

Tuve que agregar el if para obtener una advertencia en lugar de un error:

> cl /nologo /FAs /c a.cpp a.cpp(6) : warning C4715: ''f'': not all control paths return a value

El código de ensamblaje que se genera es bastante simple y he eliminado las partes irrelevantes. Aquí está la carne de f() :

f: xor eax, eax je label mov eax, 42 label: ret

La línea xor es básicamente eax=0 . Porque if (false) es una condición constante, el código generado ni siquiera se molesta en hacer una comparación y luego saltará incondicionalmente a la label , que simplemente regresa de la función. Puede ver que el "valor de retorno" ( 42 ) en realidad se almacenaría en eax , pero esta línea nunca se ejecutará. Por lo tanto, eax == 0 .

Esto es lo que hace main() :

call f mov _i$[ebp], eax ret

Llama a f() y copia ciegamente eax en una ubicación en la pila (donde está i ). Por lo tanto, i == 0 .

Probemos algo más complicado con un objeto y un constructor:

struct S { int i=42; }; S f() { if (false) return {}; // oops } int main() { S s = f(); }

Lo que hace main() básicamente es reservar bytes de sizeof(S) en la pila, poner la dirección del primer byte en eax y luego llamar a f() :

lea eax, _s$[ebp] push eax call f

De nuevo, f() no hará nada, ya que saltará incondicionalmente al final de la función:

f: xor eax, eax je label ; some stuff ; call S::S(), which would set i to 42 ; but none of that will happen label: ret

Entonces, ¿qué pasó con los bytes sizeof(S) en main? Nunca fueron cambiados. Contienen todo lo que ya estaba en la memoria en ese lugar en particular. Contienen basura.

Esto es con una compilación no optimizada, en una versión dada de un compilador dado. Cambia el compilador, cambia el comportamiento. Habilitar el optimizador, cambiar drásticamente el comportamiento .

No lo hagas