71. 기억 부류 지정자(Storage Class Specifier)
1. 키워드
- 기억 부류 지정자(Storage Class Specifier)
- 자동 변수(Automatic Variable)
- 정적 변수(Static Variable)
- 레지스터 변수(Register Variable)
- 형 한정자(Type Qualifier)
2. 기억 부류 지정자 사용하기
- C의 변수는 기억 부류 지정자를 사용하여 다양한 특성을 지정할 수 있다.
- 전역 변수(
extern
)와 정적 변수를 선언할 때 값을 초기화했으면 데이터 섹션(.data
)에 생성되고, 초기화하지 않았으면 BSS 섹션(.bss
)에 생성되며0
이 들어간다. - 이번에는 변수와 함수에 기억 부류 지정자를 사용했을 때의 특징을 알아보자.
3. 자동 변수 사용하기
- 자동 변수는 변수를 선언할 때 앞에
auto
키워드를 붙인다. - 단, 전역 변수에는
auto
키워드를 붙일 수 없다.
#include <stdio.h>
int main()
{
auto int num1 = 10; // 자동 변수 num1 선언. 현재 블록이 끝나면 사라짐
printf("%d\n", num1);
return 0;
}
- 자동 변수는 현재 블록이 끝나면 사라진다.
- 지금까지 함수 안에 선언했던 변수는 모두 자동 변수이며 워낙 많이 사용하는 변수 형태이다 보니
auto
키워드를 생략한다.
num1
은 자동 변수이며 지역 변수인데main
함수 블록(범위)을 벗어나면 자동으로 사라져서 자동 변수이고,main
함수 블록(범위) 안에서만 접근할 수 있어서 지역 변수이다.- 보통 지역 변수는 자동 변수로 선언하기 때문에 자동 변수라는 말은 생략하고 그냥 지역 변수라고 말한다.
- 특히 지역 변수는 전역 변수와 대응하는 말이며 자동 변수는 정적 변수와 대응하는 말이다.
- 자동 변수는 선언할 때 초깃값을 지정하지 않으면 쓰레기 값이 들어간다.
- 전역 변수와는 달리 알아서 초기화되지 않는다.
- 참고로 C++의
auto
키워드는 자료형을 자동으로 추론하여 변수를 선언하는 기능이라 C와는 전혀 다르다.
4. 정적 변수 선언하기
- 정적 변수를 알아보기 전에 먼저 자동 변수로 예제를 작성해 보자.
#include <stdio.h>
void increaseNumber()
{
int num1 = 0; // 변수 선언 및 값 초기화
printf("%d\n", num1); // 변수 num1의 값을 출력
num1++; // 변수의 값을 1씩 증가
}
int main()
{
increaseNumber();
increaseNumber();
increaseNumber();
increaseNumber(); // 변수가 매번 생성되고 사라지므로 0이 출력됨
return 0;
}
// 0
// 0
// 0
// 0
- 함수로
increaseNumber
에 변수num1
을 선언한 뒤0
으로 초기화했다. - 그리고 값을 출력한 뒤
++
연산자로num1
의 값을1
증가시켰다. - 이제
increaseNumber
를 여러 번 호출해 보면0
이 계속 출력된다. num1
은 지역 변수이므로increaseNumber
를 벗어나면 값이 사라지며 다시increaseNumber
를 호출했을 때 이전 결과와는 상관없이 새로운 변수가 생성된다.
- 변수가 사라지지 않게 하려면 정적 변수를 사용해야 한다.
- 정적 변수는 변수를 선언할 때 앞에
static
키워드를 붙인다.
#include <stdio.h>
void increaseNumber()
{
static int num1 = 0; // 정적 변수 선언 및 값 초기화
printf("%d\n", num1); // 정적 변수 num1의 값을 출력
num1++; // 정적 변수 num1의 값을 1 증가시킴
}
int main()
{
increaseNumber();
increaseNumber();
increaseNumber();
increaseNumber(); // 정적 변수가 사라지지 않고 유지되므로 값이 계속 증가함
return 0;
}
// 0
// 1
// 2
// 3
- 여기서는 변수
num1
을 선언할 때static
키워드를 붙여서 정적 변수로 만들었다. - 이제
increaseNumber
를 여러 번 호출해 보면0 1 2 3
과 같이 출력된다. - 정적 변수는 함수를 벗어나더라도 변수가 사라지지 않고 계속 유지되므로
++
연산자가 적용되어 값이 계속 증가하게 된다.
static int num1 = 0;
은 프로그램이 시작될 때 변수를 초기화하며increaseNumber
함수가 호출될 때는 변수를 초기화하지 않고 무시한다.
void increaseNumber()
{ // ↓ 0
static int num1 = 0; // 프로그램이 시작될 때 정적 변수가 생성되고 초기화됨
num1++; // 0에서 1로 증가
}
void increaseNumber()
{ // ↓ 1
static int num1 = 0; // 다시 함수가 호출될 때는 값 초기화 무시
num1++; // 1에서 2로 증가
}
void increaseNumber()
{ // ↓ 2
static int num1 = 0; // 다시 함수가 호출될 때는 값 초기화 무시
num1++; // 2에서 3으로 증가
}
void increaseNumber()
{ // ↓ 3
static int num1 = 0; // 다시 함수가 호출될 때는 값 초기화 무시
num1++; // 3에서 4로 증가
}
- 여기서
num1
은 정적 변수이면서 지역 변수인데increaseNumber
함수 블록(범위)을 벗어나도 사라지지 않아서 정적 변수이고,increaseNumber
함수 블록(범위) 안에서만 접근할 수 있어서 지역 변수이다.
- 전역 변수는 정적 변수가 아니더라도 값이 계속 유지되지만 외부에서 사용할 수 있느냐 없느냐의 차이가 있다.
print.c
소스 파일에 다음 내용을 작성한다.
#include <stdio.h>
static int num1 = 10;
void printNumber()
{
printf("%d\n", num1); // 정적 전역 변수 num1의 값 출력
}
- 이제
main
함수가 있는main.c
소스 파일을 작성한다.
#include <stdio.h>
extern int num1; // 다른 소스 파일(외부)에 있는 정적 전역 변수는 extern으로 사용할 수 없음
// 컴파일 에러
int main()
{
printf("%d\n", num1); // 정적 전역 변수 num1의 값 출력
return 0;
}
static
으로 선언한 변수를extern
으로 사용하려고 하면 컴파일(링크) 에러가 발생한다.- 정적 전역 변수는 자신이 선언된 소스 파일 안에서만 사용할 수 있고, 외부에서는 가져다 쓸 수 없다.
- 즉, 전역 변수에
static
을 붙이면 변수의 범위를 파일 범위로 제한하는 효과를 낸다.
extern int num1;
대신extern static int num1;
과 같이 만들더라도 컴파일 에러가 발생한다.
- 이번에는 정적 변수의 초깃값에 대해 알아보자.
#include <stdio.h>
static int num1; // 정적 변수는 초깃값을 지정하지 않으면 0이 들어감(전역 변수)
int main()
{
static int num2; // 정적 변수는 초깃값을 지정하지 않으면 0이 들어감(지역 변수)
printf("%d\n", num1);
printf("%d\n", num2);
return 0;
}
// 0
// 0
- 정적 변수는 초깃값을 지정하지 않으면
0
으로 초기화된다. - 특히 정적 변수를 전역 변수로 선언했든 지역 변수로 선언했든 상관없이 초깃값을 지정하지 않으면
0
으로 초기화된다. - 그리고 정적 변수(전역, 지역)는 프로그램이 시작될 때 생성 및 초기화되고 프로그램이 끝날 때 사라진다.
정적 변수와 매개변수
- 정적 변수는 함수의 매개변수로 사용할 수 없다.
- 매개변수에
static
을 붙이더라도 매개변수는 정적 변수가 되지 않으며 값이 유지되지 않는다.
5. 정적 함수 사용하기
- 이번에는 정적 함수를 알아보자.
print.c
소스 파일에 다음 내용을 작성한다.
- 이제
main
함수가 있는main.c
소스 파일을 작성한다.
#include <stdio.h>
void print() // main.c에서 print 함수 정의
{
printf("main.c\n");
}
int main()
{
print();
return 0;
}
- 소스 파일
print.c
와main.c
에 같은 이름으로 된 함수print
를 정의했다. - 이 상태에서 컴파일을 해보면 컴파일(링크) 에러가 발생한다.
- 즉, 한 프로젝트 안에서는 함수 이름이 중복될 수 없다.
main.c
소스 파일의 내용을 다음과 같이 수정해 보자.
#include <stdio.h>
static void print() // main.c에서 print을 정적 함수로 정의
{
printf("main.c\n");
}
int main()
{
print();
return 0;
}
// main.c
- 함수
print
를 선언하고 정의할 때 앞에static
키워드를 붙여서 정적 함수로 만들었다. - 이제 컴파일 에러가 발생하지 않고 실행도 잘 된다.
- 여기서
static
을 붙인 정적 함수print
는main.c
소스 파일 안에서만 사용할 수 있다. - 만약
print.c
소스 파일의print
를 정적 함수로 만들었다면print.c
소스 파일 안에서만 사용할 수 있다.
- 이처럼 정적 함수를 사용하면 같은 이름을 가진 함수를 파일마다 만들 수 있다.
- 따라서 정적 함수는 기능이 여러 파일로 분리되어 있을 때 각 파일 안에서만 사용하는 기능을 구현할 수 있다.
6. 레지스터 변수 사용하기
- 변수를 선언할 때 앞에
register
를 붙이면 변수는 메모리 대신 CPU의 레지스터를 사용한다. - 따라서 일반 변수보다 속도가 빠르다.
- 단, 레지스터는 개수가 한정되어 있으므로
register
를 붙인다고 해서 모두 레지스터를 사용하지는 않는다.
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
int main()
{
register int num1 = 10; // 변수 num1은 CPU의 레지스터를 사용
printf("%d\n", num1);
// printf("%p\n", &num1); // 컴파일 에러. num1은 메모리에 없으므로 메모리 주소를 구할 수 없음
// error C2103: 레지스터 변수에 '&'이(가) 있습니다.
register int *numPtr = malloc(sizeof(int));
// 레지스터 변수에 메모리 주소는 저장할 수 있으므로 역참조 연산자를 사용할 수 있음
*numPtr = 20;
printf("%d\n", *numPtr);
free(numPtr);
return 0;
}
// 10
// 20
- 레지스터 변수는 변수가 CPU 레지스터만 사용하므로 메모리에는 생성되지 않는다.
- 따라서
&
(주소 연산자)로 메모리 주소를 구할 수 없다. - 단, 레지스터 변수에 메모리 주소는 저장할 수 있으므로
*
(역참조 연산자)를 사용할 수 있다.
- 레지스터 변수는 반복 횟수가 매우 많을 때 유용하다.
register
는 지역 변수에만 사용할 수 있고 전역 변수에는 사용할 수 없다.
형 한정자
- C에는 기억 부류 지정자 이외에 형 한정자라는 것도 있다.