공부/C언어

C언어 - (8) : 함수/지역변수/전역변수/static변수

BKM 2024. 10. 10. 17:39

함수

함수의 문법적인 형태는 다음과 같다.

int main(void)
{
	실행문
}

/*
int : 함수가 반환하는 데이터의 형태
main : 함수의 이름
void : 함수를 실행시키기 위해 입력되어야 하는 데이터의 형태
*/

 

함수 유형1 : 전달 인자(O), 반환 값(O)

// add 함수
int main(int num1, int num2)
{
    int result = num1+num2;
    return result;
}

함수 유형2 : 전달 인자(O), 반환 값(X)

void ShowAddResult(int num) // 전달 인자(num), 반환인자(X): return값 없음
{
    printf("덧셈 결과 출력: %d \n", num);
}

함수 유형3 : 전달 인자(X), 반환 값(O)

// 전달 인자(X): void, 반환 값(int num)
int ReadNum(void)
{
    int num;
    scanf("%d", &num);
    return num;
}

함수 유형4 : 전달 인자(X), 반환 값(X)

// 전달 인자(X:void), 반환 값(X:void)
void HowToUseThisProg(void)
{
    printf("두개의 정수를 입력하시면 덧셈 결과가 출력됩니다. \n");
    printf("두개의 정수를 입력하세요. \n");
}

값을 반환하지 않는 return (== void)

return문이 있으면 값을 반환하는 것으로 생각할 수 있지만, 무엇을 return하는지 정하지 않는다면 아무것도 반환하지 않는 것과 동일하며,

함수를 탈출하는 용도로도 사용할 수 있다.

void NoReturnType(int num)
{
     if (num<0)
     	return; // 아무것도 return하지 않고, 함수 탈출
}

함수의 작성 순서

작성된 소스 코드의 위에서 아래로 컴파일이 순차적으로 이루어지기 때문에, 함수의 작성과 호출에 대한 순서가 중요하다.

// 함수의 컴파일 순서
#include <stdio.h>

int main(void)
{
    printf("3,4중에서 큰 수는 %d이다. \n", NumCompare(3,4));
    printf("7,2중에서 큰 수는 %d이다. \n", NumCompare(7,2));

    return 0;
}

int NumCompare(int num1, int num2)
{
    if (num1>num2)
        return num1;
    else if (num2>num1)
        return num2;
    else
        return 0;
}

위의 소스 코드의 경우에 `main`함수에 대한 컴파일이 먼저 이루어지는데, 그 과정에서 `NumCompare`이라는 함수에 대한 정보가 없기 때문에 Error가 발생하게 된다. 그러므로 다음과 같이 `NumCompare`함수를 먼저 작성해주어야 한다.

// 함수의 컴파일 순서
#include <stdio.h>

int NumCompare(int num1, int num2)
{
    if (num1>num2)
        return num1;
    else if (num2>num1)
        return num2;
    else
        return 0;
}

int main(void)
{
    printf("3,4중에서 큰 수는 %d이다. \n", NumCompare(3,4));
    printf("7,2중에서 큰 수는 %d이다. \n", NumCompare(7,2));

    return 0;
}

하지만, 변수의 선언과 유사한 방식으로 함수의 내용은 뒤에 작성하되 선언을 미리해주면서 `main`함수를 가장 위에 위치시킬 수 있다.

// 함수의 컴파일 순서
#include <stdio.h>

int NumCompare(int num1, int num2); // 함수 미리 선언

int main(void)
{
    printf("3,4중에서 큰 수는 %d이다. \n", NumCompare(3,4));
    printf("7,2중에서 큰 수는 %d이다. \n", NumCompare(7,2));

    return 0;
}

int NumCompare(int num1, int num2)
{
    if (num1>num2)
        return num1;
    else if (num2>num1)
        return num2;
    else
        return 0;
}

지역 변수(Local Variable) vs 전역 변수(Global Variable)

함수 내에서 선언되어 함수 내에서만 사용되는 변수를 '지역 변수(Local Variable)'라고 하며,

함수 외부에서 선언되어 전체적으로 사용되는 변수를 '전역 변수(Global Variable)'라고 한다.

Local Variable

#include <stdio.h>

void SimpleFuncOne(void)
{
    int num=10;
    num++;
    printf("SimpleFuncOne num: %d \n", num);
}

void SimpleFuncTwo(void)
{
    int num1=20;
    int num2=30;
    num1++, num2++;
    printf("num1 & num2: %d %d \n", num1, num2);
}

int main(void)
{
    int num=17;
    SimpleFuncOne();
    SimpleFuncTwo();
    printf("main num: %d \n", num);
    return 0;
}

 

위의 예시처럼 같은 변수 이름인 `num`을 `SimpleFuncOne`과 `main`함수에서 사용하였지만, `SimpleFuncOne`에서는 10의 값을 가지고 `main`에서는 17의 값을 가지고 있다.

 

이는 각각의 `num`이 서로 다른 함수 안에서 지역변수로 사용되었기 때문이다.

Global Variable

void Add(int val);
int num; // 함수 외부에 전역 변수 선언(전역 변수는 기본 0으로 초기화됨)

int main(void)
{
    printf("num: %d \n", num); // num: 0
    Add(3); // 전역 변수 num에 3 더해주기
    printf("num: %d \n", num); // num: 3 
    num++; // 전역 변수 num에 1 더해주기
    printf("num: %d \n", num); // num: 4
    return 0;
}

void Add(int val)
	num+=val;

위의 예시와 같은 방식으로 전역변수를 사용할 수 있다.

int Add(int val);
int num=1; // global

int main(void)
{
    int num=5; // local
    printf("num: %d \n", Add(3)); // num: 12
    printf("num: %d \n", num+9); // num: 14
    return 0;
}

int Add(int val)
{
    int num=9; //local
    num+=val;
    return num; // return local 'num'
}

위의 예시는 `num`이라는 동일한 이름의 변수에 대해서 local, global 변수의 사용이 혼용된 예이다.

Global 변수를 처음에 선언하였지만, 위의 코드에서 Global `num`이 사용된 경우는 없다.

static 변수

'static 변수'는 함수안에서 선언되며, 함수안에서만 호출이 가능하다는 점에서 지역 변수의 성격을 갖는다.

하지만, 프로그램 시작과 동시에 메모리 공간에 저장되고 프로그램이 종료될 때까지 존재한다는 점에서 전역 변수의 성격도 갖는다.

추가적으로, static 변수는 프로그램 동작 과정에서 단 1회만 초기화되며 초기화 값을 지정해주지 않으면 0으로 초기화된다.

#include <stdio.h>
void SimpleFunction(int i);

int main(void)
{
    int i;
    for (i=0; i<3; i++)
        SimpleFunction(i);

    return 0;
}

void SimpleFunction(int i)
{
    static int num1; // static num1 초기화 = 0
    num1=i; // num1에 새로운 값 대입
    int num2=0;
    num1++, num2++;
    printf("static: %d, local: %d \n", num1, num2);
    /*
        static: 1, local: 1 
	    static: 2, local: 1 
	    static: 3, local: 1 
    */
}

위의 예시처럼 static변수 `num1`을 선언하고 for loop를 돌아가면서 `num1`에 새로운 값을 대입해주지만

새롭게 초기화가 되지 않고 0에서부터 1씩 순차적으로 증가하는 것을 확인할 수 있다.

 

static변수와 전역 변수는 함수를 빠져나가더라도 메모리 공간에 존재한다는 점에서 공통점이 존재하며, 이를 위한 용도로 선언 및 사용된다

 

하지만, 전역 변수의 경우에는 다른 어떤 함수에서도 호출이 가능하여 코드의 작성자가 헷갈리는 경우에 그 값이 변경될 수 있지만

static 변수는 접근의 범위가 선언된 함수만으로 한정되기 때문에 더욱 안정적이라는 이점이 존재한다.

(참고) Register 변수

'register 변수'는 cpu 내부의 register라는 장치에 저장되는 변수로서, 가장 빠르게 접근하여 사용이 가능하다.

int SimpleFunc(void)
{
    register int num=3;
    return 0;
}