본문 바로가기

변수

1. 변수

 

변수에 대한 이야기는 다 한 것 같지만, 사실 아직 하지 않은 이야기가 있다.

바로 변수는 선언된 위치에 따라 전역변수(global variable)지역변수(local variable)로 

나뉜다는 것.

그리고 이 둘은 다음의 두 가지에 대해 차이점을 보인다.

 

① 메모리 상에 존재하는 기간

② 변수에 접근할 수 있는 범위

 

전역변수는 프로그램이 실행되고 종료될 때까지 메모리 상에 존재한다.

또한 이름에서 알 수 있듯이, 어디서든 접근할 수 있다.

여기서 접근이란 변수에 담긴 값을 사용하거나 변경하는 것을 말한다.

 

지역변수는 특정 지역에서만 접근할 수 있고, 그 지역에 있을 때만 메모리 상에 존재한다.

 

 

 

 

2. 지역변수

 

사실 지역변수는 이미 많이 선언하고 사용해본 변수다.

지역이란 중괄호로 둘러싸인 영역을 말한다.

그러니까 지역변수란 중괄호로 둘러싸인 영역에 선언된 변수를 말한다.

 

지역변수는 선언된 지역에서만 유효하다. 또한 특정 지역에서만 접근할 수 있다.

예를 들어 다음과 같이 함수 안에 변수가 선언되었다고 해보자.

 

1
2
3
4
5
6
7
8
int add(int a, int b)
{
    int result;
    
    result = a + b;
 
    return result;
}
cs

 

변수  result 는  add 라는 함수의 중괄호 안에 선언되었으므로 지역변수다. 

따라서 변수  result 는  add  함수가 실행 중일 때만 유효하다.

그러니까  add  함수를 호출했을 때부터  return result; 가 실행될 때까지만 

유효하다는 것이다.

따라서 지역이 다르다면 이름이 같은 변수도 선언할 수 있다.

선언된 지역 내에서만 유효하기 때문에 지역만 다르면 이름이 같아도 문제 될 게 없는 것이다.

 

정리하면 다음과 같다.

 

지역변수는 선언된 지역에 진입할 때 메모리를 할당받고, 

선언된 지역을 벗어날 때 메모리 공간에서 사라진다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
 
int add(int a, int b);
int sub(int a, int b);
 
int main()
{
    int num1; 
    int num2;
 
    num1 = add(35);
    num2 = sub(93);
 
    printf("3 + 5 = %d\n", num1);
    printf("9 - 3 = %d\n", num2);
 
    return 0;
}
 
int add(int a, int b)
{
    int result;
    
    result = a + b;
 
    return result;
}
 
int sub(int a, int b)
{
    int result;
 
    result = a - b;
 
    return result;
}
cs

 

위 코드의 실행 흐름을 따라 지역변수의 할당과 소멸 과정을 보자.

편의상  printf  함수는 생략.

 

 

① main() 호출

 

변수 num1과 num2가 선언된다.

따라서 메모리 공간의 상태는 다음과 같다.

 

%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22160%22%20y%3D%2280%22%20width%3D%22400%22%20height%3D%22200%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E

 

 

② add 함수 호출 및 종료

 

변수  a b 와  result 가 선언된다.

따라서 메모리 공간의 상태는 다음과 같다.

 

 

 

그리고  result = 3 + 5; 가 실행된 후의 상태는 다음과 같다.

 

 

 

 

여기서 주의할 것은 2가지다.

 

먼저, 매개변수도 일종의 지역변수라는 것.

그러니  add  함수와  sub  함수의 매개변수 이름이 같을 수 있는 것이다.

 

또 한 가지는  add  함수를 호출했다고 해서  main  함수의 실행이 끝난 게 아니라는 것.

 main  함수 실행 중에  add  함수를 실행할 뿐이다. 따라서  add  함수 실행이 끝나면 

다시  main  함수로 돌아간다.

 

 add  함수 실행이 끝나면  num1 으로  add  함수의  result  값을 반환한 후 

 add  함수 내에 선언된 지역변수 할당이 모두 해제되므로 메모리의 상태는 다음과 같다.

 

 

 

 

 

③ sub 함수 호출 및 종료

 

마찬가지로 변수  a  b 와  result 가 선언된다.

따라서 메모리 공간의 상태는 다음과 같다.

 

 

그리고  result = 9 - 3; 이 실행된 후의 상태는 다음과 같다.

 

 

 sub  함수 실행이 끝나면  num2 로  sub  함수의  result  값을 반환한 후 

 sub  함수 내에 선언된 지역변수 할당이 모두 해제되므로 메모리의 상태는 다음과 같다.

 

 

 

④ main 함수 종료

 

 main  함수에서  return 0; 이 실행되면 운영체제로  0 을 반환한 후

 main  함수 내에 선언된 지역변수 할당이 모두 해제된다.

 

 

따라서 메모리 공간은 위와 같이 비게 된다.

 

 

앞에서 지역변수는 중괄호로 둘러싸인 영역에 선언된 변수라고 했다.

따라서 반복문이나 조건문 내에도 선언될 수 있다.

어쨌든 중괄호로 둘러싸여 있지 않은가?

 

다음을 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    int i;
 
    for (i = 1; i <= 3++i)
    {
        int num = 1;
 
        printf("%d. num = %d\n", i, num);
        ++num;
    }
 
    return 0
}
cs

 

우선 실행결과를 예상해보자.

 i 는 보나마나  1 2 3 일 거고,  num 은 어떨까?

 i 처럼  1  2  3 일까, 아니면 계속  1 일까?

 

 

정답은 후자다. 어찌 보면 당연하지만, 또 어찌 보면 이상하다. 왜 계속  1 이 출력되는 걸까?

이것은 반복이 괄호 내부에서 이루어지는 것이 아니라, 

중괄호 내부와 외부를 왔다갔다 하며 이루어짐을 뜻한다.

그러니까 내부 코드를 실행할 때는 중괄호 내부에 있지만,

조건을 검사하고 증감식을 실행할 때는 중괄호 외부에 있다는 것이다.

 

 

이번엔 조건문 내에 선언된 지역변수다.

다음 코드에서 출력되는 것은 1일까, 9일까?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int num = 1;
 
    if (num == 1)
    {
        int num = 8;
 
        printf("num: %d\n", num);
    }
 
    return 0
}
cs

 

 

출력은 if 문 내에서 이루어진다.

따라서 출력되는 것도 if 문 내의  num 이 출력되는 것이다.

 

여기서 알 수 있는 사실은 다음과 같다.

 

현재 지역의 변수가 우선이다.

 

이런 현상을 가려진다고 표현한다.

그러니까 위 예제에서는  main  함수의  num 이 if 문 내의  num  의해 가려졌다고 할 수 있다.

 

 

 

 

3. 전역변수

 

전역변수는 다음과 같이 중괄호 밖에 선언된다.

이러면 중괄호로 둘러싸인 영역, 즉 지역에 속하지 않으니 어디서든 접근할 수 있게 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int num;
 
void add(int value);
 
int main()
{
    printf("num: %d\n", num);
 
    add(3);
    printf("num: %d\n", num);
 
    return 0
}
 
void add(int value)
{
    num += value; 
}
cs

 

 

여기서 알 수 있는 전역변수의 특성은 다음과 같다.

 

① 프로그램의 시작과 동시에 메모리를 할당받는다.

② 별도로 초기화하지 않으면 0으로 초기화된다.

③ 프로그램의 어디서든 접근할 수 있다.

 

선언만 하고 초기화는 하지 않은 상태에서 출력했을 때 0이 출력되었다.

또한 사용자 정의 함수(사용자가 직접 정의한 함수)에서도 접근할 수 있다.

 

그런데 지역변수와 전역변수의 이름이 같으면 어떻게 될까?

 

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int num = 1;
 
int main()
{
    int num = 3;
 
    printf("num: %d\n", num);
 
    return 0
}
cs

 

 

앞에서 우리는 다음과 같은 사실을 알아냈다.

 

현재 지역의 변수가 우선이다.

 

이것은 전역변수에도 적용된다.

따라서 전역변수  num 은 지역변수  num 에 의해 가려진다.

 

하지만 이런 상황을 만들지 않는 것이 좋다. 

가급적이면 전역변수든 지역변수든 변수 간 이름은 다르게 하는 것이 좋다.

 

앞서 말했지만 전역변수는 프로그램 시작부터 끝까지 값도 유지되고 알아서 초기화도 된다.

이렇게 보면 꽤 쓸 만 한 것 같지만, 전역변수는 신중히 선언해야 한다.

 

예를 들어 20개의 함수 f1, f2, , f20를 정의했다고 하자.

그리고 f1과 f2가 공유해야하는 값이 있어서 전역변수 a를 선언했다고 하자.

그 다음에는 f3와 f4가 공유해야하는 값이 있어서 전역변수 b를 선언하고,

또 그 다음에는, 이런 식으로 전역변수 10개가 만들어졌다고 하자.

 

이런 상황에서 int형으로 선언된 전역변수 g를 double로 변경해야 한다면 어떻게 될까?

g를 공유하는 모든 함수들을 수정해야한다. 

게다가 이 함수에서 또 다른 함수를 호출하는 구조라면, 20개의 함수를 모두 살펴봐야 하는 경우도 생긴다.

결론적으로 프로그램이 복잡해져, 작은 수정 사항이 생기면 코드 전체를 갈아 엎어야하는 상황이 생길 수도 있다는 것이다.

 

이런 코드를 스파게티 면처럼 코드가 얽히고 설켰다는 의미에서 스파게티 코드라고 한다. 

 

 

 

 

4. 정적변수

 

정적변수는 지역변수에  static 이라는 선언을 추가해 만든다.

이렇게 선언된 변수는 전역변수의 성격을 갖는 지역변수가 된다.

 

정적변수의 특성은 다음과 같다.

 

① 선언된 지역에서만 접근할 수 있다. 

② 0으로 초기화된다.

③ 프로그램이 시작할 때 할당되어 종료까지 메모리 공간에 존재한다.

 

왜 전역변수의 성격을 갖는 지역변수라고 했는지 알겠는가?

①은 지역변수의 특성이고 ②,③은 전역변수의 특성이다.

그러니까 특성으로 따지면 지역을 탈출해도 유지되는 지역변수 또는

선언된 지역에서만 접근할 수 있는 전역변수라고 할 수 있다.

 

주의할 점은 함수에 선언된 게 아니라면 무의미하다는 것이다.

예를 들어 if 문 안에 정적변수가 선언되었다면, 그 지역으로 다시 이동할 수 없으니

변수가 유지되어봤자 메모리만 차지할 뿐 의미가 없다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
void func();
 
int main()
{
    int i;
 
    for (i = 0; i < 5++i)
        func();
    
    return 0
}
 
void func()
{
    static int num1 = 0;
    int num2 = 0;
 
    ++num1, ++num2; //++num1; ++num2; 도 가능하다.
 
    printf("정적변수: %d, 지역변수: %d\n", num1, num2);
}
 
cs

 

 

실행결과에 주목하자.

 

함수는 호출할 때 함수 지역에 진입하고 반환할 때 함수 지역을 탈출한다.

따라서  num2 는 함수를 호출할 때 메모리 공간에 할당되고, 

함수 실행이 끝날 때 할당이 해제된다.

 

반면  num1 은 함수를 호출할 때가 아니라 프로그램이 시작할 때 메모리 공간에 할당된다.

그리고 함수 실행이 끝나도 할당이 유지된다. 

 

정적변수는 전역변수의 단점을 어느정도 해결했다.

전역변수의 단점은 어디서든 접근할 수 있기 때문에, 여러 곳에서 접근할 경우 

코드가 엉켜버릴 수 있다는 것이었다.

정적변수는 선언된 지역에서만 접근 가능하니, 전역변수에 비해 안정적이다.

그러니 정적변수를 전역변수로 대체하는 일은 없어야 한다.

오히려 가능하면 전역변수를 정적변수로 대체해 프로그램의 안정성을 높여야 한다.

 

 

 

 

5. register 변수

 

지역변수에는 다음과 같이  register 라는 선언을 추가할 수 있다.

 

register int num = 1;

 

이는 변수가 메모리가 아닌 ⒜레지스터에 할당될 확률을 높인다.

여기서 할당되는 게 아니라 할당될 확률을 높인다고 했는데, 이는 register 선언을 추가해도 

레지스터가 아닌 메모리에 할당될 수 있다는 뜻이다.

이는 최종적으로 레지스터에 할당할지, 메모리에 할당할지는 컴파일러가 결정하기 때문이다.

때문에 반대로 register 선언을 추가하지 않아도 레지스터에 할당될 수도 있다.

 

register 변수를 선언하는 이유는 간단하다. 메모리에서 값을 읽어내 연산하는 것보다 

레지스터에 저장된 데이터를 대상으로 하는 연산이 더 빠르기 때문이다.

결국 연산은 CPU의 ⒝ALU에서 이루어지기 때문에, 레지스터에서 값을 읽어 연산하는 것이 

더 빠를 수밖에 없다.

 

하지만 전역변수에는 register 선언을 붙일 수 없다.

프로그램의 시작부터 끝까지 변수 하나가 들어서 있기에는 중요한 메모리 공간이기 때문이다.

(자세한 내용은 https://t0pli.tistory.com/8?category=1039330 참조)

때문에 선언을 추가할 수 없거나, 추가할 수 있더라도 무의미하다.

 

⒜ 레지스터(register): CPU 내에 존재하는 크기가 작은 메모리.

                             

⒝ ALU(Arithmetic Logical Unit): 산술 논리 장치. CPU의 구성 요소로, 산술 및 논리 연산을 처리한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Programming > C' 카테고리의 다른 글

포인터  (0) 2019.03.16
1차원 배열  (0) 2019.03.15
함수  (0) 2019.03.06
조건 분기  (0) 2019.03.04
반복문  (0) 2019.03.03