void형 함수는 반환을 하긴 하는걸까?

#include <cstdio>

void sayHello();

int main(){
    sayHello();
    return 0;
}

void sayHello(){
    printf("Hello, World!\n");
    return; // <-- 왜 있는거임
}

void 자료형을 반환하는걸까? 반환을 하지 않는걸까?

반환을 할까요? 안할까요? 이걸 알려면 반환하는게 무엇인지 정의를 내려야겠네여.
그 정의는 바로 함수호출규약(Calling Convention)에 있읍니다.
함수호출규약에는 stdcall, cdecl 등이 있고요.
VS의 기본값은 cdecl이네요.

cdecl 규약에서 반환한다의 의미는 이렇네요.

그러니까 반환하는게 있으면 EAX같은 레지스터에 담아서 caller(함수를 호출한)에게 전달한다 이거죠.
반환하는게 없는 경우는 내용이 없는거 보니까, 반환을 아예 안한다는 상식(?)같은 판단을 따르는 건가보네요.

실제 한 번 봐봅시당.
다음과 같은 간단한 코드가 있읍니다.

이 코드는 cdecl 규약을 따르고 있고요.
반환하는 것을 보려면 디스어셈블리를 봅시다.

image

차이가 보이나연? ㅋ-ㅋ
숙5~

아 그리고 마지막에 return;은 왜 있는건가 생각해보니… 꼭 반환을 하지 않더라도 함수의 종료를 나타내기도 하니까요.

void ReturnEarly()
{
    if(이러이러한 조건이면 진행 안하고 끝냄)
    {
        return;
    }
}
4 Likes
#include <cstdio>
#include <cmath>

int intRetn(){
    return abs(-1);
};
void voidRetn(){
    abs(-2);
};
__declspec(naked) int EAX(){    
    __asm ret;    
}

int main()
{
    printf("int  returns : %d\n", (intRetn(), EAX()));   //int  returns : 1
    printf("void returns?: %d\n", (voidRetn(), EAX()));  //void returns?: 2
    return 0;
}

void가 반환을 안한다는 말은 C 안쪽이냐 C 밖이냐에 따라 달라질 것 같습니다.

//Windows 32bit Assembly

.text:00401EC0 int __cdecl intRetn(void) proc near     ; CODE XREF: intRetn(void)↑j
.text:00401EC0                 push    ebp
.text:00401EC1                 mov     ebp, esp
.text:00401EC3                 push    -1              ; Number
.text:00401EC5                 call    _abs
.text:00401ECA                 add     esp, 4
.text:00401ECD                 cmp     ebp, esp
.text:00401ECF                 call    j___RTC_CheckEsp
.text:00401ED4                 pop     ebp
.text:00401ED5                 retn
.text:00401ED5 int __cdecl intRetn(void) endp

.text:00401EE0 void __cdecl voidRetn(void) proc near   ; CODE XREF: voidRetn(void)↑j
.text:00401EE0                 push    ebp
.text:00401EE1                 mov     ebp, esp
.text:00401EE3                 push    -2              ; Number
.text:00401EE5                 call    _abs
.text:00401EEA                 add     esp, 4
.text:00401EED                 cmp     ebp, esp
.text:00401EEF                 call    j___RTC_CheckEsp
.text:00401EF4                 pop     ebp
.text:00401EF5                 retn
.text:00401EF5 void __cdecl voidRetn(void) endp

.text:00401F00 int __cdecl EAX(void) proc near         ; CODE XREF: EAX(void)↑j
.text:00401F00                 retn    0
.text:00401F00 int __cdecl EAX(void) endp

말씀하신 예제로는 설명할 수 없는 결과를 보면, 서브루틴 intRetn과 voidRetn이 완전히 동일한 instruction을 가집니다. 하나는 반환하지 않고, 하나는 반환한다고 했지만 컴파일러를 거친 결과는 같으니 '반환한다는 말’이 좀 더 자세히 짚어져야 할 것입니다.

우선 C로 짰으니, C의 입장을 들어보겠습니다.

A return statement with an expression shall not appear in a function whose return type is void . A return statement without an expression shall only appear in a function whose return type is void .

1999년, 2011년도의 C 표준에서 void return에 관한 내용을 찾아보면 void에 return을 사용해야 한다/아니다 정도의 가이드만 주고 return문이 정말 return을 하지 않는지는 말해주고 있지 않습니다.

즉, 반환값이 void인 함수가 return을 하냐 마냐의 문제는 C가 강제할 수 없는(혹은 강제하지 못하는) 문제이며, 이는 컴파일러가 결정해줄 것이라 조심스럽게 예상해볼 수 있겠습니다.

확실한 답을 위해 나머지 답안을 stackoverflow의 것으로 갈음합니다.

앞서 제기한 문제: 왜 intRetn과 voidRetn은 어셈블리 상에서 동일한가? 에 대한 답으로 요약하겠습니다.

상기한 질문의 답은 '우리가 사용하는 이진 바이너리 인터페이스(ABI)에서, intRetn의 반환값은 당연히 int형의 scratch register를 사용하고 voidRetn의 반환값은 (사용되거나 보존되어야 할 이유는 없지만) 이 역시 반환시 int형의 scratch register를 사용하도록 허가받았기 때문이다’이며, machine은 그 둘을 구분할 수 없다로 줄일 수 있겠습니다.

void형 함수는 반환을 하긴 하는걸까?
-> 반환하나 ABI에 따라 보장되지 않음. C 문법상으로 void의 반환값을 사용할 수 없다(해서는 안된다).

5 Likes

오우썸!