[c++]지역, 전역, 동적 변수

Date:

카테고리:

#변수의 모든 종류

변수에 관한 모든것을 정리하려고 한다.

나중에 더 추가될 내용도 있을 수 있지만, 내가 아는 모든 내용을 정리하려 한다.

지역변수

지역변수는 특정 범위에서만 접근할 수 있는 변수를 말한다.

지역변수는 주로 함수 내부에서 선언되는데, 메인 함수도 함수이기 때문에, 같은 맥락에서 이해하면 된다.

지역변수는 함수가 호출될 때마다 생성되고 함수가 종료되면 소멸한다.

그렇기에 해당 변수는 함수 내부에서만 사용할 수 있으며,

함수 외부에서는 접근할 수 없는것이 원칙이다.



만약 변수명이 같다면 컴파일러가 구분할 수 없기에 오류가 나지만,

다른 함수에서 동일한 이름의 변수를 사용해도 서로에게 영향을 주지 않는다.

이는 변수의 범위, 즉 스코프 이다. 스코프 연산자를 통하여도 같은 동작을 할 수 있다.



지역 변수의 활용을 다루자면, 함수의 실행 중에 필요한 임시 데이터를 보관하는 데 유용하다.

함수가 종료되면 메모리를 해제해 주지 않아도 자동으로 소멸하기 때문에, 메모리 관리 측면에서 효율적이다.



그리고, 지역 변수는 스택 메모리에 할당되기 때문에, 지역변수의 크기가 매우 커지게 되면,

스택의 버퍼 크기를 초과하게 되면, 스택 오버플로우가 발생하게 된다.

이는 내 블로그 글에서 자세히 알아볼 수 있다.


지역변수의 특징

  1. 초기화되지 않은 지역 변수의 값은 소위 말하는 쓰레기 값이 나오게 된다.
    그렇기에 항상 초기화를 하여 사용하거나, 입력을 필연적으로 받게 설계하여 사용해야 한다.

  2. 재귀함수에서의 지역 변수는 같은 이름을 가지더라도 다른 주소 공간을 차지하기 때문에,
    재귀 알고리즘에서 임시 데이터를 저장하는 용도로 유용하게 사용할 수 있다.

  3. 전역변수의 이름과 지역변수의 이름이 같은 경우, 해당 함수 내에서는 지역변수로 접근하게 된다.
    따라서, 다음과 같이 스코프 연산자를 사용하여 두 변수에 대하여 각각 접근할 수 있다.


#include <iostream>

int x = 10; // 전역변수

void test()
{
    int x = 5; // 지역변수
    std::cout << "지역변수 x: " << x << std::endl; // 지역변수 출력
    std::cout << "전역변수 x: " << ::x << std::endl; // 전역변수 출력
}

int main()
{
    test();
    return 0;
}


출력 결과는 이렇다.

지역변수 x: 5
전역변수 x: 10
  1. 또한, 함수 내에서도 중괄호 ‘{}’ 안은 다른 지역으로 인식되기 때문에, 같은 이름을 사용할 수 있다.
    이를 활용하여 비슷한 종류의 선언을 반복해야 할 때, 변수 이름에 고통받지 않고 선언할 수 있다.

전역변수

전역 변수는 모든 함수 내에서 접근할 수 있는 변수이다.

데이터 영역에 저장된다는 특징이 있어서, 프로그램이 종료될 떄까지 유지된다.



전역변수의 활용을 다루자면, 모든 코드 영역 내에서 접근이 가능해야 하고, 공유해야 하는
데이터를 관리할 때 주로 사용된다.

반대로 생각하면, 모든 코드 영역에서 접근할 수 있기 때문에, 조심히 사용해야 한다



만약 이 변수로 인해서 프로그램에 치명적인 오류가 발생할 수 있는 여지가 있으면
전역변수 사용을 신중히 고려할 필요성이 있다.

c++ 프로그래밍에서 접근제한자가 있는 이유랑 같은 맥락이다.



전역변수를 선언하는 방법은, 함수 밖에서 선언하는 방법과,

static 키워드를 사용하여 선언하는 방법이 있다.


전역변수의 특징

  1. 전역변수는 초기화하지 않는다면 쓰레기값이 아닌, 0으로 초기화되게 된다.
    포인터의 경우에는 nullptr이 되는 것이다.
    사실 NULL 의 경우에도 0 과 같은 의미이기 때문에, 항상 기본값인 0으로 초기화 된다고 봐도 무방하다.
    이를 이용하여, 굳이 힘들게 초기화를 해주지 않아도 된다.

  2. static 키워드를 붙이지 않고, 함수 외부에서 선언한 변수는 외부 연결을 가지게 된다.
    이는 즉 다른 파일에서도 접근이 가능하다는 뜻이다.

  3. static 키워드를 붙이고 함수 외부에서 선언한 변수는 내부 연결을 가지게 된다.
    이는 즉 한 파일 내에서만 접근이 가능하다는 뜻이다.

  4. static 키워드를 붙이고 함수 내부에서 선언한 변수는
    전적 전역변수가 되며, 이 변수는 함수 내에서만 접근할 수 있다.
    또한, 함수가 종료되어도 메모리가 사라지지 않고 남아있기 때문에
    재귀 함수 등에서 함수의 상태 등을 저장하고자 할 때 사용된다.

  5. 외부 연결을 가지는 전역 변수는 다른 파일에서 동일한 이름을 가진 전역 변수와 충돌할 수 있기 때문에,
    서로 다른 파일에서 같은 이름의 전역 변수를 사용할 때는 충돌을 방지하기 위해 extern 키워드를 사용하여 변수를 선언해야 한다.

  6. 전역변수를 선언할 때, extern 키워드를 사용하지 않고도 그냥 선언한 변수를 사용할 수 있다.
    이 경우는 암묵적으로 외부 연결을 가지는 전역 변수로 간주되는 컴파일링 때문이다.
    변수를 선언하고 정의하는 파일 내에서는 extern 키워드를 생략할 수 있다.


동적변수

동적 변수는 프로그램 실행 중에 동적으로 할당되고 사용되는 변수이다.

동적할당 이라고 표현하는 경우가 더 많다.

힙 메모리 영역에 할당되며, 프로그래머가 직접 메모리를 할당하고 해제해야 한다.



동적 할당은 malloc, calloc, realloc, New 등의 키워드를 사용하여 필요한 메모리를

할당하고, delete, free 함수 등을 사용하여 메모리를 해제한다.



메모리를 재때 해제해주지 않는 등의 실수를 하게 되면, 메모리 누수라던지

댕글링 포인터 등이 발생할 수 있기 때문에 필연적으로 메모리를 해제하게 설계해야 하며,

포인터의 경우에는 스마트포인터로 어느정도 방지가 가능하지만,

동적할당의 경우에는 제공되지 않기 때문에, 무조건 해제를 직접 해줘야 한다.



동적할당은 보통 복잡한 데이터구조나 가변적인 크기의 데이터를 관리할 때 사용한다.

당연히 가변적인 데이터를 관리하는 vector와 같은 stl이 동적할당을 사용하게 된다.


동적변수의 특징

  1. 동적할당 시에 메모리 할당을 실패하는 경우도 생기게 된다.
    이는 가변적인 메모리 구조 때문에 일어날 수 있는데, 이러한 상황이 생기지 않도록 예외 처리를 해주어야

프로그램이 동작중에 런타임오류로 종료되어 버리는 불상사를 막을 수 있다.
이는 try catch구문 등을 통해 구현할 수 있다.


#include <iostream>
#include <cstdlib>

int main()
{
    int* dynamicVar = nullptr;
    
    try {
        dynamicVar = new int; // 메모리 할당 시도

        if (dynamicVar == nullptr)
        {
            throw std::bad_alloc(); // 할당 실패 시 예외 던지기
        }

        // 할당이 성공한 경우에 대한 로직 수행
    }
    catch (const std::bad_alloc& e) {
        std::cerr << "메모리 할당 실패: " << e.what() << std::endl;
        // 예외 처리 및 클린업 작업 수행
    }

    delete dynamicVar; // 메모리 해제
    return 0;
}

e.what함수의 경우에는 오류 메세지를 출력하기 때문에, 게임 같은 프로그램의 경우에는 로딩 등으로
우회해서 클린업 작업을 수행하는 등의 구현을 할 수 있다.

  1. 반복적인 동적할당과 해제는 오버헤드가 높기 때문에, 성능에 엄청난 영향을 줄 수 있다.
    그렇기 때문에 최대한 일괄적으로 해제하는 것이 성능 향상에 도움이 된다.

메모리 공간은 한정되어 있기 때문에, 여러 번 할당하고 해제하게 된다면,
큰 메모리를 할당할 공간이 없게되는 메모리 단편화가 생길 수 있다.
이와 같은 문제들을 해결하는 기법으로는 메모리 풀과 오브젝트 풀 등의 기법을 사용할 수 있다.


공통적인 특징

  1. 변수의 메모리 크기는 할당한 크기와 실제 할당된 크기가 다를 수 있다.
    이는 64비트 시스템이 4바이트 정렬을 요구하고 있기 때문인데, 특히 클래스나 구조체의 경우에 이러한 현상이 나타나게 된다.

CPU가 이러한 연산 처리를 하는것을 바이트 패딩 이라고 부른다.
sizeof함수를 사용하거나 메모리의 크기를 알아야 한다면, 디버그를 통해 값을 미리 확인하고 사용하는 것이 현명하다.

  1. 특정한 값을 나타내기 위하여 변수를 사용하는 경우가 있는데, 그 값이 변하지 않고 일정하게 유지된다면,
    공간복잡도를 낮추기 위해 매크로를 사용할 수 있다.
    변수와 달리, 매크로는 컴파일 시간에 코드를 치환하기 때문이다.

마치며

위의 내용들은 초보자가 바로 이해하기에 무리가 있는 내용들이 많다.
나는 이 내용을 다시 정리하기 위해 작성했기에, 불친절하게 느껴질 수 있지만
언급한 키워드를 위주로 공부하고 리서치 한다면 변수에 국한되는 것이 아닌,
방대한 프로그래밍 지식을 습득할 수 있다고 확신한다.

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기