함수내 지역변수 반환문제........

indiebox의 이미지

:mrgreen:

c언얼 공부하다 의문점이 있어 질문합니다.
다음코드에서 add()내 i가 선언되고 나서 return될때
제가 알기런 함수가 종료되면서 함수내 지역변수는 stack에서 사라지는 걸로
알고있는데 실행상에는 아무 문제가 없네요.
혹시 add()함수가 종료하고 그값이 sum에 저장되고 난후
사라지는 건가요?
아니면 제가 잘못 알고 있나요?
자세한 답변 부탁 드립니다.

#include <stdio.h>

int add(int a, int b)
{
int i;
i=a+b;
return i;
}

int main()
{
int a=1;
int b=2;
int sum;

sum=add(a, b);
printf("%d\n", sum);
return 0;
}

vacancy의 이미지

리턴되는 것은 변수 i가 아니라
변수 i가 가지고 있는 값입니다.

maddog의 이미지

일난 return i ; 하게 되면 i가 직접 caller에게 전달되는 것이 아니라
i의 사본이 전달됩니다. 뭐 i가 단순하게 int 또는 char 라면 직접 전달
되는 것이나 사본이 전달되는 것이나 별 차이가 없어보이기는 합니다만,

예전에 Turbo Assembler 뭐라는 책이 있었는데 어셈블러로 C와 인터페이스
하는 내용이 있어서 본적이 있었죠. C, 적어도 Turbo C에서는 함수가
int를 리턴하면 그 값을 AX 레지스터에 담아서 리턴합니다. 위에서 i를
리턴한다면 그 i는 실제로 스택에 있지만 리턴하기 전에 스택에 있는 i값을
AX로 '복사'한 후 이 AX를 리턴하는 것이죠. (물론 그 순간에 스택에 있는
i값은 사라집니다.) AX 하나에 담지 못하는 LONG 같은 경우에는 AX:BX 인지
AX:DX인지 잘 기억은 안나지만 하여튼 두개의 레지스터를 사용해서
리턴한다고 합니다. 더불어 레지스터로는 도저히 담을 수 없는 구조체 같은
것은 AX:DX에 구조체의 주소를 담아서 리턴한다고 되었던 기억이 있네요.
그때는 별로 그럴 일이 없어서 깊게 생각하지 않았는데 오늘 님의 질문을
보고 궁금해서 VC++로 테스트해봤습니다.

struct typeA
{
  int a ;
  int b ;
  int c[10] ;
} ;

typeA initA()
{
  typeA a = { 0,  } ;
  return a ;
}

typeA a ;
int main()
{
  a = initA() ;
}

디버그 모드에서 어셈으로 변환된 main()함수는 다음과 같습니다.

;	COMDAT _main
_TEXT	SEGMENT
$T596 = -48
$T597 = -96
_main	PROC NEAR					; COMDAT

; 38   : {

	push	ebp
	mov	ebp, esp
	sub	esp, 160				; 000000a0H
	push	ebx
	push	esi
	push	edi
	lea	edi, DWORD PTR [ebp-160]
	mov	ecx, 40					; 00000028H
	mov	eax, -858993460				; ccccccccH
	rep stosd

; 39   : 	a = init() ;

	lea	eax, DWORD PTR $T597[ebp]
	push	eax
	call	?init@@YA?AUtypeA@@XZ			; init
	add	esp, 4
	mov	esi, eax
	mov	ecx, 12					; 0000000cH
	lea	edi, DWORD PTR $T596[ebp]
	rep movsd
	mov	ecx, 12					; 0000000cH
	lea	esi, DWORD PTR $T596[ebp]
	mov	edi, OFFSET FLAT:?a@@3UtypeA@@A		; a
	rep movsd

; 40   : 	return 0 ;

	xor	eax, eax

; 41   : }

	pop	edi
	pop	esi
	pop	ebx
	add	esp, 160				; 000000a0H
	cmp	ebp, esp
	call	__chkesp
	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP

잘 보시면 initA를 호출하기 전에 무언가 스택에 집어넣는 것을 볼 수 있네요.

; PART1
	lea	eax, DWORD PTR $T597[ebp]
	push	eax
	call	?init@@YA?AUtypeA@@XZ			; init

그리고 호출을 끝낸 후에 리턴값을 소스 포인터로 해서 임시 버퍼에 복사를
하네요.

; PART2
	mov	esi, eax
	mov	ecx, 12					; 0000000cH
	lea	edi, DWORD PTR $T577[ebp]
	rep movsd

이 모든 것이 끝난 다음에서야 비로소 a로 아까 복사한 데이터를 다시
복사하는 군요.

; PART3
	mov	ecx, 12					; 0000000cH
	lea	esi, DWORD PTR $T577[ebp]
	mov	edi, OFFSET FLAT:?a@@3UtypeA@@A		; a
	rep movsd

PART3에서 a로 복사되는 데이터는 PART2에서 init 함수의 리턴값으로
넘겨준 EAX가 가르키는 버퍼에 있던 값입니다. 이 버퍼의 실제 값은
main함수의 스택에 있는 160바이트에서 -96(=$T578) 입니다. 이
값은 main 함수가 init를 호출하기 전에 넘겨줬던 값이죠. (실제로
init 쪽에서 어떻게 작업하는지에 대한 ASM 코드는 생략합니다. 글이
길어져서... 직접 확인해보세요. ^^;)

간단히 설명하면 구조체를 리턴하는 함수일 경우에는 caller가 리턴할 때
구조체를 임시로 보관할 버퍼를 알려준다는 얘기입니다. callee는 a를 초기화
한 후에 리턴하기 위해서 a 값을 (스택이 아닌) 임시 버퍼에 복사를 하고
그 버퍼의 포인터를 EAX를 사용해서 리턴합니다. caller는 이 값을 받아서
그 버퍼로부터 다시 자신의 임시 버퍼로 복사를 하고, 자신의 임시 버퍼에서
최종 목적지인 로컬 변수 a로 복사를 합니다. 다음과 같은 작업이 되겠죠.

typeA *init( typeA *buffer )
{
  typeA a = { 0, } ;
  memcpy( buffer, &a, sizeof(typeA) ) ;
  return buffer ;
}

typeA a ;
int main()
{
  typeA bufferFor_init ;
  typeA bufferFor_localUse ;
  
  typeA *pReturn = init( &bufferFor_init ) ;
  memcpy( &bufferFor_localUse, pReturn, sizeof(typeA) ) ;

  memcpy( &a, &bufferFor_localUse, sizeof(typeA) ) ;
}

방금 C++로 클래스를 리턴할 때는 어떨까 해봤는데 (동일한 코드에서
typeA를 struct에서 생성자를 가진 class로 변경했습니다.) 한번의 생성과
두번의 복사가 일어나는 군요. 결과적으론 위 코드와 개념상으로 크게 다른
것이 없어보입니다.

아, 물론 이것은 VC++ 6.0을 사용해서 디버그 모드에서 컴파일한 결과라는
것을 다시 한번 말씀드립니다. 다른 컴파일러나 릴리즈 모드에서 컴파일한
것과는 차이가 있을 수 있겠죠.

별것 아닌거 가지고 장황하게 설명했군요. 늦은 밤에 기나긴 문서 작성하기
싫어서 시작한 짓이... -.-; 도움되셨음 합니다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
재미없는 일은 하지 않겠다는 인간 쓰레기. ㅡ,.ㅡ;;
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

see2002의 이미지

컴파일옵션을 디버그모드를 사용했을때는 아마 지역변수가
리턴되어도 값은 보존되지만... 디버그옵션을 없애고 컴파일하면,
변수가 보존이 안됩니다.
하지만, 컴파일러 종류에 따라 다르게 나올거같네요.

wind772의 이미지

<변수 i 의 값 리턴>

#include <stdio.h> 

int add(int a, int b) 
{ 
int i; 
i=a+b; 
return (i); 
} 

int main() 
{ 
int a=1; 
int b=2; 
int sum; 

sum=add(a, b); 
printf("%d\n", sum); 
return 0; 
}

<변수 i의 주소 리턴>

#include <stdio.h> 

int* add(int a, int b) 
{ 
int i; 
i=a+b; 
return (&i); 
} 

int main() 
{ 
int a=1; 
int b=2; 
int *sum; 

sum=add(a, b); 
printf("%d\n", *sum); 
return 0; 
}

지역 변수인 i가 스택 상에서 사리질 경우 잘못되는 것은
i의 주소를 리턴했을 경우겠죠^^
메모리 상에서 사라졌는데 그 주소를 사용하려니...
하지만 값을 리턴하는 경우는 상관없습니다..^^

===================================================
중요한건 얼마나 아느냐가 아니라 그것에 대한 열정이다.

see2002의 이미지

요지는....
지역변수는 리턴하지않는게 바람직하겠죠...

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.