2020. 1. 2. 16:23ㆍC,C++
출처 : http://www.soen.kr/
SoEn:소프트웨어 공학 연구소
www.soen.kr
7-1. 지역변수
7-1-가. 전역변수와 지역변수
기억 부류에는 4가지 종류가 있는데 일단 도표로 특성을 요약하였다.
4가지 기억 부류 중에 가장 중요한 것은 전역변수와 지역변수를 구분하는 것이다.
1)변수의 선언 위치가 다르다. 두 부류의 가장 뚜렷한 차이점인데 전역변수는 함수 바깥에서 선언하고 지역변수는 함수 내부에서 선언한다.
2)변수의 통용 범위가 다르다. 전역변수는 특정한 함수 내부에서 선언된 것이 아니므로 함수에 속하지 않고 프로그램 전체가 공유한다. 따라서 변수가 선언된 위치 이후에는 어디서든지 이 변수를 사용할 수 있다. 위 예제에서 보다시피 global은 main 함수나 func 함수에서 자유롭게 읽고 쓸 수 있다.
3) 변수의 파괴 시기가 다르다. 전역변수는 프로그램에 소속되어 있고 모든 함수에서 사용 가능해야 하므로 프로그램이 실행중인 동안에는 파괴되지 않는다.
4) 변수가 생성되는 기억 장소가 다르다. 지역변수는 프로그램 실행중에 생성, 파괴를 반복하므로 스택에 생성된다.
전역변수는 한 번 정해진 메모리 위치에 계속 남아 있어야 하므로 정적 데이터 영역에 생성된다.
5) 초기화 여부가 다르다. 전역변수는 별도의 초기식이 없더라도 0으로 초기화된다. 반면 지역변수는 별도의 초기식이 없을 경우 초기화되지 않는다.
루프 제어 변수나 중간 계산에 필요한 변수와 같이 함수 내부에서 임시적인 용도로 쓴다면 지역변수를 사용하고 프로그램 전체에 걸쳐 값을 유지해야 하는 변수는 전역변수를 사용한다.
7-1-나. 지역변수의 장점
지역변수는 함수 내부에서만 사용할 수 있고 함수가 끝나면 파괴되는데 비해 전역변수는 모든 함수에서 자유롭게 사용할 수 있고 프로그램이 실행중인 동안은 계속 유지된다. 지역변수가 전역변수에 비해 어떤 점이 우월하며 지역변수의 장점은 무엇인지 알아보자.
1) 함수의 독립성을 높인다. 부품끼리 공유하는 것(전역변수)이 많아지다 보면 의존 관계를 가지게 되므로 서로 얼키고 설켜서 좋지 않은 구조를 만들어 낸다. 함수끼리 정보를 주고 받기 위해서는 인수와 리턴값을 사용하는 것이 정석이다.
2) 지역변수는 디버깅 효율을 향상시킨다. 버그, 즉 논리적인 에러가 발생하는 원인의 십중팔구는 변수를 잘못 조작한 것이다. 지역변수는 디버깅하기 아주 쉽다. 일단 지역변수를 많이 쓰면 전역변수의 수가 상대적으로 줄어들게 되므로 관찰 대상 변수의 범위가 대폭 좁아진다.
3) 지역변수는 메모리를 절약한다.
4) 재귀 호출이나 상호 호출같은 특별한 기법은 지역변수가 있어야만 사용할 수 있다.
지역변수가 전역변수에 비해 월등히 많은 장점을 가지고 있다. 전역변수는 편리하기는 하지만 복잡한 문제를 일으킬 수 있기 때문에 전역변수의 사용은 가급적이면 자재하는 것이 좋다. 전역변수를 전혀 사용하지 않고도 프로그램을 작성할 수 있다는 것이 이미 수학적으로 증명되어 있으며 전역변수를 병적으로 싫어하는 개발자들도 있다.
7-1-다. 외부변수
지정자(Specifier)는 기억 부류를 비롯하여 상수 지정, 최적화 금지 등 변수의 여러 가지 성질을 지정하는 키워드인데 필요없을 경우 생략할 수도 있다. 기억 부류 지정시 auto,extern, static, register 등의 키워드를 사용한다.
auto int i,sum;
이렇게 선언하면 i와 sum은 지역변수가 된다. 지정자를 생략하면 디폴트로 auto가 되기 때문에 거의 사용하지 않는다.
extern 키워드는 변수가 외부 어딘가에 선언되어 있다는 것을 알리는 역할을 한다.
함수가 전역변수를 사용하기 위해서는 extern선언을 하는게 원칙적이나, 함수보다 앞쪽에 선언되어 있는 외부변수는 굳이 extern선언을 하지 않아도 된다.
extern선언이 필요한 경우는 전역변수가 다른 외부 모듈에 선언되어 있을 때이다.
아무리 전역변수라도 정보가 공개된 이후에만 사용할 수 있는데 이런 선언을 하는 키워드가 바로 extern이다.
다른 모듈에 있는 전역변수를 참조하고자 할 때는 extern 키워드로 이 변수가 외부에 있다는 것과 변수의 타입을 선언해야 한다.
7-2. 정적 변수
7-2-가. 정적 변수
정적변수(Static Variable)는 전역변수와 지역변수의 성격을 동시에 가지는 좀 특별한 기억 부류이다.
정적변수의 성질을 요약하자면 저장 장소는 전역변수이되 통용 범위는 지역변수라 할 수 있다. 정적변수를 선언할 때는 반드시 static이라는 지정자를 붙여야 한다.
ex)
void PrintCount();
void main()
{
int i;
for (i=0;i<5;i++) {
PrintCount();
}
}
void PrintCount()
{
static int count=0;
count++;
printf("저는 %d번째로 호출되었습니다.\n",count);
}
더 자세한 설명 : https://dojang.io/mod/page/view.php?id=690
C 언어 코딩 도장: 79.2 정적 변수 선언하기
정적 변수를 알아보기 전에 먼저 자동 변수로 예제를 작성해보겠습니다. 다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요. variable.c #include void increaseNumber() { int num1 = 0; // 변수 선언 및 값 초기화 printf("%d\n", num1); // 변수 num1의 값을 출력 num1++; // 변수의 값을 1씩 증가 } int main() { increaseNumber(); // 0 increas
dojang.io
7-2-나. 레지스터 변수
레지스터형 변수는 메모리가 아닌 CPU의 레지스터에 저장된다.
컴퓨터의 가장 핵심 부품인 CPU의 한 가운데에 있는 기억 장소이기 때문에 레지스터의 속도는 메모리와 비교가 되지 않을 정도로 빠르다. 값을 읽거나 쓰는데 수십억분의 1초 정도밖에 걸리지 않는다. CPU의 종류에 따라 다르지만 레지스터는 보통 10개~20개 정도밖에 없는 아주 귀한 기억 장소인데 여기에 변수를 저장하면 이 변수를 참조하는 문장의 속도가 빨라진다.
CPU의 레지스터 개수가 많지 않기 때문에 레지스터형 변수는 두 개까지만 선언할 수 있다.
레지스터형 변수를 사용하는 이유는 조금이라도 더 빠른 속도를 얻기 위해서이다. 대규모의 루프를 돌린다거나 할 때 루프 제어 변수를 레지스터형으로 선언하면 이 변수의 읽기, 증감 속도가 빨라지므로 전체 루프의 실행 속도가 빨라질 것이다.
register int i, j;
for (i=0;i<10000000;i++) {
for (j=0;j<1000;j++)
총 루프 반복 회수는 100억번인데 이런 경우에 레지스터형 변수를 쓸 때와 지역변수를 쓸 때 속도 차이가 많이 나게 된다. 이런 경우가 아니라면 레지스터형 변수로 속도상의 이득을 볼 수 있는 경우는 그리 흔하지 않다. 요즘 CPU나 메모리가 워낙 빨라져서 루프를 도는 정도의 차이는 거의 실감하기 어렵다.
속도상의 차이 외에는 일반적인 지역변수와 다른 점이 전혀 없다. 레지스터형 변수는 응용 프로그램을 고도로 최적화할 때 가끔 사용되는데 사실 이 기억 부류는 몰라도 별로 손해볼 것이 없다. 왜냐하면 별도의 지정이 없더라도 컴파일러가 남는 레지스터가 있으면 알아서 레지스터형으로 만들 정도로 충분히 지능적이기 때문이다.
7-2-다. 정적 함수
기억 부류는 주로 변수에 대해 적용되지만 함수도 기억 부류를 가진다.C의 함수들은 원칙적으로 전역이다.
기억 부류 중에 함수에 적용되는 것은 정적(static) 기억 부류밖에 없다.
함수 정의문 앞에 static이라는 지정자만 붙이면 이 함수는 정적 함수가 된다.
static void func()
{
....
}
정적 함수와 반대되는 개념에 대해 별다른 명칭은 없고 굳이 이름을 붙인다면 비정적 함수나 외부 함수 정도가 될 것이다. 외부 함수는 별다른 지정이 없는 한 외부로 항상 알려지며 원형 선언만 하면 어떤 모듈에서나 이 함수를 호출할 수 있다.
그러나 정적 함수는 특정 모듈에서만 사용하도록 정의된 것이므로 외부에서 원형을 선언한다 하더라도 이 함수를 호출할 수 없다.
외부에서 그 존재를 알 수 없도록 해야 하는 이유는 외부 정적변수의 경우와 마찬가지로 이름 충돌을 방지하기 위해서이다.
7-3-가. 통용 범위 규칙
변수나 함수, 태그 같은 명칭은 상호 구분되어야 하므로 중복되어서는 안된다. 그래서 같은 이름을 가진 두 개의 변수를 선언할 수 없다.
그러나 이 법칙에 예외가 있는데 통용 범위가 다른 명칭끼리는 같은 이름을 가질 수도 있다. 다음 예를 보자.
void func()
{
int i;
....
void proc()
{
double i;
....
컴파일러는 하나의 명칭에 대해 통용 범위가 겹쳐 있을 경우 좁은 범위를 가지는 명칭에게 우선권을 줌으로써 모호한 상황을 극복한다. 규칙을 마련하고 이 규칙대로 동작하면 모호하지 않다.
7-3-나. 블록 범위
지역변수는 { } 괄호안의 블록에 선언된 변수를 의미하며 변수가 선언된 블록 내부에서만 통용된다. { } 괄호안에서만 통용되는 범위를 블록 범위라고 하는데 { } 괄호가 보통 함수의 시작과 끝을 나타내고 함수 선두에 지역변수를 선언하는 경우가 많기 때문에 지역변수의 통용 범위는 함수 내부가 되는 것이다.
함수의 범위보다도 더 좁은 이런 통용 범위를 블록 범위라고 한다. 만약 이미 선언된 변수명을 다시 사용하고 싶다면 일부러 블록을 하나 만들고 이 블록안에 원하는 변수를 선언하면 된다.
사실 같은 이름의 변수를 재사용하기 위해 일부로 블록을 만들어 쓰는 것보다는 차라리 다른 이름의 변수를 쓰는 것이 더 좋을 것이다. 하지만 함수가 아주 길 때 for 문 내부에서만 또는 case문의 내부에서만 사용할 지역변수가 필요하다면 블록 범위의 변수가 유용하게 사용된다.
7-3-다. 선언과 정의
아주 비슷한 용어인 것 같지만 달라도 한참 다르며 다소 헷갈리는 용어라 정리가 필요하다. 일단 도표로 선언과 정의의 특성을 정리해 보자.
선언(Declaration) - 컴파일러에게 대상에 대한 정보를 알린다.
int Max(int a, int b);
정의(Definition) - 대상에 대한 정보로부터 대상을 만든다.
정의는 실제 대상을 만들어 내기 때문에 중복되어서는 안된다. 전체 프로그램을 통해 단 한 번만 나타나야 하며 두 번 이상 중복할 필요도 없다. 만약 정의를 두 번 반복하면 컴파일러는 왜 똑같은 함수를 두 번 정의하느냐는 에러 메시지를 출력할 것이다.
7-3-라. 설계 원칙
잘 짜여진 프로그램을 분석해 보면 함수의 분할 구조가 감탄스러울 정도로 잘 되어 있음을 볼 수 있고 그런 함수를 만드는 능력이 부러워지기까지 한다.
그러나 함수를 잘못 디자인하면 코드는 더 커지고 프로그램은 더 느려지며 조금이라도 수정하려면 어디를 건드려야 할지 판단하기 힘든 나쁜 구조가 만들어진다.
1)함수의 이름을 최대한 설명적으로 작성하여 이름만으로 무엇을 하는 함수인지, 이왕이면 어떻게 쓰는 것인지도 알 수 있도록 한다. 특히 팀 작업을 하거나 오랫동안 관리해야 할 코드라면 더욱 더 정성스럽게 이름을 붙여야 한다.
ex) GetHighestScore, GetAverageScore
2) 두 번 이상 중복된 코드는 반드시 함수로 분리한다. 10줄짜리 코드를 10번 반복한다면 나머지 90줄은 불필요하게 용량만 차지하는 것이다. 용량의 문제보다 더 심각한 것은 코드를 유지, 확장하기가 아주 곤란해진다는 점이다. 중복되는 회수에 상관없이 앞에서 이미 만들었던 코드와 비슷한 코드를 또 작성해야 한다면 일단 그 부분을 함수로 만들고 기존 코드를 함수 호출로 수정해야 한다.
3) 반복되지 않더라도 한 단위로 볼 수 있는 작업은 함수로 만든다.함수의 소스가 아주 길어져서 수백줄이 되면 그 많은 코드들의 어떤 부분이 어떤 작업을 하는지 얼른 파악되지 않는다. 게다가 다른 일을 하는 코드들이 한 곳에 섞여 있으면 필시 꼬이게 마련이며 이런 복잡한 코드는 대체로 메인 코드인 경우가 많다. 이 코드들의 그룹을 나누어 함수로 분리해 두면 메인 코드는 이 함수들을 조립하는 수준으로 간단해진다.
4) 함수는 한 번에 하나의 작업만 해야 한다. 만약 꼭 여러 가지 일을 한꺼번에 해야 하는 함수가 필요하다면 각각의 함수를 만든 후 이 함수들을 호출하는 함수를 하나 더 만들면 된다.
5) 입력과 출력이 직관적이고 명확해야 한다. 인수는 함수에게 주어지는 작업거리인데 함수가 하는 일에 꼭 필요한 정보만 최소한의 인수로 받아들여야 한다.
6) 함수는 자체적으로 에러 처리를 해야 한다.
여기서 논한 함수 설계에 대한 지침은 어디까지나 일반적인 참고사항일 뿐이다. 특수한 실무 환경에서는 이 지침과는 다르게 함수를 만들어야 하는 불가피한 경우도 존재한다.
'C,C++' 카테고리의 다른 글
[C] C언어 공부하기 9. 배열 (0) | 2020.01.02 |
---|---|
[C] C언어 공부하기 8. 표준 함수 (0) | 2020.01.02 |
[C] C언어 공부하기 6. 함수 (0) | 2020.01.02 |
[C] C언어 공부하기 5.연산자 (0) | 2020.01.02 |
[C] C언어 공부하기 4. 제어문 (0) | 2019.12.30 |