신입 게임 개발자의 프로그래밍 일기

[C++ 입문자에서 벗어나기] Chapter_5 포인터와 배열 #1 본문

Language/C++

[C++ 입문자에서 벗어나기] Chapter_5 포인터와 배열 #1

KFGD 2018. 4. 6. 19:09

[C++ 입문자에서 벗어나기]Chapter_5: 포인터와 배열


이번 포스팅의 주제는 포인터와 1차원 배열입니다.

기본적인 내용들은 생략하고 이것들에 대해 흔히 범할 수 있는 실수나 착각들에 대해서 이야기해보려고 합니다.

이번 포스팅에서 다룰 질문들은 아래에 나열된 두가지입니다.


Q1: 포인터와 1차원 배열은 완벽하게 바꾸어 사용할 수 있다?

Q2: int (*pArr)[2]와 int *arr[2]의 차이는? (feat. right-left rule)


[Q1: 포인터와 1차원 배열은 완벽하게 바꾸어 사용할 수 있다?]


소제목에서 추측할 수 있으시겠지만 포인터와 1차원 배열은 대부분의 경우에서 호환이 됩니다.

하지만 이 둘에게는 분명한 차이가 존재합니다.

설명을 위해 예시 코드를 보여드리고 시작하겠습니다.



위 코드는 컴파일을 성공할까요?

예 맞습니다. 성공하는 코드라면 이 코드를 예시로 들지도 않았을 겁니다.

그럼 어느 부분에서 틀린 걸까요? 짐작하실 수 있으신가요?

답은 3번입니다. 여러분의 짐작이 맞았기를 기원하며 설명을 하도록 하겠습니다.

*arr++ 라는 코드는 왜 컴파일 에러를 발생시킬까요?

gcc가 내뱉은 컴파일 에러 이유는 아래와 같습니다.


error: lvalue required as increment operand


increment operand(++)연산자를 사용하려면 l-value를 이용하라는 말인데요.

l-value와 r-value로 설명하기 보다는 포인터와 배열 이름이 가진 목적의 차이로 설명을 해보겠습니다.

여러분도 알다시피 포인터는 특정 메모리의 주소를 저장할 수 있는 변수입니다.

배열은 동일한 타입의 변수들 여러 개를 하나의 연속된 메모리 공간에 저장하는 변수이고 배열 이름은 그 연속된 메모리 공간의 가장 첫 메모리를 가리킵니다.

그리고 위 코드에서 배열 이름을 활용한  *arr++ 은 안되는데 포인터 변수를 활용한 *pArr++ 코드는 동작이 되는 이유는 배열 이름은 변수가 아니라는 데에 있습니다.

중요한 것이니 한 번 더 이야기하겠습니다.

배열 이름은 변수가 아닙니다. 연속된 메모리 공간의 첫 메모리를 가리킬 뿐 값을 저장할 수 있는 변수가 아닙니다.

배열 이름으로는 어떤 값도 저장할 수 있으며 단지 operator []와 함께 사용하기 위함입니다.



[Q2: int (*pArr)[2]와 int *arr[2]의 차이는?(feat. right-left rule)]


2차원 배열을 많이 다루셨거나 혹은 C++ 프로그래밍에 어느정도 익숙해지신 분들에게는 어찌보면 너무나도 쉬운 차이점입니다.

전자는 2차원 배열을 담을 수 있는 포인터 변수이고 후자는 2개의 포인터 변수를 담을 수 있는 배열입니다.

위 두 선언문 코드를 읽는 규칙에 대해서 생각해보신 적이 있으신가요?

이와 같은 규칙을 right-left rule 이라고 합니다.

구글링을 통해 좋은 예시와 설명을 찾았습니다.

원본 글은 아래 링크된 글이며 이 게시글에 적힌 설명은 원본을 바탕으로 좀 더 이해하기 쉽도록 재구성한 것입니다.

<원본: From C Programming Guide, Jack Purdum, Que Corp. 1983>


C/C++에서 복잡한 자료형을 읽는 순서는 아래와 같은 규칙들을 적용합니다.

1. 먼저 선언문에서 변수의 이름(식별자, identifier)를 찾습니다.

2. 이름을 찾았다면 우선 이름의 우측에 위치한 심볼들이 있는 지를 탐색합니다.

3. 탐색 도중 " ) " 심볼을 확인한다면 우측 탐색을 현재 지점에서 멈추고 이름의 좌측 탐색을 시작합니다. 좌측 탐색 중 " ( " 심벌이 확인 된다면 그 지점에서 좌측 탐색을 멈추고 멈추었던 우측 탐색을 다시 진행합니다.

4. 선언문의 우측 끝에 도달하였다면 이제 이름의 좌측 혹은 좌측 탐색을 멈추었던 지점부터 다시 심볼 탐색을 진행합니다.

5. 좌측 탐색 도중 선언문의 타입 부분에 도달하면 탐색을 종료합니다.


심볼들을 찾았다면 이것들을 이제 영어 문장들로 번역해야 합니다.

이것에 대한 규칙은 아래와 같습니다.


Symbol    -    English keyword

()      -    function that returns

[n]    -    Array of n

    [n][m] -    Array of n arrays of m

*      -    pointer to


그럼 이것들을 활용하여 int (*pArr)[2]와 int *arr[2]이 다을 수 있는 타입을 확인해보겠습니다.

먼저 int (*pArr)[2] 부터입니다.


1. 변수의 이름을 찾았습니다.

=>의미: pArr is

2. " ) " 을 만났으니 좌측을 탐색하였고 탐색 중에  " * "을 만났습니다.

=>의미: pArr is a pointer to

3. " ( " 을 만났으니 좌측 탐색을 멈추고 다시 우측 탐색을 시작하였으며 " [2] "를 만났습니다.

=>의미: pArr is a pointer to an array of 2

4. 우측 끝에 도달하였으니 좌측 탐색을 재개하였고 타입 " int "를 만나 탐색을 종료하였습니다.

=>의미: pArr is a pointer to an array of 2 integers. 

    번역: pArr은 두개의 int 타입 변수들을 담을 수 있는 배열을 가리키는 포인터.


그다음으로 넘어갑시다.

이번에는 int *arr[2] 입니다.


1. 변수의 이름을 찾았습니다.

=>의미: arr is

2. 우측 탐색 중 " [2] " 를 만났습니다.

=>의미: arr is an array of 2

3. 우측 끝에 도달하였으니 좌측 탐색을 시작하였고 " * "를 만났습니다.

=>의미: arr is an array of 2 pointer to

4. 좌측 탐색 중 타입 " int "를 만나 탐색을 종료하였습니다.

=>의미: arr is an array of 2 pointers to integer.

    번역: arr은 int 타입을 가리키는 포인터 2개를 담을 수 있는 배열.


(글쓴이는 영어를 못합니다..)

얼추 원하는 의미가 만들어진 것 같습니다.

그럼, 이것으로 이번 포스팅을 마치도록 하겠습니다.


추가적으로 구글 검색 중 영어 키워드가 아닌 한글 방식으로 풀어쓴 right-left rule을 설명한 블로그 게시글을 발견하였는데 

해당 게시글의 링크를 알려드리겠습니다.

링크: c 언어 복잡한 자료형 읽기(가칭 : 왼쪽 오른쪽 규칙. right left rule 의 변형)


Comments