"noreturn"함수가 반환되는 이유는 무엇입니까?
호출자에게 반환되지 않는 함수에 사용되는 속성에 대한 이 질문을 읽었습니다 noreturn
.
그런 다음 C로 프로그램을 만들었습니다.
#include <stdio.h>
#include <stdnoreturn.h>
noreturn void func()
{
printf("noreturn func\n");
}
int main()
{
func();
}
그리고 이것을 사용하여 코드 어셈블리를 생성 했습니다 .
.LC0:
.string "func"
func:
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %edi
call puts
nop
popq %rbp
ret // ==> Here function return value.
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
call func
속성 func()
을 제공 한 후 함수가 반환되는 이유는 무엇 noreturn
입니까?
C의 함수 지정 자는 컴파일러에 대한 힌트 이며 수용 정도는 구현이 정의됩니다.
우선, _Noreturn
함수 지정자 (또는, noreturn
using <stdnoreturn.h>
)는 이 함수가 결코 반환하지 않을 것이라는 프로그래머 의 이론적 약속 에 대한 컴파일러에 대한 힌트 입니다. 이 약속에 따라 컴파일러는 특정 결정을 내리고 코드 생성을위한 최적화를 수행 할 수 있습니다.
IIRC, 함수 지정자로 noreturn
지정된 함수가 결국 호출자에게 반환되면
- 사용 및 명시 적
return
진술 - 기능 본문 끝에 도달하여
동작은 정의되지 않는다 . 당신은 안 함수에서 반환합니다.
명확하게하기 위해 noreturn
함수 지정자를 사용한다고해서 호출자에게 반환되는 함수 양식이 중지되지는 않습니다 . 최적화 된 코드를 생성 할 수있는 더 많은 자유를 허용하는 것은 프로그래머가 컴파일러에 대한 약속입니다.
자, 만약 당신이 일찍 그리고 나중에 약속을했다면 이것을 위반하기로 선택하면 결과는 UB입니다. 컴파일러는 _Noreturn
함수가 호출자에게 반환 할 수있는 것처럼 보일 때 경고를 생성하도록 권장되지만 필수는 아닙니다 .
장 §6.7.4 C11
,, 단락 8에 따라
_Noreturn
함수 지정자로 선언 된 함수는 호출자에게 반환되지 않습니다.
그리고 단락 12, ( 주석에 유의하십시오 !! )
EXAMPLE 2 _Noreturn void f () { abort(); // ok } _Noreturn void g (int i) { // causes undefined behavior if i <= 0 if (i > 0) abort(); }
의 경우 C++
동작이 매우 유사합니다. 장 §7.6.4,, C++14
단락 2 에서 인용 ( 내 강조 )
이전에 속성으로 선언 된
f
위치 에서 함수 가 호출 되고 결국 반환되면 동작이 정의되지 않습니다.f
noreturn
f
[참고 : 예외가 발생하여 함수가 종료 될 수 있습니다. —end note][참고 : 표시된 함수
[[noreturn]]
가 반환 될 수 있는 경우 구현시 경고를 발행하는 것이 좋습니다 . —end note]3 [예 :
[[ noreturn ]] void f() { throw "error"; // OK } [[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0 if (i > 0) throw "positive"; }
-예제 종료]
noreturn 속성을 제공 한 후 함수 func ()가 반환되는 이유는 무엇입니까?
당신이 그것을 알려주는 코드를 작성했기 때문입니다.
당신이 반환, 전화로 기능하지 않으려는 경우 exit()
나 abort()
또는 이와 유사한 그것을 반환하지 않도록.
함수가 호출 된 후 반환하는 것 외에 무엇을 printf()
할까요?
C 표준 의 6.7.4 기능 지정자 조 (12)는 구체적으로는 예 포함 noreturn
실제로 돌아갈 수 기능 -과 같은 동작 라벨 정의를 :
예 2
_Noreturn void f () {
abort(); // ok
}
_Noreturn void g (int i) { // causes undefined behavior if i<=0
if (i > 0) abort();
}
즉, noreturn
A는 제한 하는 것이 당신이 에 배치 당신 은 컴파일러 알 - 코드 "지금까지 반환하지 않습니다 MY 코드를" . 그 제한을 위반하면 그게 전부입니다.
noreturn
약속이야. 여러분은 컴파일러에게 "명백 할 수도 있고 아닐 수도 있지만, 제가 코드를 작성한 방식에 따라이 함수는 절대 반환되지 않을 것임을 압니다."라고 말합니다. 이렇게하면 컴파일러가 함수가 제대로 반환되도록하는 메커니즘을 설정하는 것을 피할 수 있습니다. 이러한 메커니즘을 생략하면 컴파일러가보다 효율적인 코드를 생성 할 수 있습니다.
함수가 반환되지 않는 이유는 무엇입니까? 한 가지 예는 exit()
대신 호출 하는 경우입니다.
하지만 컴파일러에게 함수가 반환되지 않는다고 약속하고 컴파일러가 함수가 제대로 반환되도록 준비하지 않은 경우 반환하는 함수를 작성 하면 컴파일러는 무엇을 해야 합니까 ? 하다? 기본적으로 세 가지 가능성이 있습니다.
- 당신에게 "잘"하고 어쨌든 함수가 제대로 반환되도록하는 방법을 알아 내십시오.
- 함수가 부적절하게 반환되면 충돌하거나 임의로 예측할 수없는 방식으로 동작하는 코드를 내 보냅니다.
- 약속을 어겼다는 경고 또는 오류 메시지를 제공하십시오.
컴파일러는 1, 2, 3 또는 일부 조합을 수행 할 수 있습니다.
이것이 정의되지 않은 동작처럼 들리면 그 때문입니다.
실제 생활에서와 같은 프로그래밍의 결론은 다음과 같습니다. 지킬 수없는 약속을하지 마십시오. 다른 사람이 당신의 약속에 따라 결정을 내렸을 수 있으며, 당신이 약속을 어기면 나쁜 일이 일어날 수 있습니다.
noreturn
속성이있는 약속입니다 당신이 당신의 기능에 대한 컴파일러 할 수 있습니다.
당신이 경우 어떻게 이러한 기능에서 수익을, 행동은 정의되지 않는다, 그러나 이것은 완전히 제거하여 제정신 컴파일러는 엉망 응용 프로그램의 상태를 당신을 수 있습니다 의미하지 않는다 ret
컴파일러는 종종 심지어 사실을 유추 할 수있을 것이다, 특히 이후 문을 반환이 실제로 가능합니다.
그러나 이것을 작성하면 :
noreturn void func(void)
{
printf("func\n");
}
int main(void)
{
func();
some_other_func();
}
그런 다음 컴파일러가 some_other_func
완전히 제거하는 것이 합리적입니다 .
다른 사람들이 언급했듯이 이것은 고전적인 정의되지 않은 동작입니다. func
돌아 오지 않겠다고 약속 했지만 어쨌든 돌아 오게했습니다. 그것이 깨질 때 당신은 조각을 집어 들게됩니다.
컴파일러 func
는 일반적인 방식으로 컴파일되지만 (에도 불구하고 noreturn
) noreturn
함수 호출 에 영향을줍니다.
You can see this in the assembly listing: the compiler has assumed, in main
, that func
won't return. Therefore, it literally deleted all of the code after the call func
(see for yourself at https://godbolt.org/g/8hW6ZR). The assembly listing isn't truncated, it literally just ends after the call func
because the compiler assumes any code after that would be unreachable. So, when func
actually does return, main
is going to start executing whatever crap follows the main
function - be it padding, immediate constants, or a sea of 00
bytes. Again - very much undefined behavior.
This is transitive - a function that calls a noreturn
function in all possible code paths can, itself, be assumed to be noreturn
.
According to this
If the function declared _Noreturn returns, the behavior is undefined. A compiler diagnostic is recommended if this can be detected.
It is the programmer's responsibility to make sure that this function never returns, e.g. exit(1) at the end of the function.
ret
simply means that the function returns control back to the caller. So, main
does call func
, the CPU executes the function, and then, with ret
, the CPU continues execution of main
.
Edit
So, it turns out, noreturn
does not make the function not return at all, it's just a specifier that tells the compiler that the code of this function is written in such a way that the function won't return. So, what you should do here is to make sure that this function actually doesn't return control back to the callee. For example, you could call exit
inside it.
Also, given what I've read about this specifier it seems that in order to make sure the function won't return to its point of invocation, one should call another noreturn
function inside it and make sure that the latter is always run (in order to avoid undefined behavior) and doesn't cause UB itself.
no return function does not save the registers on the entry as it is not necessary. It makes the optimisations easier. Great for the scheduler routine for example.
See the example here: https://godbolt.org/g/2N3THC and spot the difference
TL:DR: It's a missed-optimization by gcc.
noreturn
is a promise to the compiler that the function won't return. This allows optimizations, and is useful especially in cases where it's hard for the compiler to prove that a loop won't ever exit, or otherwise prove there's no path through a function that returns.
GCC already optimizes main
to fall off the end of the function if func()
returns, even with the default -O0
(minimum optimization level) that it looks like you used.
The output for func()
itself could be considered a missed optimization; it could just omit everything after the function call (since having the call not return is the only way the function itself can be noreturn
). It's not a great example since printf
is a standard C function that is known to return normally (unless you setvbuf
to give stdout
a buffer that will segfault?)
Lets use a different function that the compiler doesn't know about.
void ext(void);
//static
int foo;
_Noreturn void func(int *p, int a) {
ext();
*p = a; // using function args after a function call
foo = 1; // requires save/restore of registers
}
void bar() {
func(&foo, 3);
}
(Code + x86-64 asm on the Godbolt compiler explorer.)
gcc7.2 output for bar()
is interesting. It inlines func()
, and eliminates the foo=3
dead store, leaving just:
bar:
sub rsp, 8 ## align the stack
call ext
mov DWORD PTR foo[rip], 1
## fall off the end
Gcc still assumes that ext()
is going to return, otherwise it could have just tail-called ext()
with jmp ext
. But gcc doesn't tailcall noreturn
functions, because that loses backtrace info for things like abort()
. Apparently inlining them is ok, though.
Gcc could have optimized by omitting the mov
store after the call
as well. If ext
returns, the program is hosed, so there's no point generating any of that code. Clang does make that optimization in bar()
/ main()
.
func
itself is more interesting, and a bigger missed optimization.
gcc and clang both emit nearly the same thing:
func:
push rbp # save some call-preserved regs
push rbx
mov ebp, esi # save function args for after ext()
mov rbx, rdi
sub rsp, 8 # align the stack before a call
call ext
mov DWORD PTR [rbx], ebp # *p = a;
mov DWORD PTR foo[rip], 1 # foo = 1
add rsp, 8
pop rbx # restore call-preserved regs
pop rbp
ret
This function could assume that it doesn't return, and use rbx
and rbp
without saving/restoring them.
Gcc for ARM32 actually does that, but still emits instructions to return otherwise cleanly. So a noreturn
function that does actually return on ARM32 will break the ABI and cause hard-to-debug problems in the caller or later. (Undefined behaviour allows this, but it's at least a quality-of-implementation problem: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158.)
This is a useful optimization in cases where gcc can't prove whether a function does or doesn't return. (It's obviously harmful when the function does simply return, though. Gcc warns when it's sure a noreturn function does return.) Other gcc target architectures don't do this; that's also a missed optimization.
But gcc doesn't go far enough: optimizing away the return instruction as well (or replacing it with an illegal instruction) would save code size and guarantee noisy failure instead of silent corruption.
And if you're going to optimize away the ret
, optimizing away everything that's only needed if the function will return makes sense.
Thus, func()
could be compiled to:
sub rsp, 8
call ext
# *p = a; and so on assumed to never happen
ud2 # optional: illegal insn instead of fall-through
Every other instruction present is a missed optimization. If ext
is declared noreturn
, that's exactly what we get.
Any basic block that ends with a return could be assumed to never be reached.
참고URL : https://stackoverflow.com/questions/45981545/why-does-noreturn-function-return
'IT TIP' 카테고리의 다른 글
Firebase 클래스에 직렬화 할 속성이 없습니다. (0) | 2020.10.30 |
---|---|
속성 텍스트 중심 정렬 (0) | 2020.10.30 |
교리 2로 마지막 삽입 ID를 얻습니까? (0) | 2020.10.30 |
Facebook 그래프 API를 사용하여 사용자 프로필 사진을 표시하려면 어떻게해야합니까? (0) | 2020.10.30 |
Enter 키를 눌렀을 때 실행되는 WPF TextBox 명령 (0) | 2020.10.30 |