★★★★★★★★★★★오류 글시작★★★★★★★★★★★★★★★★★
포인터와 배열 차이점
(Pointer & Array)
(1)
C/C++에서 제일 어렵게 여기지는 것이 바로 포인터일 것이다. 포인터를 어려워
하는
이유는 여러가지가 있겠으나 C/C++에서 일관성이 결여된 느낌을 주는 몇몇
문법으로 인하여 더욱 헛갈리고 어려운 것이 아닌가 생각이 든다.
또한 C/C++은 배열이 포인터와 호환되는 특성이 있다. 그래서 포인터를 배열처럼
혹은 배열을 포인터처럼 사용하기도 하는데, 그
특성이 조금은 서로 달라서 가끔
치명적인 문제를 일으키기도 한다. 이번 글에서는 포인터와 배열의 특징과 차이에
대해서 심도있게
살펴보겠다.
포인터는 배열일까?
아래의
코드를 보고 v1 값이 무엇인지 생각해보자!
int arr[2] = {0, 1};
int* pArr = (int*)arr;
int v1 = pArr[1];
자! v1 값은 무엇일까? 너무 쉽다고? 처음엔 일부러 쉬운 문제를 내봤다. 그래야 계속
보니까...
이것을 보면서 무엇인가 느껴져야 한다. '아! 배열과 포인터는 비슷하구나!'
맞다! 사실 상당히 비슷하다. 쓰는 방식도 비슷하고, 초보 개발자라면 그냥 배열은
포인터의 또다른 표현법 정도로 생각해도 큰 무리는 없을 것이다.
그러나 세상사가 이것처럼 간단하지는 않다. 좀 더 복잡하고 까다로운 것이 나오기
시작하며 가끔씩 삑사리가 날 때는 초보개발자들은 밤샘 지옥을 경험해야 한다.
우리가 항상 1차원 배열만 사용하는 것은 아니다. 그랬으면 얼마나 좋겠나?
가끔은 2차원 이상 다차원 배열도 사용해야만 한다.
다시 코드를 보자!
int arr[2][3] = {{0, 1, 2}, {10, 11, 12}};
int** ppArr = (int**)arr;
int v12 = ppArr[1][2];
자! v12 값은 무엇일까? 음... 12? 12라면 얼마나 행복하겠나! 이것이 정말 12였다면
이 세상은 아마 C/C++ 개발자들로 넘쳐날 것이다. 어쩌면 한국사람이 한국말을 기본으로
하듯이 개발자들은 C/C++은 기본 소양으로 마스터하고 있을 것이다.
이번 글은 왜 v12가 12가 아니고 실행중에 왜 삑사리(Memory Exception)가 나는지에
대한 이야기이다. 그리고 이것을 제대로 이해하기 위해서는 C/C++이 어떻게 메모리를
관리하고 있는지 포인터와 배열은 어떤 개념을 가지고 동작하는지를 알아야만 한다.
1. 원시형 변수명은 값으로서 사용된다.
------------------------------------------------------------------------
원시형이란 int, int*, double, double* 등등 기본적으로 제공되는 타입을 말한다.
이런 타입으로 변수를 선언할 경우 해당 변수명은 VALUE로서 사용된다.
int X = 3; // X가 l-value로서 사용
int Y = X; // X가 r-value로서 사용
------------------------------------------------------------------------
int v = 1;
위의 의미는 무엇일까? '뭐야? 뻔한거잖아!' 사실 쉬워도 너무 쉽다. 그러나 C/C++
개발자라면 정확한 의미와 구조를 이해하고 있어야 한다.
일단 가정을 몇가지 하자! 편의상 메모리의 크기는 1KB(1024Byte)이다.
그리고 32Bit x86 CPU를 사용한다.
그림을 보자!

이 블럭을 쉽게 표현하는 좋은 방법이 있을까? 일단 블럭에 이름을 부여하자!
블럭의 이름은 v 이다. 그리고 블럭은 메모리에서 주소가 1000에서
시작하며 크기는 4Byte이다. 이 4Byte의 블럭에는 어떤 값을 쓸 수도 있고
현재 있는 값을 읽을 수도 있다. 이 블럭에서 값을 읽고 쓸 수 있는 저장소를
나타내는 것을 VALUE 라고 부르자! 그렇다면 현재 이 블럭의 VALUE는 1을
담고 있는 것이 된다.
* 메모리블럭 자체가 VALUE와 무슨 차이가 있는지 의문을 가질 수 있을 것이다.
사실 같은 의미이지만 메모리블럭은 개념적으로 블럭의 전체적인 대표성을
나타내며 VALUE는 값을 단순히 저장하는 저장소라는 하나의 속성쯤으로 이해하면
될 듯 싶다. VALUE는 엄밀히 말하면 프로그래밍에서 말하는 l-value, r-value를
나타낸다. 따라서 보통 등호의 왼쪽에서 l-value로 오른쪽에서 r-value로 사용된다.
VALUE = 1; 은 VALUE 저장소에 1을 쓰라는 의미이며
int x = VALUE; 는 VALUE 저장소에 들어있는 값을 가져오라는 의미이다.
이렇게 하면 블럭을 구성하는 요소가 어느정도 정해진다.
바로 [NAME, ADDRESS, SIZE, VALUE] 가 된다.
그렇다면 위의 식은 [v, 1000, 4, VALUE = 1] 정도로 표현될 수 있다.
C/C++은 메모리블럭의 속성을 좀더 쉽게 다루기 위해서 몇몇 연산자를
정의해 놓았다. 물론 그것이 아쉽게도 직관적이지는 못하다.
일단 v는 블럭을 대표하는 이름이다. 그러면서 v는 블럭의 VALUE로서 동작한다.
int v = 2; // (1)
int w = v; // (2)
(1)에서 v는 VALUE로 쓰이고 VALUE에 2를 쓰게 된다.
(2)에서 v와 w는 모두 VALUE로 쓰이고 w의 VALUE에 v의 VALUE를 쓰게 된다.
즉, 위의 예에서 v와 w는 각각 4Byte 크기의 메모리블럭을 대표하는 이름이면서
각 메모리블럭의 VALUE의 개념으로 쓰이고 있다.
그렇다면 블럭의 주소와 크기는 어떻게 표현될까? C/C++에서는 &NAME, sizeof(NAME)
형식으로 주소와 크기를 표현한다. 즉, & 연산자를 블럭의 이름 앞에 붙이면
블럭의 주소를 나타내고, sizeof 에 이름을 넣으면 블럭의 크기를 알려주는 것이다.
여기까지는 C/C++ 책 어떤 것을 보아도 잘 설명되어 있을 것이다.
잘 이해가 안가면 아무 책이나 잡고 먼저 공부를 해야한다. 책 서너권을 읽었음에도
도저히 모르겠다면 이 분야는 적성에 맞지 않는 것일 수 있다. 심각하게 고민해야 한다.
2. 배열명은 주소로서 사용된다.
-------------------------------------------------------------------------
배열이란 원시형 변수가 하나이상 일렬로 이루어진 것을 말한다. 이때 배열명은
일렬로 이루어진 변수 묶음의 가장 첫 시작주소를 의미한다. 따라서 배열명은
값이 대입되거나 값을 빼올 수 있는 변수가 아니므로 배열명은 말 그대로 배열명일뿐
배열 변수라고 해서는 안된다.
int arr[2] = {0};
arr = {1, 2}; // Compile Error
int XArr[2] = arr; // Compile Error
배열명은 배열의 주소를 나타내므로 주소연산자 &를 사용하게 될 경우
[배열명 == &배열명] 이 성립한다.
-------------------------------------------------------------------------
이제 배열을 살펴보자! 이것은 어떻게 분석해야 할까?
int arr[2] = {1, 2};
이 식을 그림으로 표현해 보자!

위에서 제시된 방식으로 메모리블럭을 표현해보자! 블럭의 이름은 arr이다.
주소는 1000이고, 크기는 8Byte 이다. 그런데 arr의 VALUE를 표현하기가 꽤 애매하다.
8Byte 정도야 VALUE를 표현할 수 있지만, 만약 int arr[1000]; 이었다면
과연 VALUE를 표현할 수 있을까? 또한 블럭의 크기는 8이라고 했는데, 8은 1Byte가
8개 있는 것인지, 4Byte가 2개 있는 것인지 구분이 안간다.
따라서 배열을 표현하기 위해서는 새로운 표현 방식이 필요하다.
[NAME, ADDRESS, BASIC SIZE, <1d Count, 2d Count, ... , Nd Count>] 형태로
표현해보자! 그렇다면 위의 블럭은 [arr, 1000, 4, <2>)로 표현될 수 있다.
* BASIC SIZE는 메모리블럭을 구성하는 원시타입의 메모리블럭의 크기를 의미한다.
arr 배열이 결국 int 블럭 두개로 이루어져 있으므로 BASIC SIZE는 4가
된다.
* <1d Count, 2d Count, ..., Nd Count> 이것은 무엇을 의미하는가?
d는 Dimension 으로 차원을 나타낸다. 즉, 1차원 배열 arr[3]일 경우 <3> 이 되고
3차원 배열 arr[2][3][4]일 경우 <2,3,4> 로 표현될 수 있다.
C/C++은 배열 메모리블럭의 속성을 쉽게 다루기 위해서 몇몇 연산자를 정의해
놓았다. 그러나 배열의 경우 위에서 설명한 int v; 와 같은 블럭 속성과는 꽤 다른
점이 있다. 어떻게 보면 상당히 일관성이 떨어질 수도 있으나 그 차이점은
반드시 알아야만 하는 사항이다. 그럼 그 차이가 무엇인지 살펴보자!
arr은 블럭의 이름이지만, arr이 블럭의 VALUE로서 사용되는 것은 아니다.
대신 arr은 블럭의 주소를 나타낸다. 따라서 배열 메모리블럭에서는 블럭의 이름이
블럭의 주소로서 사용될 수 있다. arr이 블럭의 VALUE가 아니기 때문에
arr = {1, 2}; 와 같이 VALUE처럼 사용하면 바로 컴파일 에러가 나게 된다.
위에서 얘기했지만 &Name은 블럭의 주소를 나타낸다고 했다.
배열에서 arr은 이름으로 쓰이면서 주소를 나타내기도 한다. 마찬가지로
&arr 또한 블럭의 주소를 나타낸다. 즉, 개념적으로 받아들이기 어렵겠으나
arr과 &arr이 같은 주소값을 나타내게 된다. 이런 점은 C/C++에서 한편으로는
일관적인 모습을 보이지만, 또다른 면에서는 모순적인 모습을 보여주는 것이다.
그러나 일단 받아들이도록 하자! (그림에서 arr과 &arr이 같다는 것을 볼 수
있다.)
* 이런 경우는 함수포인터에서도 비슷하게 나타난다. 다음에 함수포인터에
대해서 글을 올리면 확인하기 바란다.
* 참고로 int arr[2] = {1, 2}; 는 컴파일이 아주 잘 된다. 이것은 특이한 경우로
arr이 VALUE로서 사용되는 것이 아니라 arr이 생성되면서 초기화되는 것이다.
일반적으로 C/C++ 에서는 등호(=)가 대입연산자로서 사용도 되지만 초기화 역할을
하기도 한다. C/C++의 이런 예외적인 경우가 혼란스럽겠으나 잘 이해하자!
이것도 다음에 대입연산자를
다룰 때 자세히 설명하도록 하겠다.
다시 위의 그림을 살펴보자!
arr[0]과 arr[1]은 각각 메모리블럭의 4Byte 공간을 나타내고 있다.
여기서 arr[0] 과 arr[1]은 가장 처음 그림 v처럼 메모리블럭의 이름이자,
VALUE로서 사용된다. 즉, arr[0] = 1; 이나 int x = arr[0]; 처럼 사용될 수 있다.
여기까지는 1차원
배열이었다. 이제 2차원 배열로 넘어가 보자!
int arr[2][3] = {{0, 1, 2}, {10, 11, 12}};
위의 식은 아래 그림처럼
표현된다.

|
위 식은 |
http://ideone.com/dggA3D
178.162.*.* // 그 링크 코드가 저거랑 먼상관이야. 난 저부분에서 arr과 &arr;이 같다 라는 부분이 틀렸다고 말한건데. 저세끼 글 존나 신빙성있게 길게썻는데 결국 다 틀렸음 저거 하나때문에 ㅇㅇ
맨 위의 댓글 사이트의 코드를 수정해서 돌려보면 arr과 &arr;가 결과가 똑같은데 ;;; 평소에 c언어 할 때 배열명가지고만 해서 모르겠다.뭐가 옳은지