C언어 배열에 대한 이해(1)

C언어의 기본문법을 체계적으로 정리할 생각은 아니었는데, 글도 점점 쌓여가고, 앞으로도 이런식이면 뒤죽박죽이 될듯싶어서 이해의 흐름 순서대로 정리해 나갈까합니다. 우선 그 유명했던 포인터에 대한 정리는 했으니, 이번엔 포인터의 연산이 많이 쓰이는 배열에 대한 정리를 할까합니다. 기존에도 배열에 관한 정리글을 올려놨는데, 중요한 핵심만 적어 놨습니다.

이번 글에선 그 핵심을 설명하고 포인터가 어떤식으로 작용하는지, 구체적인 예를 들어 설명해 볼까합니다.

배열을 단순하게 일반적인 변수에 첨자를 집어넣는 것으로 생각하면 많은 실수를 할 수 있습니다.

단순한 듯 보이는 배열녀석은 생각보다 다양한 속성을 가지고 있습니다.

어짜피 C언어의 전체를 훑으실거라면 적어도 배열과 포인터만은 확실히 하시길 바랍니다

배열과 포인터부분을 확실하게 이해한다면 구조체뿐만아니라 나머지 문법들을 더 빠르고 쉽게 이해할 수 있습니다.



배열은 어떤 특정 타입을 메모리상에 일렬로 늘어트린 것입니다.

그래서 배열의 선언시 타입, 초기 주소값, 요소의 개수를 알아야 형태가 정해집니다.

단순하게 일반 변수의 선언에서 요소의 개수만 추가된 것입니다.

메모리상에 데이터 공간인 변수와 배열이 형성되는 그림입니다.


int형 변수 a는 주소공간 자체 상징합니다.

배열의 선언은 []사이에 원하는 요소의 개수를 넣어서 선언합니다. 위그림은 int형 요소의 개수가 3으로 배열의 크기는 12byte가 됩니다. 여기서 ar과 []를 따로 떨어뜨려서 생각할 필요가 있습니다.

배열의 특징중 제일 중요한 것이 ar(배열명)은 포인터가 됩니다.

위 그림처럼 ar은 포인터변수로서 배열의 첫머리를 가리킵니다.

그리고 이 특징은 그냥 외워두시는 게 편리한데 배열명은 sizeof 연산자의 피연산자가 될때 배열 전체를 상징하기도합니다. 그래서 사이즈가 12라는 값이 나옵니다.

이렇게 배열명은 포인터로서, 이 배열명을 가지고 배열의 요소에 포인터 연산을 통해 접근할 수가 있는 것 입니다.


그럼 []에 대해서 말하자면 선언시에는 []안에 총 요소의 개수를 써서 선언을 합니다.

위 같은경우 3으로 선언했는데 요소가 총 3개면, 각 요소는 0번째 요소,1번째 요소, 2번째요소, 이렇게 3개가 됩니다.

0부터 시작하는 이유는 포인터연산을 통해 ar(배열명)이 가리키는 첫번째 요소는  *(ar+0) 이 되기 때문이고, 이 표현을 가독성을 위해서 ar[0] 이라고 약속을 합니다. 결국 배열은 포인터의 연장선 상에 있는 것입니다. 


=은 대입연산자가아니고 같은 표현이라는 뜻입니다.


이렇게 숨겨진 속뜻은 계속 드러납니다.

배열의 선언시에 a[3]은 단순히 선언을 위한 약속입니다.


int ab[5]={1,2,3,4,5};


배열의 초기화는 위와 같이 하는데, 배열의 선언시에 첨자는 배열요소전체의 개수를 나타냅니다. 5번째 요소를 지칭하는 것이아닌 요소가 5개 있음을 뜻하며, 요소의 시작은 0번째부터 시작하기 때문에 5번째 요소는 없습니다.

마직막 요소를 는 ar[4]이고 사람이 생각하기엔 5번째 요소지만 컴퓨터는 0번을 첫번째로 생각하여 4번째 요소가 되는 것입니다.

0번째요소 부터 시작되는 원리는 포인터를 통한 연산을 위해서 라고 생각하고 이해하시기 바랍니다.


위 선언에서 노란 땡글이녀석 생략될 수 있는데 배열의 크기를 나타냅니다.

생략된 경우 우변의 선언에 맞춰서 자동으로 공간이 할당됩니다.


위에서 배열명은 포인터라고 했습니다. 여기서 더 알아두셔야 할것이 있는데 배열명은 단순한 포인터가 아닌 포인터 상수입니다.

포인터 상수의 특징은 제가 쓴 const에 대한 글을 읽어 보시길 권합니다.


2014/01/14 - [프로그래밍/C언어] - 상수포인터, 포인터상수, const


포인터상수기 때문에 여러가지 특징들이 나타납니다. 우선 포인터 상수는 가리키는 주소값을 바꿀 수 없기 때문에, 한번 선언된 공간만 가리킵니다. 예를들어 힙영역으로 동적할당을 하고 싶은데, 배열을 동적할당을 하고 싶다면 에러가 발생할 겁니다.

배열은 동적할당이 불가합니다. 동적할당에 대해서는 제가 조만간 다시 글을 올리겠습니다.

(참고로 C++에서는 new 연산자로 선언과 동시에 배열의 동적할당이 가능합니다.)


2014/02/11 - [프로그래밍/C언어] - 값에 의한 호출(call by value)과 참조에 의한 호출(call by reference)에 대한 이해 C언어


배열의 자세한 기본 특성들은 다음글을 참조하시길 바랍니다.


2013/05/25 - [프로그래밍/C언어] - 배열의 특징 C언어


각각의 요소들은 개별적으로 존재하고 일반적인 변수로서 사용이 가능합니다. 즉, 각 요소의 값을 바꿀 수 있습니다.

하지만 배열이 선언과 동시에 초기화를 하던, 초기화를 안하고 단지 배열만 선언을 하던, 한번 선언된 배열은 초기화식을 사용하여 초기화할 수는 없습니다. 이유는 배열의 초기화라는 것은 일반적인 변수의 초기화와 달리 대입연산자가 아니기 때문입니다. 배열선언시 첨자는 요소개수를 나타내기 때문에 요소의 번지수를 상징하는 것이 아닐 뿐더러, 저 형식은 단지 초기화를 위한 약속이기때문에, 처음 선언후에 초기화를 하지않는다면, 배열의 값을 바꾸는 방법은 요소 하나씩 바꾸는 방법 뿐입니다.


그럼 가장 핵심이 되는 특징인 배열명은 포인터라는 특징을 통해 배열안에서의 포인터 연산을 연습해보겠습니다.


배열의 각 요소에 배열명을 통해 접근 하는 코드를 한번 작성해 보겠습니다.



배열명은 포인터로서 배열의 시작점 즉 0번째 요소를 가리킵니다. 

ar[0],ar[1],ar[2],ar[3],ar[4]과 같은 표현은  *(ar+0),*(ar+1),*(ar+2),*(ar+3),*(ar+4) 입니다.


결과

두가지 표현이 같은 표현이고 우리는 편의상 가독성 좋게 ar[요소]로 표현을 하여 사용합니다.

결국 다시강조하지만 배열은 포인터 연산의 연장선에 있는 구조입니다.

조만간 구조체가 나오는데 구조체도 배열과 다를 것이 없습니다. 단지 배열은 한가지 타입을 길게 늘어놓은 자료구조면, 구조체는 다양한 타입들이 섞여서 일렬로 늘어트린 겁니다.

배열은 타입이 동등하기 때문에 몇번째인지만 알면 단순히 배열명에 포인터에 요소번호를 더해서 각 요소를 쉽게 찾을 수 있지만, 구조체는 요소들의 타입들이 다양할 수 있기 때문에, 다른 형식을 취합니다. 형식은 다르지만 내부적으로 보면 포인터가 구조체의 시작점을 가리키고 각 멤버들의 offset(변위)을 통해 접근하기때문에, 이 또한 포인터 연산입니다. '.'연산과'->'연산이 나오는데 그것은 조만간 구조체에 대해서 이야기할때 소개해 드리겠습니다.

이 댓글을 비밀 댓글로