c에서 함수가 끝나도, 지역변수는 남아있는 건가요?

tmal의 이미지

# include<stdio.h>
 
char* func()
{
	char c = 12;
 
	return &c;
}
 
int main()
{
	char*ptr = func();
	printf("%d", *ptr);
 
}

함수가 끝나면, 지역변수가 사라진다고 알고 있는데요. 실행을 해보니 "지역 변수 또는 임시: c의 주소를 반환하고 있습니다."라는 경고가 뜹니다. 그런데, printf("%d", *ptr); 부분에서 12가 잘도 출력되더군요.

c의 값을 바꾸어 봐도, 바꾼 값이 제대로 출력됩니다. 왜 그러는 걸까요?

스택이라고 하니 어셈이 생각나더군요. 제가 예전에 어셈을 한번 맛보고 싶어서, (8086)어셈에서 간단한 것들만 찾아본 적이 있습니다. 거기서 함수 부분과 스택 부분을 봤었는데요,

함수를 호출할때,

1. 함수의 인자들과 복귀주소를 스택에 Push함.

2. Mov bp, sp 이렇게 하고, 함수 내용 전개.

3. 함수 내용이 끝났으면, Mov sp, bp를 하고, ret명령으로 복귀주소를 스택에서 꺼냄.

4. 함수 호출이 끝나고 나서, sp에 인자들의 크기만큼 Add해줌.

4번의 설명을 보니까, 함수의 호출이 끝나면, 스택에 남아있는 인자들을 지워주어야 한다고 하더군요. POP을 하는 방법도 있지만, Add 명령을 사용하는 것이 더 명령이 적으므로, 이렇게 사용한다고 하더군요.
그런데 sp는 돌아갔지만, 스택에는 값이 남아 있다고 했습니다.

제가 질문하고 싶은 것은 c도 함수를 호출할때, 이런식으로 되는 것인가요? 만약 맞다면, 함수의 인자와 지역변수들은 스택에 고스란히 남아 있는 건가요? 궁금합니다.

ifree의 이미지

pop을 하건 add를 하건 스택 포인터의 값만 바뀌는 겁니다.
해당 스택 주소 내의 값은 그대로 있지만, 유효한 값은 아닙니다.
이후의 과정에서 덮어 쓸수 있게 됩니다.

tmal의 이미지

그렇다면, c에서 함수가 종료되어도 스택에 함수의 인자와 지역변수들은 값이 남아 있다는 건가요?

스택 주소 내에 남아있는 값은 유효하지 않다고 하셨습니다. 그런데, 유효하다는 것은 어떻게 받아들여야 할까요?
경고는 뜨긴 하지만, 일단 값은 나오기는 나오는데.. 유효하다는 것이 이 경고가 뜨고 안뜨고를 말하는 건가요?

익명 사용자의 이미지

네. 남아 있습니다.

언어 스펙에 따르면 지역 변수는 지역을 벗어나면 사라지지만, 현실적으로는 지역 변수가 차지하던 공간이 갑자기 어디로 가버리는 건 아닙니다.
그러니 잠깐 사이에 지역 변수가 담고 있던 값이 유지되고 있다고 해도 전혀 신기할 일은 아니지요.

이건 마치 쓰레기통에 버려진 물건이 한동안은 거기 고스란히 남아있는 것과 같습니다. 한동안은 쓰레기통을 뒤적거려서 내가 버렸던 물건이 아직 남아 있는지 확인해 볼 수 있겠지요.

하지만 언젠간 쓰레기통이 비워질 겁니다.

덧. C언어를 공부하면서 이상한 프로그램을 짜놓고 왜 이상한 결과가 나오지 않느냐고 의문을 가질 필요는 없습니다. 애초에 그런 걸 보장하고 있지 않기 때문이죠.

C언어가 보장하는 건 제대로 짠 프로그램이 제대로 동작해야 한다는 것입니다.

라스코니의 이미지

C 언어는 함수 호출시 함수 매개 변수(parameters)를 넘겨주고 호출되는 함수의 지역 변수(local variables) 공간을 만들기 위해 스택 영역을 사용합니다.

함수는 순차적으로 수행됨으로 함수가 호출되면 스택에 공간이 줄어들고, 함수에서 복귀하면 그만큼 스택 공간이 회복됩니다. 그런 공간입니다. 함수 호출시 용도가 끝나면 스택에 무슨 값이 남아있던 쓸모없는 값입니다.
다만 스택에 있는 주소를 있는 값들을 출력해 보면 스택이 어떻게 동작하는지 알수 있습니다.

func()에 좀더 많은 지역 변수들을 생성해 보시고 main()에서 그 값들과 주소값들을 찍어보세요.

tmal의 이미지

한번 main()과 func()에 지역변수를 더 생성해보고, 주소값과 값들을 출력해보았는데요.

# include<stdio.h>
 
void func(char a, char b)
{
	char c=7, d=8;
 
	printf("a의 주소값, 값: %p, %d\n", &a, a);
	printf("b의 주소값, 값: %p, %d\n", &b, b);
	printf("c의 주소값, 값: %p, %d\n", &c, c);
	printf("d의 주소값, 값: %p, %d\n\n", &d, d);
}
 
int main()
{
	char A = 1;
	char B = 2;
 
	int C = 3;
	int D = 4;
 
	printf("A의 주소값, 값: %p, %d\n", &A, A);
	printf("B의 주소값, 값: %p, %d\n", &B, B);
	printf("C의 주소값, 값: %p, %d\n", &C, C);
	printf("D의 주소값, 값: %p, %d\n\n", &D, D);
 
	func(5, 6);
 
}

실행결과는

A의 주소값, 값: 002AFE6F, 1
B의 주소값, 값: 002AFE63, 2
C의 주소값, 값: 002AFE54, 3
D의 주소값, 값: 002AFE48, 4

a의 주소값, 값: 002AFD70, 5
b의 주소값, 값: 002AFD74, 6
c의 주소값, 값: 002AFD5F, 7
d의 주소값, 값: 002AFD53, 8

이런식으로 나왔습니다.
이 코드를 몇 번씩 돌려보면서 발견한 것이, 주소값은 계속 랜덤으로 바뀌며 , 이 주소값들간의 거리는 매번 같고, 변수를 선언할때마다,선언한 변수들의 주소값은 점점 낮아지고 , int형, char형을 선언한다고 해서, 주소값이 전에 선언한 변수로부터 -4, -1이 되지는 않는다는 것
을 발견했습니다.

여기서 궁금한 것이 생겼습니다. 평소에는 신경 쓰지는 않았지만, 주소값은 어째서 매번 바뀌는 건가요? 그리고

변수를 선언했을 때, (전에 선언한 변수의 주소값) - (그 다음에 선언할 변수의 자료형의 크기)이렇게 주소값이 정해지는 것이 아닌 것인 가요? 예를 들어, 전에 선언한 변수의 주소값이 0x123이라고 하고, 그 다음에 선언할 변수의 자료형이 int라고 할때, 선언할 변수의 주소값은 0x123 -4 = 0x119 이렇게 되는 것이 아닌가요? 위의 소스코드에서는 이런식으로 되지 않고, 12, 15정도 떨어져서 주소값이 정해지더군요. 혹시, 변수끼리 공간을 침범하지 말라고 이렇게 주소값을 정해주는 건가요? 궁금합니다.

Hodong Kim@Google의 이미지

main 함수에 func 함수가 있는데.. 1번 호출하잖아요.
그걸 for 문으로 10번 정도 돌려보세요.
값이 변하는지 함 보세요.

tmal의 이미지

여러번 반복을 해도 func함수를 호출할때, 사용하는 인자와 지역변수들은 주소값이 처음 호출했을때와 그대로 나왔습니다.

그래서, 함수를 호출하면, 스택이 줄어들고, 호출이 끝나면 스택이 회복되고 이러는 것이라는 것이군요.진짜 그렇게 나오는 군요. 직접 확인해볼 수 있었습니다. 감사합니다~.

사실, 제가 질문한 것은 함수를 호출할때 인자&지역변수의 주소값을 말하는 것이 아니라, 컴파일시, 매번 바껴서 출력되는 주소값을 말한 것입니다. 그런데, Hodong님이 말씀하신 것을 하다가 한가지 궁금한게 생겼는데, 이것도 질문해도 될까요?

제가 올린 코드에서 반복문으로 여러번 func함수 호출을 돌린 코드입니다.

# include<stdio.h>
 
void func(char a, char b)
{
	char c=7, d=8;
 
	printf("a의 주소값, 값: %p, %d\n", &a, a);
	printf("b의 주소값, 값: %p, %d\n", &b, b);
	printf("c의 주소값, 값: %p, %d\n", &c, c);
	printf("d의 주소값, 값: %p, %d\n\n", &d, d);
}
 
int main()
{
	char A = 1;
	char B = 2;
 
	int C = 3;
	int D = 4;
 
	printf("A의 주소값, 값: %p, %d\n", &A, A);
	printf("B의 주소값, 값: %p, %d\n", &B, B);
	printf("C의 주소값, 값: %p, %d\n", &C, C);
	printf("D의 주소값, 값: %p, %d\n\n", &D, D);
 
	for(int i=0; i< 10; i++)
	func(5, 6);
 
	char E=1;
 
	printf("E의 주소값, 값: %p, %d\n\n", &E, E);
 
	func(5, 6);
 
}

for문으로 func함수를 호출했을때, 같은 주소값이 여러번 출력되는 것은 이해가 됬습니다. 그런데, main()에서 그 다음줄인 char E=1;을 실행하고 나면, 스택에 이 만큼의 공간이 채워질텐데, 마지막 줄의 func호출에서는 func함수의 인자와 지역변수들이 어째서 아까전과 같은 주소값이 나오는 걸까요?

혹시, 함수를 만들었다는 것은 나중에도 여러번 쓸 거라는 뜻이니까, func함수가 처음으로 호출될때 썼던 스택 공간을 func전용 공간으로 남겨둔 것인가요?(func함수를 호출할때는 이 공간만을 사용함(?))

라스코니의 이미지

아마 컴파일러는 이미 char E; 라는 것을 알고 있기 때문에 미리 스택에 공간을 잡아놔서 그럴 겁니다.
이렇게 해보세요.

   {
	char E=1;
 
	printf("E의 주소값, 값: %p, %d\n\n", &E, E);
 
	func(5, 6);
   }

블록( { ... } ) 으로 구분된 공간을 별도의 함수 공간으로 구분됩니다. 따라서 앞의 func()와 { } 블록 안의 func()는 다른 스택 공간을 가질 수도 있어요.

Hodong Kim@Google의 이미지

아무튼,, 저도 해보니까.. 값이 똑같이 나오던데.. 아래처럼 사용하시면 안 되요.

#include <stdio.h>
 
char *func_a (char a)
{
	return &a;
}
 
char *func_b ()
{
	char b;
 
	return &b;
}
 
int main()
{
	char *ap;
	char *bp;
	int   i;
 
	ap = func_a (5);
	bp = func_b ();
 
	for (i = 0; i < 1000000; i++)
	{
		if (ap != func_a (i))
			puts ("ap != func_a (i)");
 
		if (bp != func_b ())
			puts ("bp != func_b ()");
	}
}

컴파일할 때도 주의 메시지가 나옵니다.

hodong@debian:~$ gcc -o test-c test-c.c
test-c.c: In function ‘func_a’:
test-c.c:5:9: warning: function returns address of local variable [-Wreturn-local-addr]
  return &a;
         ^~
test-c.c: In function ‘func_b’:
test-c.c:12:9: warning: function returns address of local variable [-Wreturn-local-addr]
  return &b;
         ^~

지금은 잘 작동해도 나중에 오류가 날 수 있는 부분입니다.
잠재적 버그죠. 저런 버그는 오류가 간헐적으로 발생합니다.
재현이 될 때도 있고 안 될 때도 있고.
위 함수와는 다른 함수에서 어떤 걸 출력을 했는데 다른 함수는 문제가 전혀 없는데 거기서 에러가 날 수도 있죠.
그래서 매우 질이 안 좋은 버그에요.
코드가 1만줄, 10만줄 이런데서 저런 버그 잡으려면 머리 터지니까..
저런 버그는 애초에 만들지 않는게 좋아요. 디버깅하다가 욕 튀어나오고 분위기 험악해지고 ㅎㅎㅎ

그러면 어떻하느냐?
위의 함수는 char 변수를 사용하는데, char 은 int 형(또는 uint) 으로 상호변환이 가능합니다.
값으로 전달해야죠. char * 이런거 아니니까요.

char func_a (char a)
{
  return a;
}

주소를 사용하려면 아래처럼 해야죠.
할당된 메모리는 free() 하지 않는 이상 프로그램이 종료될 때까지 보존됩니다.

/* 함수명이 그럴싸 하죠? ㅋㅋㅋ 저렇게 문자열 다루는 함수를 만들어 쓰셔도 됨 */
char *string_new (const char *str)
{
    int len = strlen (str);
    char *string = malloc (len + 1);
    /* 어쩌구 저쩌구 */
    string[len] = NULL;
 
    return string;
/* 써놓고 보니 버그가 있어서 수정했습니다 ㅎㅎㅎ;; */
}
 
string = string_new ("Hello");
 
free (string); /* 이거 깜막해서 메모리 누수 발생하는 경우가 흔해요 */

그리고 C언어에

static


이라는 거 있으니까 그거 책에서 찾아서 꼭 보세요.
디아블로에서 소서리스가 스태틱 공격 쓰는 거 아니에요 ㅋㅋ
그렇게 시간 지나다보면
컴퓨터구조, 컴파일러, 운영체제, 프로그래밍언어론에서 다 배웁니다.
배우면서 아~~~ 그래서 그랬구나... 하실거에요.
링커, 로더, 주소 재배치 이런 것도 배우고, call by reference, value by reference 등등..
화이팅~~

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.