Skip to content

71. 기억 부류 지정자(Storage Class Specifier)


1. 키워드

  • 기억 부류 지정자(Storage Class Specifier)
  • 자동 변수(Automatic Variable)
  • 정적 변수(Static Variable)
  • 레지스터 변수(Register Variable)
  • 형 한정자(Type Qualifier)


2. 기억 부류 지정자 사용하기

  • C의 변수는 기억 부류 지정자를 사용하여 다양한 특성을 지정할 수 있다.


001


  • 전역 변수(extern)와 정적 변수를 선언할 때 값을 초기화했으면 데이터 섹션(.data)에 생성되고, 초기화하지 않았으면 BSS 섹션(.bss)에 생성되며 0이 들어간다.
  • 이번에는 변수와 함수에 기억 부류 지정자를 사용했을 때의 특징을 알아보자.


3. 자동 변수 사용하기

  • 자동 변수는 변수를 선언할 때 앞에 auto 키워드를 붙인다.
  • 단, 전역 변수에는 auto 키워드를 붙일 수 없다.


auto 자료형 변수이름;
#include <stdio.h>

int main()
{
    auto int num1 = 10; //  자동 변수 num1 선언. 현재 블록이 끝나면 사라짐

    printf("%d\n", num1);

    return 0;
}


  • 자동 변수는 현재 블록이 끝나면 사라진다.
  • 지금까지 함수 안에 선언했던 변수는 모두 자동 변수이며 워낙 많이 사용하는 변수 형태이다 보니 auto 키워드를 생략한다.


int main()
{
    auto int num1 = 10; //  자동 변수 num1 선언. 현재 블록이 끝나면 사라짐


  • 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 키워드를 붙인다.


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을 붙이면 변수의 범위를 파일 범위로 제한하는 효과를 낸다.


002


  • 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을 붙이더라도 매개변수는 정적 변수가 되지 않으며 값이 유지되지 않는다.


void increaseNumber(static int num1) // 정적 변수는 매개변수로 사용할 수 없음. 잘못된 문법
{                                    // warning C4042: 'num1': 저장소 클래스가 잘못되었습니다.
    printf("%d\n", num1);
    num1++;
}


5. 정적 함수 사용하기

  • 이번에는 정적 함수를 알아보자.
  • print.c 소스 파일에 다음 내용을 작성한다.


#include <stdio.h>

void print() // print.c에서 print 함수 선언 및 정의
{
    printf("print.c\n");
}


  • 이제 main 함수가 있는 main.c 소스 파일을 작성한다.


#include <stdio.h>

void print() // main.c에서 print 함수 정의
{
    printf("main.c\n");
}

int main()
{
    print();

    return 0;
}


  • 소스 파일 print.cmain.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을 붙인 정적 함수 printmain.c 소스 파일 안에서만 사용할 수 있다.
  • 만약 print.c 소스 파일의 print를 정적 함수로 만들었다면 print.c 소스 파일 안에서만 사용할 수 있다.


003


  • 이처럼 정적 함수를 사용하면 같은 이름을 가진 함수를 파일마다 만들 수 있다.
  • 따라서 정적 함수는 기능이 여러 파일로 분리되어 있을 때 각 파일 안에서만 사용하는 기능을 구현할 수 있다.


6. 레지스터 변수 사용하기

  • 변수를 선언할 때 앞에 register를 붙이면 변수는 메모리 대신 CPU의 레지스터를 사용한다.
  • 따라서 일반 변수보다 속도가 빠르다.
  • 단, 레지스터는 개수가 한정되어 있으므로 register를 붙인다고 해서 모두 레지스터를 사용하지는 않는다.


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 int i, j;
for (i = 0; i < 1000000; i++)
{
    for (j = 0; j < 10000; j++)
    {
    }
}


  • register는 지역 변수에만 사용할 수 있고 전역 변수에는 사용할 수 없다.


형 한정자

  • C에는 기억 부류 지정자 이외에 형 한정자라는 것도 있다.


004


References