정적 배열

Date:     Updated:

카테고리:

배열이 뭘까

  • 앞서 포스팅했던 변수라는 개념은 지정된 데이터의 타입을 보관하는 바구니와 같다고 언급했다. 배열은 변수와 마찬가지로 데이터를 보관하는 바구니이지만, 바구니가 연속되어 위치하는 수납함과 같다고 보면 된다. 변수와 동일하게 정확한 메모리 주소도 할당되면서 연속적인 메모리 주소를 가지게 된다. 아래의 내용에서 본격적으로 배열이 어떤 구조를 가지고 있는지 확인해보자.

배열 선언하기

  • 배열은 가장 기초적인 자료구조 중 하나이다. 정수 타입의 인덱스(Index)값(Value)로 이뤄지며, 인덱스를 사용해 각 원소(값)에 접근이 가능한 구조로 이뤄진다. 다만 언어별로 배열의 개념은 약간의 차이가 있을 수 있는데, 대표적으로 C, C++, C#은 아래와 같은 차이가 있다.

C, C++

int main()
{
    // C, C++의 정적 배열은 스택에 할당된다
    // 즉, 값 타입이다

// 1차원 배열
    // 초기화 하지 않는다면 '아무 의미 없는 쓰레기값'이 들어간다.
    int intArray[5];
    // 초기화를 한다면 각 배열의 원소에 지정한 값이 할당된다.
    float floatArray[3] = { 1.5f, 0.86f, 9.0f };

// 2차원 배열
    char charArray[2][3] = { {'a', 'b', 'c'}, {'d', 'e', 'f'} };
}

C#

class Program
{
    static void Main(string[] args)
    {
        // C#은 배열의 값은 힙에 할당되고, 스택에는 힙의 주소를 참조하는 값이 할당된다
        // 즉 참조타입이다.

    // 1차원 배열
        // new 할당과 동시에 배열의 크기를 지정한다.
        int[] intArray = new int[5];

        // 초기화를 한다면
        float[] floatArray = new float[3] { 1.5f, 0.86f, 9.0f };

    // 2차원 배열
        char[,] charArray = new char[2, 3] { { 'a', 'b', 'c' }, { 'd', 'e', 'f' } };
    }
}

원소에 접근하기

  • 위에서 배열은 정수타입의 인덱스를 사용해 배열의 각 원소에 접근 가능하다는 것을 언급했다. 배열의 선언 및 초기화 후, 다음과 같이 배열의 원소에 접근할 수 있다.
int main()
{
    int Array[5] = { 100, 200, 300, 400, 500 };

    for (int i = 0; i < 5; ++i)
    {
        // 정수형의 인덱스를 통해 각 배열의 원소에 접근한다.
        std::cout << Array[i] << std::endl;
    }
}
  • 결과 :
    • Array_01
  • 예상과 마찬가지로 배열의 원소로 초기화 한 100부터 500까지 출력되었다. 어떻게 정수형의 인덱스를 사용해 배열의 원소에 접근할 수 있는 것일까.

어떻게 인덱스 접근이 가능할까

  • 답은 배열이 연속된 메모리 공간에 할당된다는 점이다. 아래는 int타입과 long long타입으로 배열을 만들고 참조자를 통해 각 배열의 메모리 주소를 콘솔에 출력하는 코드이다.
int main()
{
    int intArray[5];
    long long longlongArray[5];

    std::cout << "int 타입 Array 메모리 주소" << std::endl;
    for (int i = 0; i < 5; ++i)
    {
        std::cout << &intArray[i] << std::endl;
    }

    std::cout << "\nlonglong 타입 Array 메모리 주소" << std::endl;
    for (int i = 0; i < 5; ++i)
    {
        std::cout << &longlongArray[i] << std::endl;
    }
}
  • 결과 :
    • Array_02
  • intlong long의 데이터 타입의 크기는 각 4 Byte, 8 Byte이다, 각 메모리 주소는 16진수로 표기하며, 위의 결과에서는 int타입의 배열은 4바이트 간격으로 메모리 주소가 할당되어 있고, long long타입은 8바이트씩 메모리 주소가 할당되어 있는 것을 확인할 수 있다.

배열은 어디에 활용될 수 있는가

  • 배열은 프로그래밍에서는 상당히 기초적이고 많은 곳에 활용되는 개념이다. 물론 요구사항이나 활용방안에 따라 다른 자료구조를 사용하는 경우도 있지만, 가장 직관적이기 때문에 대부분 특정 데이터 무리의 컨테이너 용도로 자주 사용된다. 위의 예시 코드들에서 확인할 수 있듯이 반복문을 사용한 배열의 순회는 상당히 자주 사용되는 방법이다. 그도 그럴 것이 배열은 공통된 데이터 타입들을 여러개 담아 놓을 수 있는 수납함과 같다. 여기서 원하는 위치의 데이터를 찾거나, 추가하거나, 삭제하거나 하는 행위들을 할 수 있어야 하기 때문이다. 반드시 배열의 개념과 더불어 반복문에 익숙해지기 바란다.

주의점

범위를 넘어선 접근

  • 정적 배열은 선언과 동시에 크기를 지정하게 된다. 여기서 만약. 크기를 넘어선 인덱스에 접근한다면 어떤 일이 일어나는지 확인해보자. 예시 코드에서는 C++C#에 대해서 다뤄보겠다.

C++ 예시 코드

int main()
{
    int intArray[3] = { 10, 20, 30 };

    for (int i = 0; i < 5; ++i)
    {
        std::cout << i << "번째 값 = " << intArray[i] << std::endl;
    }

    std::cout << "출력 끝!" << std::endl;
}
  • 결과 :
    • Array_03

C# 예시 코드

class Program
{
    static void Main(string[] args)
    {
        int[] intArray = new int[3] { 10, 20, 30 };

        for(int i = 0; i < 5; i++) 
        {
            Console.WriteLine($"{i}번째 값 = {intArray[i]}");
        }

        Console.WriteLine("출력 끝!");
    }
}
  • 결과 :
    • Array_04
  • C++은 할당되지 않은 인덱스에 접근했기 때문에 쓰레기값이 나오긴 하지만 결국 프로그램은 돌아가긴 돌아갔다.
    헌데 C#은 어떤가. 아예 프로그램이 터져버리면서 마지막 “출력 끝!”이라는 텍스트 조차 출력하지 못 했다. 조그마한 실수좀 했다고 프로그램 전체가 터진다는게 가당키나 한 얘기인가.

그러나..

차라리 터지는게 낫다.

  • 위의 예시들을 보면 어떤가, C++은 뭔가 잘못되었더라도 프로그램이 일단 돌아가긴 했다. 이 돌아가긴 했다가 문제인데, 물론..터진다면 정신적으로 슬퍼지지만 C#에서는 프로그램이 터지면 어디가 문제인지, 어떤 문제인지 정확하게 피드백을 제공했다. 마치 프로그램이 터진다는게 더 문제인 것 같지만, 오히려 선녀다.
    프로그램이 터졌다는 것은 의도하지 않은 결과를 방지할 수 있다는 점인데, 누가 봐도 이상이 있으니 코드를 다시 살펴보면 되는 문제다. 그런데 C++은 다르다. 일단 돌아가긴 했다. 의도하지 않은 결과가 나왔을 때, 최악의 경우 상당히 많은 양의 코드를 직접 살펴보며 문제를 파악해야 할 수도 있다는 뜻이다. 물론 숙련된 프로그래머라면 웬만해선 이런 문제를 일으키지 않는다 원활한 테스트를 위한 준비도 해놨겠지만, 초보자의 입장에서는 그냥 대놓고 프로그램을 터트려주는게 차라리 낫다는 말이다.

댓글 남기기