저장공간에 따른 변수의 쓰임새
아래 필자가 공유했던 워싱턴 대학의 함수호출 시에 이루어지는 프로시저(루틴) 강좌를 살펴봤다면, 프로그램이 작동할 때, 스택 메모리와 힙 메모리가 쓰이는 용도가 다르다는 것을 알 수 있다.
스택메모리는 프로그램이 실행되기 전 소스코드를 컴파일(컴퓨터가 이해하는 기계어로 번역) 단계에서 쓰이고, 힙메모리는 런타임(프로그램 실행) 간에 쓰인다.
스택메모리와 힙메모리가 충돌할 정도로 메모리의 저장공간을 불필요하게 낭비하거나, 맹하게 중첩된 스콥(블록) 내에서 같은 이름의 변수를 선언하면 예상치 못한 결과를 초래한다. 그러면 아래 닉네임 Pope 개발자가 말하는 데로, 게임개발에 국한해서도 타 파트의 자질구레한 일(디버깅)을 지원사격해야 할 수도 있다.
'혼자 공부하는 C언어' 책의 백미는 13강인 거 같다. 정확하게는 값을 복사해서 전달하는 방식(Call by Value)과 주소를 전달하는 방식(Call by Reference)에 가까운 '주소를 반환하는 함수'를 통해 함수의 데이터 공유 방법을 알려주는 부분에서 필자의 녹록지 않은 내공이 느껴졌다. 이 책의 저자는 호주에서 시니어 개발을 하고 있다고 한다. 이 정도 실력이면 위의 PopeTV에서 얘기하는 캐나다에서의 시니어 게임개발자의 보통 수준(폴리글랏 좀 한다는)보다는 개발의 기본기는 훨씬 뛰어나기 때문이다.
변수는 크게 지역변수(Local)와 전역변수(Global)로 나뉘는 건 대다수 비전공자 개발자도 다 안다. 하지만 필자가 여기서부터 말하려는 것은 수학처럼 각 변수의 특징과 규칙을 파악해서 실제 코딩 시 까먹지 않고 정확하게 쓰자는 게 아니다.
각 변수가 실제 하드웨어단에서는 어느 공간에 저장되는지 알고, 그 공간의 특징만 잘 파악하고 있으면, 모든 변수의 종류를 외울 까닭이 없다. 원리를 알고 있으면, 단지 그 변수는 어떤 케이스에 써서 프로그램이 컴파일 단계에서 할당되는지, 런타임(실행) 단계에서 할당되는지까지 알 수 있다.
아래 실전 문제에서 살펴보자.
도전 | 전역 변수 교환 프로그램
2개의 전역 변수에 값을 입력하고 교환한 후 출력하는 프로그램을 작성합니다. 입력, 교환, 출력 작업은 다음에 제시된 함수 원형을 지켜 작성합니다.
보통의 코딩 수준에서는 전역변수를 선언할 필요까지 없었다. 왜냐하면, 특정 함수 블록 밖에서 전역적으로 쓰이는 변수의 용도는 아래와 같기 때문이다.
사용 범위는 프로그램 전체다.
어떤 함수라도 안(함수 내부)에서 직접 사용할 수 있다.
힙 메모리 공간에 할당(선언)되므로, 프로그램이 실행되면(런타임) 초기화(0으로 자동 초기화) 된다.
C언어에서 함수 내에 변수를 선언하면, 기본적으로 자료형 앞에 auto가 아래와 같이 생략되어 있다.
(auto) int a = 10;
이것은 지역변수 설정 예약어(키워드)다. 메모리의 스택 영역에 할당되어, 프로그램이 실행되기 이전(컴파일 단계)에 이미 초기화되어 있어야 하므로 사용자가 값을 세팅하지 않으면(Garbage 값이 들어있기 때문에) 오류가 뜬다(컴파일 에러).
반면에 프로그램이 실행할 때 메모리 할당과 디폴트로 세팅이 되어 있는 전역변수는 힙(그중 Data 영역)이라는 메모리 공간에 저장되기 때문에, 사용자가 깜빡하고 초기화하지 않더라도 0이 초기화되어 있다. 대표적인 전역 변수는 위의 실전예제에서와 같이 main() 함수 바깥에 있는 'int a, b'와 같다. 따로 예약어가 없이 특정 함수 블럭의 내부가 아니라 '외부'에서 선언한다.
전역변수와 같이 static은 힙 메모리의 데이터 영역에 저장되나, 코드 블록 내부(지역)에서 선언되더라도 프로그램 전체에서 공유가 가능하다. 즉, 지역 변수에 static을 사용해서 정적 지역 변수로 만들면 프로그램의 시작부터 종료까지 저장공간이 유지된다. 위의 실전예제에서는 불필요하고, 아래 예제 소스 코드에서 static을 사용했을 때의 결과와 사용하지 않았을 때의 결과가 다른 걸 보고 무슨 말인지 가늠할 수 있다.
위와 똑같은 소스 코드이지만, *sum() 포인터 함수 내부에서 res 변수를 위와 달리 static(정적 지역) 변수로 선언하지 않았기 때문에 이 함수 내부 영역(scope)에서만 사용이 가능하다. 그래서 실제로 힙메모리(프로그램 전체 실행 중 사용)에 할당되지 않고, 스택 메모리(지역변수)에 할당되어 경고메시지를 띄운다. 지역 변수는 특정 함수 블록 안에 변수를 선언하여 필요한 경우 잠깐 사용하고 메모리를 재활용하는 효과를 볼 수 있다.
스택과 힙이 충돌할 수 있는 케이스다. 그래서 이런 부분이 메모리의 경계 칩입으로 인해 프로그램 실행 간에 런타임 오류가 발생할 수 있는 소지가 있다. 그러므로 전역변수를 덜 쓰는 것도 중요하지만, 필요할 때 같은 변수를 여러 함수에서 쉽게 공유하기 위해 적절히 사용할 필요도 있다.
하지만 여기서 전역 변수를 남용하는 모질이 같은 위의 시니어 개발 연령인데도 초짜 짓을 하는 게 바로 전역 변수는 아래와 같은 문제점(장점보다 부작용이 더 큼)을 가지고 있기 때문이다.
전역 변수의 이름을 바꾸면 그 변수를 사용하는 모든 함수를 찾아 수정해야 한다.
전역 변수의 값이 잘못된 경우 접근 가능한 모든 함수를 의심해야 한다.
코드 블록 내에 같은 이름의 지역 변수를 선언하면 거기서는 전역 변수를 사용할 수 없다(우선순위가 지역변수 -> 전역변수).
'혼자 공부하는 C언어'의 13-2강의 소스코드를 직접 참고하려면 아래 필자의 깃헙(github)에서 직접 다운로드할 수 있다.
참조
서현우. (2023). 혼자 공부하는 C언어 (2nd ed., Vol. 1). 한빛미디어.
"개발에서 절반이 디버깅"이라고 말하는 Pope 뉴스