[C] 이게 변수 범위도 모르는게 까불어!

장세민·2022년 10월 8일
1

📝 TIL

목록 보기
22/40
post-thumbnail

프로그램에서 사용하는 변수는 선언 위치와 방법에 따라 다양한 특징을 가진다.

기철이처럼 일정 선(블록)을 넘으면 변수를 사용 못 하게 될 수도 있고,
사용 범위에 상관 없이 사용할 수 있는 변수도 있다.

변수에 대해 한 번 공부해보자.



📌 변수 사용 영역

Before Starting

변수의 유효 범위

C언어에서는 변수의 선언 위치에 따라
해당 변수의 유효 범위, 초기화 여부, 메모리 반환 시기, 저장되는 장소 등이 변경된다.

위와 같은 특징에 따라 변수를 다음과 같이 나눌 수 있다.

  • 지역 변수(local variable)
    전역 변수(global variable)
    정적 변수(static variable)
    레지스터 변수(register variable)

메모리의 구조

컴퓨터 운영체제(윈도, MacOS 등)는 프로그램의실행을 위해 다양한 메모리 공간을 제공한다.
C 프로그램이 운영체제로부터 할당받는 대표적인 메모리 공간은 다음과 같다.

  • 코드(code) 영역
    데이터(data) 영역
    스택(stack) 영역
    힙(heap) 영역

메모리 공간에 관한 내용은 다음을 기약하자.

변수의 유효 범위, 코딩의 시작,TCP School

📖 지역 변수

지금까지 사용했던 변수는 지역 변수이고,
사용 범위가 함수 내, 즉 일정 지역에서만 사용하는 변수이다.

본래

auto 자료형 변수명;

auto 예약어와 함께 함수 안에 지역 변수를 선언하지만, 생략할 수 있으며,
이 경우 함수 안에 선언된 변수는 자동으로 지역 변수가 된다.

지역 변수와 자동 변수는 같은 용어이다.

지역 변수는 기억 부류 중 하나인데,

기억 부류는 변수를 사용 범위메모리에서의 존재 기간에 따라 나눈 것이다.
따라서 지역 변수는 변수의 특성 중 사용 범위를 강조!

지역 변수의 특징

1. 지역 변수는 사용 범위가 블록 내부로 제한되므로 다른 함수에서 사용 불가능

  1. # include <stdio.h>
  2.  
  3. void assign(void); // 함수 선언
  4.  
  5. int main(void)
  6. {
  7. int a = 0;
  8.  
  9. assign();
  10. printf("main 함수 a: %d\n", a);
  11.  
  12. return 0;
  13. }
  14.  
  15. void assign(void)
  16. {
  17. int a;
  18.  
  19. a = 10;
  20. printf("assign 함수 a: %d\n", a);
  21. }

7행과 17행에 같은 이름의 변수를 두 번 사용했지만,
7행의 변수 a는 사용 범위가 main 함수 블록으로 제한되므로,

19행의 컴파일러에서는 assign 함수에 선언된 변수 a를 사용하고,
assign 함수 내 변수 a의 선언문이 없다면 19행과 20행에서 a의 정체를 모르므로 컴파일 에러가 발생한다.


2. 지역 변수는 이름이 같아도 선언된 함수가 다르면 각각 독립된 저장공간을 갖는다.

함수에 선언된 변수 a는 이름만 같을 뿐 메모리에 별도의 저장 공간을 갖는다.


지역 변수를 사용하는 이유

1. 메모리를 효율적으로 사용한다.

지역변수가 선언된 함수가 반환되면 할당된 저장공간을 자동 회수하여 재활용한다.

2. 디버깅에 유리하다.

지역 변수는 그 변수가 선언된 함수에서만 원인을 찾으면 된다.

하지만, 지역 변수가 할당된 저장 공간은 자동으로 초기화되지 않으므로
쓰레기 값이 사용되지 않도록 주의해야 한다.

그동안 함수를 만들 때

int main()

괄호 안에 선언하는 매개변수는 호출할 때 전달되는 값을 받기 위해
특별한 위치에 선언하는 것일 뿐 변수가 갖는 모든 특징은 지역 변수와 같다.


지역 변수의 규칙

1. 특정 블록 안에 변수를 선언하면 사용 범위가 블록 내부로 제한된다.

메모리에 할당된 저장공간도 블록이 끝나면 자동으로 회수되어 더이상 존재하지 않는다.

  1. # include <stdio.h>
  2.  
  3. int main(void)
  4. {
  5. int a = 10, b = 20;
  6.  
  7. printf("교환 전 a와 b의 값: %d, %d\n", a, b);
  8. {
  9. int temp;
  10.  
  11. temp = a;
  12. a = b;
  13. b = temp;
  14. }
  15. printf("교환 후 a와 b의 값: %d, %d\n", a, b);
  16.  
  17. return 0;
  18. }

변수 temp는 8행~14행의 블록 안에 선언된 지역변수이다.
메모리에 할당된 공간도 14행에서 회수되므로 15행 이후에는 temp 사용이 불가능하다.

반면 a와 b는 main 함수 블록 어디서든 사용할 수 있으므로 8~14행 블록 안에서도 사용 가능하다.

다만, 블록이 중첩된 경우 주의할 규칙이 있다.

2. 사용 가능한 변수가 둘 이상이면 가장 가까운 블록에 선언된 변수를 사용한다.

9행에 새로운 변수 a, b를 선언하면 8~14행 블록 안에서는 5행에 선언된 변수 a와 b 대신
가까운 블록에 선언된 9행의 변수 a와 b를 사용한다.

안쪽 블록에서 선언된 변수는 메모리에 독립된 저장 공간을 가지므로,
안쪽 블록에서 a나 b의 값을 바꿔도 5행의 변수 a, b의 값은 변함 없다.


📖 전역 변수

블록 안이 아닌 함수 밖에 변수를 선언하면 전역 변수가 된다.
전역 변수는 특정 함수의 블록에 포함되지 않으므로

사용 범위가 함수나 블록으로 제한되지 않는다.

  1. # include <stdio.h>
  2.  
  3. void assign10(void);
  4. void assign20(void);
  5.  
  6. int a;
  7.  
  8. int main(void)
  9. {
  10. printf("함수 호출 전 a값: %d\n", a);
  11.  
  12. assign10();
  13. assign20();
  14.  
  15. printf("함수 호출 후 a 값: %d\n", a);
  16.  
  17. return 0;
  18. }
  19.  
  20. void assign10(void)
  21. {
  22. a = 10;
  23. }
  24.  
  25. void assign20(void)
  26. {
  27. int a;
  28.  
  29. a = 20;
  30. }

전역 변수는 특별한 예약어는 사용하지 않으며,
특정 함수 안에 있는 것이 아니므로

프로그램이 실행될 때 메모리에 할당되고 프로그램이 끝날 때까지 존재한다.

10행에서 처음 a를 출력하는데, 실행결과를 보면

전역 변수는 특별한 값으로 초기화하지 않아도 0으로 자동 초기화된다.

assign10 함수가 호출되면 22행에서 a가 10으로 바뀌고
assign20 함수가 호출되어 a에 20을 대입하지만
전역 변수 a는 바뀌지 않는다.

즉, 전역 변수와 지역 변수의 이름이 같으면 지역 변수를 먼저 사용한다.
따라서 함수가 반환되고 난 후 15행에서 전역 변수 a에는 assign10에서 저장한 값 10이 그대로 남아있다.


전역 변수의 문제점

1. 전역 변수의 이름을 바꾸면 그 변수를 사용하는 모든 함수를 찾아 수정해야 한다.

2. 전역 변수의 값이 잘못된 경우 접근 가능한 모든 함수를 의심해야 한다.

3. 코드 블록 내에 같은 이름의 지역 변수를 선언하면 그 영역에서는 전역 변수를 사용할 수 없다.


결국 사용 범위가 명확하고 통제 가능한 지역 변수를 우선적으로 사용하며
전역 변수는 많은 함수에서 수시로 데이터를 공유하는 경우에 제한적으로 사용하자.


📖 정적 지역 변수

지역 변수를 선언할 때 static 예약어를 사용하면 정적 지역 변수가 된다.
일반 지역 변수와 같이 사용 범위가 블록 내부로 제한되지만,

선언된 함수가 반환되더라도 그 저장 공간을 계속 유지한다.

  1. # include <stdio.h>
  2.  
  3. void auto_func(void);
  4. void static_func(void);
  5.  
  6. int main(void)
  7. {
  8. int i;
  9.  
  10. printf("일반 지역 변수(auto)를 사용한 함수...\n");
  11. for (i = 0; i < 3; i++)
  12. {
  13. auto_func();
  14. }
  15.  
  16. printf("정적 지역 변수(static)를 사용한 함수...\n");
  17. for(i = 0; i < 3; i++)
  18. {
  19. static_func();
  20. }
  21.  
  22. return 0;
  23. }
  24.  
  25. void auto_func(void)
  26. {
  27. int a = 0;
  28.  
  29. a++;
  30. printf("%d\n", a);
  31. }
  32.  
  33. void static_func(void)
  34. {
  35. static int a;
  36.  
  37. a++;
  38. printf("%d\n", a);
  39. }

25행에 정의된 auto_func 함수는 지역 변수 a를 선언하고 0으로 초기화 한다.
변수 a는 auto_func 함수가 호출될 때마다 메모리에 새롭게 할당되고 그때마다 0으로 초기화된다.
그 값을 29행에서 1 증가시키고 출력하므로 항상 1이 출력된다.

반면 35행처럼 static 예약어를 사용하면 프로그램이 실행될 때 메모리에 할당되며
프로그램이 끝날 때까지 존재한다.
또한 초기화하지 않으면 0으로 자동 초기화한다.


35행에 선언된 정적 지역 변수는 static_func 함수 안에 선언되었지만,
저장 공간의 할당이나 초기화 방법, 메모리 존재 기간이 전역 변수와 같다.

결국 정적 지역 변수는 선언된 블록 안에서만 사용하는 전역 변수와 같다.

🔔 정리

정적 지역 변수는 함수에 필요한 값을 보관하여 호출할 때마다 계속 사용할 수 있다.
프로그램이 끝날 때까지 저장 공간을 유지하면서 특정 함수에서만 쓰는 경우 유용하다!



📖 레지스터 변수

레지스터는 CPU 안에 있어 데이터 처리 속도가 가장 빠른 저장 공간이다.
register 예약어를 사용하고, 반복문에 쓰는 변수와 같이 사용 횟수가 많은 경우 실행 시간을 줄일 수 있다.

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

반복문 안의 변수 i처럼 자주 사용하는 변수를 레지스터 변수로 선언하면
프로그램 실행 시간을 줄일 수 있다.

메인 메모리에 있는 일반 변수의 값은 레지스터로 옮긴 후 연산 장치에 사용된다.

🚨 레지스터 변수 사용 시 주의점

1. 전역 변수는 레지스터 변수로 선언할 수 없다.

CPU 자원을 잠깐 빌리는 것이므로 실행하는 동안
계속 저장 공간을 확보해야 하는 전역 변수는 레지스터에 할당할 수 없다.

2. 레지스터 변수는 주소를 구할 수 없다.

저장 공간이 메모리에 있는 것이 아니므로 주소 연산자를 써서 주소를 구할 수 없다.

3. 레지스터의 사용 여부는 컴파일러가 결정한다.

컴파일러는 레지스터와 메모리 중 어디에 할당하는 것이 더 이득인지 판단하여 적당한 저장 공간을 선택한다.

profile
분석하는 남자 💻

0개의 댓글