C & C++/디딤돌 C언어

Part 18. 다양한 종류의 변수 (전역, 지역, 정적, 상수화)

언휴 2024. 1. 15. 15:36

Part 18. 다양한 종류의 변수 (전역, 지역, 정적, 상수화)

57. 전역 변수

다양한 종류의 변수, 전역 변수, 지역 변수, 정적 변수, 상수화 변수 - C언어

아시는 것처럼 C언어에서는 프로그램에서 관리할 데이터를 할당하고 관리하기 위해 변수에 관한 문법을 제공하고 있죠.

C언어에서는 프로그램의 모든 함수에서 접근할 수 있는 전역 변수와 선언한 블록에서만 접근할 수 있는 지역 변수가 있어요.
그리고 이 외에도 정적 변수와 상수화 변수를 제공하는데 이들에 관해 하나 하나 살펴봅시다.

전역 변수

C언어에서는 변수 선언을 특정 블록 내부가 아닌 외부에 선언한 변수를 전역 변수라 불러요.
전역 변수는 프로그램 시작할 때 할당하고 프로그램 끝날 때 해제하며 프로그램 전제 영역에서 접근할 수 있죠.

예를 들어 50명의 국어 성적을 관리하는 프로그램이 있다고 가정할게요.
그리고 프로그램에 성적을 추가, 삭제, 조회, 전체 보기 메뉴를 제공합시다.
아마도 이 프로그램에는 각 메뉴를 선택했을 때 수행하는 부분을 각각의 함수로 만드는 것이 효과적일 거예요.

이 때 10명의 국어 성적을 기억하는 변수를 어디에 선언하면 좋을까요?
여러가지 방법이 있겠지만 전역 변수로 선언하면 모든 함수에서 접근할 수 있어요.

#define MAX_STUDENT    10
int scores[MAX_STUDENT];

추가 기능에서는 학생 번호를 입력하여 성적을 추가할 학생을 선택하겠죠.

int num = 0;
printf("성적을 추가할 학생 번호를 입력:");
scanf_s("%d",&num);

그리고 학생 성적을 입력받아요.

int score = 0;
printf("성적:");
scanf_s("%d",&score);

학생 성적은 scores 배열에 기억할 것이므로 인덱스 연산을 사용할게요.
입력한 학생 번호에 1을 빼면 되겠네요.
그리고 인덱스 연산으로 참조한 원소에 성적을 대입하여 값을 설정해요.

scores[num-1] = score;

이렇게 추가 기능을 작성하면 전체 보기 기능을 선택하더라도 그 값은 유지되어 원하는 결과를 얻을 수 있어요.

다음은 10 명의 국어 성적을 관리하는 프로그램이예요.
프로그래밍 작성에 관해서는 뒤에서 다시 다루지만 한 번 분석해 보시고 프로그램을 작성해 보세요.

◈ 10 명의 국어 성적을 관리하는 프로그램

#include <stdio.h>
#pragma warning(disable:4996)
#define MAX_STUDENT        10
int scores[MAX_STUDENT];
 
void Init();//초기화
void Run(); //사용자와 상호 작용
char SelectMenu(); //메뉴 선택
void InputScore(); //성적 추가
void DeleteScore();//성적 삭제
void SearchScore();//성적 조회
void ListScore();  //전체 성적 보기
void ViewScore(int num);//특정 학생 성적 보기
 
int main()
{
    Init();
    Run();
    return 0;
}
 
void Init()
{
    int i = 0;
    for (i = 0; i<MAX_STUDENT; i++)
    {
        scores[i] = -1;
    }
}
 
void Run()
{
    char key = 0;
    while ((key = SelectMenu()) != 'e')
    {
        switch (key)
        {
        case 'i': InputScore(); break;
        case 'd': DeleteScore(); break;
        case 's': SearchScore(); break;
        case 'l': ListScore(); break;
        default: printf("잘못 선택하였습니다.\n"); break;
        }
    }
}
 
 
char SelectMenu()
{
    char key[256] = "";
 
    printf("[i]:성적 추가 [d]:성적 삭제 [s]:성적 조회 [l]: 전체 성적 보기\n");
    scanf("%s", key);
 
    return key[0];
}
 
void InputScore()
{
    int num = 0;
 
    printf("성적을 추가할 학생 번호를 입력:");
    scanf("%d", &num);    
 
    if ((num >= 1) && (num <= MAX_STUDENT))
    {
        int score = 0;
        printf("성적:");
        scanf("%d", &score);        
        scores[num - 1] = score;
    }
    else
    {
        printf("유효하지 않은 번호입니다.\n");
    }
}
 
void DeleteScore()
{
    int num = 0;
 
    printf("성적을 삭제할 학생 번호를 입력:");
    scanf("%d", &num);    
 
    if ((num >= 1) && (num <= MAX_STUDENT))
    {
        scores[num - 1] = -1;
    }
    else
    {
        printf("유효하지 않은 번호입니다.\n");
    }
}
 
void SearchScore()
{
    int num = 0;
    printf("성적을 조회할 학생 번호를 입력:");
    scanf("%d", &num);    
 
    if ((num >= 1) && (num <= MAX_STUDENT))
    {
        ViewScore(num);
    }
    else
    {
        printf("유효하지 않은 번호입니다.\n");
    }
}
 
void ListScore()
{
    int index = 0;
 
    for (index = 0; index < MAX_STUDENT; index++)
    {
        ViewScore(index + 1);
    }
}
 
 
void ViewScore(int num)
{
    if (scores[num - 1] != -1)
    {
        printf("[%d 번]:[성적: %d]\n", num, scores[num - 1]);
    }
    else
    {
        printf("[%d 번]:[성적: 입력하지 않음]\n", num);
    }
}

58. 지역변수

지역 변수는 특정 블록에 선언한 변수예요.
지역 변수는 특정 함수를 구현하는데 임시적으로 값을 기억할 필요가 있을 때 사용하죠.
그리고 지역 변수를 위한 메모리는 변수를 선언한 함수를 호출하면 할당하고 함수가 끝날 때 해제한답니다.

여러분이 주의할 점은 지역 변수는 선언한 블록에서만 보인다는 것이예요.

main 함수에 int 형식 변수 i를 선언하고 Foo 함수에서 변수 i를 사용하려고 하면 가시성이 없어서 컴파일 오류가 발생해요.

변수의 가시성으로 인한 오류
변수의 가시성으로 인한 오류

그리고 전역에 선언한 변수 이름과 같은 이름의 지역 변수를 선언하여 사용하면 지역 변수를 사용한답니다.
그리고 함수 내에서도 { }으로 블록을 지정할 수 있는데 블록에 변수를 선언하면 블록 내부에서만 사용할 수 있어요.

◈ 같은 이름의 변수를 선언하였을 때

#include <stdio.h>
int i=2;
 
int main()
{
    int i = 3;
 
    printf("TEST 1: %d\n",i);
    {
        int i = 4;
        printf("TEST 2: %d\n",i);
    }
    printf("TEST 3: %d\n",i);
    return 0;
}

◈ 실행 결과

TEST 1: 3
TEST 2: 4
TEST 3: 3

그리고 C언어에서 인자를 전달하여 구조적으로 프로그래밍 할 수 있어요.
◈ 지역 변수의 값을 전달하여 구조적으로 작성한 예

#include <stdio.h>
void View(int value);
int main()
{
    int arr[3] = {1,2,3};
    int i = 0;
 
    for(i=0;i<3; i++)
    {
        View(arr[i]);
    }
    return 0;
}
void View(int value)
{
    printf("%d\n",value);
}

◈ 실행 결과

1
2
3

59. 정적 변수

프로그래밍하다보면 전체 영역에서 사용하지 않지만 값을 유지해야 할 때가 있죠.
이럴 때는 전역 변수로 선언하면 사용하지 말아야 하는 곳에서 사용하는 실수를 범하여 버그를 만드는 원인이 되기도 한답니다.
그렇다고 지역 변수로 선언하면 함수 호출했을 때 메모리를 할당하고 끝나면 해제해서 다시 호출했을 때 이전의 값이 사라져요.

이 때 정적 변수를 선언하여 사용하면 문제를 해결할 수 있어요.
정적 변수를 선언할 때는 static 키워드를 붙여서 선언해요.
정적 변수는 특정 블록 내부에 변수를 선언해도 프로그램 시작할 때 메모리를 할당하고 해제해서 값을 유지할 수 있어요.

◈ 정적 변수와 지역 변수를 비교하는 예

#include <stdio.h>
void Foo();
int main()
{
    Foo();
    Foo();
    return 0;
}
void Foo()
{
    int i = 0;
    static int si = 0;
    i++;
    si++;
    printf("i: %d si: %d\n", i, si);
}

◈ 실행 결과

i:1 si:1
i:1 si:2

그런데 전역에 정적 변수를 선언하는 것은 어떠한 의미일까요?
전역에 정적 변수를 선언하면 같은 프로그램의 다른 소스 파일에서는 접근할 수가 없어요.
만약 같은 이름으로 다른 소스 파일에서 전역에 변수를 선언하면 두 변수는 이름은 같아도 메모리는 독립적이예요.
따라서 서로 다른 데이터를 관리할 수 있어요.

◈ 블록 외부에 정적 변수를 선언한 예

//Demo.c
#include <stdio.h>
static int si;
 
void Stub()
{
    si++;
    printf("Stub: %d\n",si);
}

//Program.c
#include <stdio.h>
static int si;
void Stub();
int main(void)
{
    si=3;
    printf("main: %d\n",si);
    Stub();
    printf("main: %d\n",si);
    si=6;
    printf("main: %d\n",si);
    Stub();
    printf("main: %d\n",si);
    return 0;
}

◈ 실행 결과

main: 3
Stub: 1
main: 3
main: 6
Stub: 2
main: 6

자세한 설명은 하지 않을게요.
위 코드를 작성해서 실행해 보고 혼자 고민해 보세요.
물론 이러한 문법을 아는 것보다는 어떨 때 이러한 변수를 선언해서 사용하는 것이 좋을 지 판단하는 능력이 더 중요해요.
여기에서는 이에 관한 설명은 생략할게요.
힌트를 주자면 srand 함수와 rand 함수 사용법을 살펴보시고 이들의 동작 원리를 고민해 보세요.
자주 사용하는 문법 사항은 아니지만 라이브러리를 만들 때 종종 사용하는 문법이예요.

60. 상수화 변수

C언어에서 변수 선언문 앞에 const 키워드를 명시한 변수를 상수화 변수라 불러요.
상수화 변수는 값을 변경하지 못하여 흔히 상수라고도 부르죠.

상수화 변수는 선언과 동시에 초기화가 필요해요.
값을 변경하지 못하는 상수화 변수의 초기값을 설정하지 않는다면 아무런 의미가 없겠죠.

상수화 변수는 l-value로 사용할 수 없어서 발생한 오류
상수화 변수는 l-value로 사용할 수 없어서 발생한 오류

const 키워드를 포인트 변수 선언문에 명시하면 위치에 따라 상수화 의미가 조금씩 달라집니다.

const 키워드가 *과 변수명 사이에 오면 포인터 변수에 초기화한 메모리 주소를 다른 주소로 변경할 수 없다는 의미예요.

#include <stdio.h>
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int *const pi = arr;
    pi = arr+1; //컴파일 오류
    pi[0] = 9; //가능    
    return 0;
}

const 키워드가 * 앞쪽에 오면 포인터 변수가 가리키는 곳의 내용을 변경할 수 없다는 의미죠.
실제 이와 같은 형태로 가리키는 곳을 변경하지 않겠다는 의미로 입력 매개 변수를 결정하는 함수는 흔히 볼 수 있어요.
그 중에 하나가 strcmp 함수예요.

int strcmp ( const char * str1, const char * str2 ); 문자열을  비교하는 함수

#include <stdio.h>
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    const int *pi = arr; //int cont *pi=arr; 과 같은 표현
    pi = arr+1; //가능
    pi[0] = 9; //컴파일 오류  
    return 0;
}