[C언어 강좌] #11 정적변수, 지역변수, 전역변수, 외부변수, 레지스터 변수


물론 변수는 여기서 배웠는데 왜 또배우나요? 라고 할 수 있습니다.

하지만 저번에 배웠던 변수에 대한 내용들은 기초적인 내용이고, 오늘은 그 변수가 메모리에 언제 생성되고, 언제 소멸되는지, 선언되는 위치, 종류에 대해 심화학습을 해볼것입니다.

 

우선 오늘 알아볼 변수는 총 5가지 입니다.

 

지역변수 (local)
전역변수 (global)
정적변수 (static)
외부변수 (extern)
레지스터 변수 (cpu)

종류가 많아보이지만 쫄 필요는 없습니다. 그럼 시작해보겠습니다.


지역변수

지역변수(Local Variable)는 중괄호 내부, 함수의 매개변수(Parameter)에서 사용되는 변수를 의미합니다.

중괄호 밖, 함수의 밖에선 그 효력을 발휘할 수 없습니다.

지역변수에서 지역은 중괄호 내부, 함수의 내부 라고 이해할 수 있습니다.

 

 

#include <stdio.h>

void print(){
	int a = 30, b = 40;
	printf("%d %d", a,b);
}
int main(){
	int a = 10, b = 20;
	printf("%d %d \n", a,b);
	print();
    return 0;
}

10 20
30 40
--------------------------------
Process exited after 0.05916 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

0 0 형태의 함수 print() 에서도 a 와 b 를 선언, main 함수 내부에서도 a 와 b를 선언해서 출력하고있습니다.

출력값을 보면 알겠지만 둘은 중괄호 내부에서 실행되는 지역변수기 때문에

 

둘의 이름이 같을진 몰라도 지역적으론 전혀 다른 변수입니다.

int a = 30, b = 40 은 지역 print() { } 에서 실행, int a = 10, b = 20 은 지역 main() { } 에서 실행되고 있는것입니다.

 

 

#include <stdio.h>

int sum(int a, int b){
	return a+b;
}
int main(){
	int a = 10, b = 20;
	printf("%d %d \n", a,b);
	printf("%d %d \n", sum(10,30));
    return 0;
}

함수의 매개변수 또한 동일합니다. 매개변수로 선언된 a,b와 main ()안에 선언된 a,b는 서로 영향을 끼치지 않습니다.

 

지역변수의 특징

#include <stdio.h>
int main(){
	int total = 0;
	for (int i=1; i<3; i++)
	{
		int r = 10;
		total += r;
	}
	
	printf("%d", total);
	printf("%d", r); // 오류발생
	return 0;
}

지역변수는 지역내(중괄호)에서 실행되는 변수라고 했습니다.

이 예제를 실행시켜보면 r 을 출력하는 부분에서 오류가 납니다.

for문 안에서 선언된 int r은 지역변수이기 때문에 for문이 종료되면 중괄호를 탈출해 메모리에서 제거됩니다.

그렇기 때문에 main()함수에서 접근할 수 없습니다.

 

대신에 total 은 main 함수 내부 중괄호에서 선언되어서 main 함수종료 전이기 때문에 아직 출력이 가능한것입니다.

 

#include <stdio.h>
int main(){
	int total = 0;
	int r = 10;
	for (int i=1; i<3; i++)
	{
		r = 5;
		total += r;
	}
	
	printf("%d \n", total);
	printf("%d", r);
	return 0;
}

만약 main()함수에서 r값의 접근이 필요하면 이런식으로 r을 맨위에 써주시고 사용하면 됩니다.

main 함수 맨위에서 r 을 선언하고 for문에서 접근하면 아직 main 함수가 종료되지 않아 r이 그대로 남아있고

for문 내부에서도 사용할 수 있게됩니다.

 

#include <stdio.h>
int main(){
	int total = 0;
	int r = 10;
	for (int i=1; i<3; i++)
	{
		int r = 5;
		total += r;
	}
	
	printf("%d \n", total);
	printf("%d", r); 
	return 0;
}

이렇게 하면 r은 5가 될까요, 10이 될까요? 한번 실행해서 확인해보세요 ^^

그리고 추가로, for문 내부에 사용된 i도 지역변수란것을 알 수 있습니다~

 

 

 

지역변수의 메모리 생성, 소멸

#include <stdio.h>

int sum(int a, int b){
	return a+b;
}
int main(){
	int a = 10, b = 20;
	printf("%d %d \n", a,b);
	printf("%d %d \n", sum(10,30));
    return 0;
}

이 코드에서 지역변수의 메모리 흐름을 파악해보면..

1. main()함수에서 a, b 를 위한 메모리 공간이 생성됩니다.

2. sum(10,30) 이 호출되어 매개변수 a,b 를 위한 메모리 공간이 생성됩니다.

3. a = 10, b = 30이 대입됩니다.

4.return 으로 a + b 의 값을 반환후 매개변수 a,b의 메모리 공간이 소멸합니다.

5. main() 함수가 종료이후 main()함수의 a , b 를 위한 메모리 공간이 소멸합니다.

 

이렇게 지역변수는 지역(중괄호)에서 메모리를 생성하고, 해당 지역을 빠져나가게 되면(return, 중괄호 탈출) 메모리가 자동으로 소멸되는 특징이 있습니다.

 

지역변수 정리

1. 초기화를 하지 않으면 쓰레기 값 저장

2. 메모리 생성시점 : 중괄호 내부 초기화

3. 메모리 소멸 시점 : 중괄호 탈출

 


전역변수

전역변수(global variable)는 지역변수와 반대로, 중괄호 외부에 선언되는 변수입니다.

전역이라는 이름 그대로 어느 지역에서든 참조해서 사용할 수 있습니다.

 

#include <stdio.h>
int global = 100;
	
int main(){
		printf("%d", global);
		return 0;
	}

사용은 이런식으로 그냥 전처리기 밑에 적어주시면 됩니다.

전역변수와 지역변수의 차이는 메모리 생성/소멸 시점이 다르다는 것입니다.

 

전역변수는 메모리 생성시점이 프로그램의 시작, 소멸시점이 프로그램의 종료입니다.

그렇기에 프로그램이 실행되는 동안에는 어디서든 접근해서 사용할 수 있는겁니다.

 

#include <stdio.h>
int global;
	
int main(){
		printf("%d", global);
		return 0;
	}
	
>>> 0

또한 초깃값을 지정하지 않았다고 해서 지역변수처럼 쓰레기 값이 저장되는것이 아닌 자동으로 0으로 초기화 됩니다.

 

#include <stdio.h>
int number = 100
int global = number; //에러 발생
	
int main(){
		printf("%d", global);
		return 0;
	}
	

전역변수를 초기화 할때는 반드시 상수로 초기화 해야합니다.

이러한 특성을 보면 #5편에서 상수가 떠올려집니다. 전역변수는 수정이 가능한 심볼릭 상수다!

라고 이해하셔도 좋을듯합니다. (물론 상수처럼 초기화가 의무적이진 않습니다)

 

 

#include <stdio.h>
void add();
int global;
	
int main(){
		printf("%d \n", global);
		add();
		printf("%d", global);
		return 0;
	}

void add(){
	global += 100;
}

0
100
--------------------------------
Process exited after 0.02017 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

전역변수의 특징을 보여주는 예제입니다.

어떤 함수에서든 접근해 사용가능합니다.

 

 

전역변수 정리

1. 초기화를 하지 않아도 자동으로 0으로 초기화

2. 메모리 생성시점 : 프로그램 시작

3. 메모리 소멸 시점 : 프로그램 종료


정적 변수(Static)

앞에서 배운 전역변수는 모든 곳에서 접근이 가능하고, 프로그램 종료전엔 메모리가 소멸되지 않는 장점이 있지만 잘못 사용하거나 남용할경우 관리(수지, 유지 보수, 재사용 등등)가 어려워질 수 있습니다.

 

이를 부분적으로 보환한것이 정적변수(Static) 입니다.

 

"동적이다"는 움직이는 성격을 나타내는 단어입니다. 

정적변수는 단어에서도 유추가 가능하듯 동적변수의 반대로 동적변수는 지금까지 C언어에서 다룬

대부분의 변수들입니다.

 

static int n;

선언시엔 변수 자료형 앞에 static 키워드를 붙여서 선언합니다.

$cf$ static : 고정된 , 정지상태의, 잡음

 

정적 변수는 전역 변수처럼 프로그램이 종료되기 전까진 메모리가 소멸되지 않으며, 특별히 초기값을 지정하지 않아도

0으로 자동 초기화 됩니다. 또한 초기화 할때 반드시 상수로 초기화 해야합니다.

 

여기까진 전역변수와 동일합니다만.. 전역 변수와 정적변수의 다른점은 초기화가 딱 한번만 진행됩니다.

이게 왜 그렇고 어떻게 사용되는가? 라는 의문이 들겁니다. 아래 예제를 보면서 사용을 보겠습니다.

 

#include <stdio.h>
void call();
	
int main(){
		call();
		call();
		call();
		
	}

void call(){
	int count = 0;
	printf("%d \n", count);
	count ++;
}

0
0
0

함수를 호출할때마다 그 횟수를 알고싶어서 call ( ) 이라는 함수에 count 라는 지역변수를 만들고

printf 로 출력했습니다. 하지만 지역변수는 중괄호 탈출시에 메모리가 소멸하므로 값을 누적시킬 수 없습니다.

 

정적변수(static)를 사용하면 지역이 사라져도 변수의 값을 그대로 유지시킬 수 있습니다.

 

#include <stdio.h>
void call();
	
int main(){
		call();
		call();
		call();
		
	}

void call(){
	static int count = 0;
	printf("%d \n", count);
	count ++;
}

0
1
2

보이시나요? 기존에 지역변수였다면 call()함수의 내용이 수행된후 호출이 끝나면 count 값이 매번 초기화 됬지만

static 을 붙여 정적변수로 만들어주니 그 지역내에서 전역 변수의 기능을 갖게 되었습니다!

초기화가 한번만 일어나기 때문입니다.

 

지역안이라는 말이 중요한데 중괄호 안에 실행됬으니 엄밀히 따지면 지역변수입니다.

 

#include <stdio.h>
void call();
	
int main(){
		call();
		call();
		call();
		
		printf("%d", count);
		
	}

void call(){
	static int count = 0;
	printf("%d \n", count);
	count ++;
}

이 코드를 보시면 알겠지만 지역내에선 전역변수처럼 사용되지만

main(다른 함수)에서 count의 값을 접근할 수 없습니다. 이것이 전역 변수와 정적변수의 차이입니다.

 

이런 변수를 정적 지역 변수라고 부릅니다.

정리해보자면 다음과 같습니다

 

 

정적 지역 변수

1. 자료형 앞 static 키워드로 정의

2. 초기화를 하지 않아도 자동으로 0으로 초기화

3. 중괄호 내에서 선언되어도 초기화는 한번만 실행.

3. 메모리 생성시점 : 중괄호 내부

4. 메모리 소멸 시점 : 프로그램 종료

 

외부 변수

외부 변수(Extern) 변수는 이름 그대로 외부 파일에 선언된 변수를 참조하여 사용하는 변수입니다.

여기서 외부파일에 선언된 변수는 전역변수입니다. 

//1.c

#include <stdio.h>
int global = 0;

int main(){
	return 0;
}
//2.c

#include <stdio.h>
extern int global; //파일 1.cpp 

int main(){
	printf("%d", global);
	return 0;
}

이런식으로 사용하면 됩니다. extern 자료형 변수이름 입니다.

여기서 변수이름은 가져올 변수의 이름과 동일해야 합니다.

 

extern 을 통해 2.c 에서 1.c 의 전역변수인 global 을 사용할 수 있습니다.

Dev c++ 에서 프로젝트 생성후 1.c 와 2.c 를 한 프로젝트에 묶고 실행하면 됩니다만..

링크 단계에서 오류가 나네요. 잘되시는 분들은 댓글 부탁드립니다.

 

그런데 외부에서 변수를 참조 못하게 하려면 어떻게 할까요?

방금 위에서 봤던걸 떠올려보면.. 정적 지역 변수는 전역변수와 거의 동일했습니다.

답을 우선 얘기하자면 정적 전역 변수를 사용하면 됩니다.

 

어렵게 말했지만 static 키워드를 붙인 변수를 중괄호 밖에 선언해주면 된다는 겁니다.

전역변수의 특징, 중괄호 밖 선언 기억나시죠?

 

 

//1.cpp

#include <stdio.h>
static int global = 0;

int main(){
	return 0;
}

이런식으로요. 이렇게 static 을 붙이게되면 1.cpp 파일이 아닌 외부에서 global을 참조할 수 없게됩니다.

global 같은 변수를 정적 전역변수 라고 부릅니다.

 

정리해보면...

구분 지역변수 전역변수 정적 지역변수 정적 전역 변수
선언 위치 중괄호 내부 중괄호 외부 중괄호 내부 중괄호 외부
메모리 생성 시점 중괄호 내부 프로그램 시작 프로그램 시작 프로그램 시작
메모리 소멸 시점 중괄호 탈출 프로그램 종료 프로그램 종료 프로그램 종료
사용 범위 중괄호 내부 프로그램 전체 중괄호 내부 선언된 소스 파일
초기화 하지 않을시 쓰레기값 저장 자동 0초기화 자동 0초기화 자동 0초기화

이렇게 되겠습니다. 

 

레지스터 변수

레지스터 변수는 CPU 내부의 임시 기억 저장소인 *레지스터에 변수를 할당하여 값을 저장하는 변수입니다.

변수를 CPU 내부에 할당하는 이유는? 빠르기 때문입니다.

레지스터는 여러 처리의 중계자 역할을 하기때문에 당연히 RAM(메모리) 보단 빨라야 합니다.

실제로 레지스터는 메모리 최상위에 위치에 가장 빠른 속도로 접근이 가능합니다.

 

* 레지스터 :  CPU 내부에서 처리할 명령어나 연산의 중간 값 등, 일시적으로 저장하는 임시 기억 장소

 

레지스터가 빠르다면 모두 레지스터에 저장하면 될까? 라는 생각이 들지만 레지스터는 제한이 있어서 그 제한을 넘어가면 자동으로 RAM에 할당 됩니다.

 

레지스터 변수는 메모리의 생성과 소멸이 빈번한 변수에 사용하면 좋습니다.

하지만 C컴파일러 대부분은 코드 최적화 단계에서 자동으로 레지스터 변수를 설정하기 때문에 레지스터 변수, 지역변수의 차이가 거의 발생하지 않습니다.

 

그래도 확인을 위해 코드를 작성해보겠습니다.

 

#include <stdio.h>
#include <time.h>

#define MAX 1000000
int main(){
	register int i;
	clock_t startTime, endTime, result; //clock_t = long형
	
	startTime = clock(); //time.h 에 있는 clock()
	for (i=0; i<=MAX; i++) printf("%d \n", i);
	endTime = clock(); // 측정완료된 시간
	
	result = endTime - startTime; //종료된 시간 - 시작시간 = 경과시간
	printf("레지스터 변수 속도 측정 : %lf \n", result); 
	return 0;
}
#include <stdio.h>
#include <time.h>

#define MAX 1000000
int main(){
	int i;
	clock_t startTime, endTime, result; //clock_t = long형
	
	startTime = clock(); //time.h 에 있는 clock()
	for (i=0; i<=MAX; i++) printf("%d \n", i);
	endTime = clock(); // 측정완료된 시간
	
	result = endTime - startTime; //종료된 시간 - 시작시간 = 경과시간
	printf("일반 변수 속도 측정 : %lf \n", result); 
	return 0;
}

int i레지스터 변수 int i 의 속도를 비교하는 소스 코드입니다.

clock() 함수에 대해선 인터넷에 검색하면 많은 자료가 있으니 참고해주세요.

속도 비교는 직접 해보는걸 권장 드립니다.

 

참고로 제가 MAX=100000 쯤에서 해봤는데 일반 변수가 더 빠르게 나옵니다 -.- 

사실 아까도 말했지만 요즘은 컴파일러가 자동 최적화를 해주기 때문에 크게 중요한건 아닙니다.

그냥 이런게 있다 정도만 이해하고 넘어가시면 되겠습니다.

 


오늘은 여기까지입니다. 오늘은 새로 배운 변수가 꽤 많았는데요, 변수는 프로그래밍에서 빠질수가 없는 개념이니 이번 심화개념을 꼭 숙지하고 넘어가셨으면 좋겠습니다.

다음강의에선 배열을 배워보겠습니다. 감사합니다 ^^

 

 

 

COMMENT WRITE

  1. 비밀댓글입니다

  2. 안녕하세요. 정적변수는 프로그램 시작 시 Data에 할당되는 것이 아닌가요?

    • 안녕하세요? 답변이 너무 늦어져서 확인하실지는 모르겠습니다만.. 실제로 메모리 구성에서 전역변수, static(정적) 변수는 데이터 영역에 할당되는게 맞습니다만
      본 변수 글은 메모리 구성의 측면이 아니라 생선시점, 소멸 시점과 같은 개념적인 측면에서 접근한 글입니다.