최근 포토로그


C++ 배열의 불편한 진실 복잡한컴퓨터이야기

C++에서 배열은 다른 자료형에 비해서 상당히 이질적인 특성을 가지고 있습니다. 배열의 표기(notation)는 특별할 것이 없으나, 내부적으로는 직간접적으로 포인터와 연관성을 매우 많이 가지고 있고, 배열이 가진 타입 정보도 은연중에 소실 되기 때문입니다.

먼저 배열의 선언과 특성에 대해서 몇 가지 살펴 보려고 합니다.

char a[10];

char b[10][20];

각각은 1차원, 2차원 배열인데, 대괄호 [ ] 를 이용하여 1차원 혹은 다차원을 표기할 수 있는 것은 매우 편리하게 보입니다. 여기서 a, b 라는 변수는 배열 전체를 나타내는 변수이기도 하지만, 그 자체로 배열의 첫번째 요소를 가리키는 포인터의 역할을 수행하기도 합니다.

포인터와의 연관성을 살펴보기 위해서 다음과 같은 재미난 시도를 해볼 수 있습니다.

assert (a[5] == 5[a]);

위와 같은 비교 연산이 성립하는 이유는 a[5]를 통해 배열의 개별 요소에 접근할 때, 직접적으로 a에 대한 포인터 연산을 수행하기 때문입니다. 아래를 보면 이해가 되실 것 같습니다.

a[5] = *(a + 5) = *(5 + a) = 5[a]

다른 관점에서, 위 배열들에 대해서 다음과 같이 코드를 작성하여 변수들의 타입정보와 메모리 크기를 살펴보면,


cout << "a[10] is type of "<< typeid(a).name() << ", sizeof()=" << sizeof(a)<< endl;

cout << "b[10][20] is type of" << typeid(b).name() << ", sizeof()=" <<sizeof(b) << endl;

다음과 같은 결과를 얻을 수 있습니다.


a[10] is type of char [10], sizeof()=10

b[10][20] is type of char [10][20],sizeof()=200


이 결과로부터 ab는그 크기와 타입 정보를 명확하게 가지고 있음을 알 수 있지요.


그런데. 그런데

추가적으로 배열에 대해서 좀 더 살펴보겠습니다. 배열을 함수의 인자로 전달하는 경우는 어떨까요?

int sum(char a[10])

{

           cout<< "a[10] is type of " << typeid(a).name() <<", sizeof()=" << sizeof(a) << endl;

           return0;

}

sum(a);
라고 호출하면 어떤 결과가 출력될까요? 결과는 다음과 같습니다.

a[10] is type of char *, sizeof()=4

앞서 출력한 내용과 그 결과가 완전히 그 다름을 알 수 있습니다.
두가지 면에서 크게 놀랄 수 밖에 없는데요, 먼저
a[10] 는 사라지고  a를 char *로 보고 있다는 것입니다.
C/C++은 만일 함수의 매개변수를 다른 타입으로 정의 하였다면,
call by value(pass by value)의 형태로 값이 전달되었을 것입니다.
그런데 배열을 인자로 전달하는 경우는 다른 타입과는 다르게, call by pointer와 유사한 형태로 인자를 전달하며, 그렇다 보니 배열은 call by value시에 발생하는 복사 과정이 이루어지지 않게 됩니다.
더 나쁜 것은 앞서 보신 것과 같이 sum()의 매개변수를 통해 전달 받은 a는 모든 타입 정보를 소실하고 a를 단순히 char * 로만 인지하며, 그 결과 sizeof(a)의 결과 같이 4가 된다는 사실입니다.
우리가 알게 모르게 배열을 인자로 전달할 때에 배열 그 자체와 더불어 배열 요소의 개수를 같이 전달할 수 밖에 었었던 것은, 이처럼 배열로 선언된 변수를 함수로 전달하면, 가지고 있는 타입 정보를 모두 소실 하기 때문입니다.
이는 배열이 C/C++에서 하나의 독립된 타입으로 인정되지 않고 포인터의 또 다른 표현 기법에 지나지 않기 때문입니다.
이로 인해 다음과 같은 문제가 발생합니다.

i
nt sum(char a[10]) {

for (auto &each : a)    // error !!!

           cout<< each;

}

 

int main() {

char a[10];

for (auto &each : a)            

           cout<< each;

sum(a);

}

다차원 배열에 대해서는 테스트 해보시면 직접 테스트 해보시기 바랍니다.
배열의 이러한 문제점을 최소화 하면서도 장점을 최대화 한 것이 바로 std::vector입니다. 이를 사용하면 타입 안정성을 계속해서 유지할 수 있을 뿐 아니라, 가변 배열의 효과도 같이 얻을 수 있을 뿐 아니라, 위와 같이 range-for에 대해서도 아무런 문제가 없이 사용될 수 있습니다.
물론 의미론적으로 바라 볼 때 배열은 std::array와 좀 더 근거리에 있다고 볼 수도 있습니다.

결론적으로, 큰 메모리 블록을 다용도로 사용하는 경우를 제외하고는 std::vectorstd::array를 사용하는 편이 실보다 득이 훨씬~~ 훨씬 많습니다.


덧글

  • kevin 2014/10/16 09:43 # 삭제 답글

    마지막 예제에서 ^^ "//error!!!" 주석을 잘못된 위치에 달은 것 같습니다. sum 함수에 있는 for 구문에서 오류가 납니다.
  • 김명신 2014/10/17 16:07 # 답글

    kevin 말씀 주신 내용은 수정하였습니다. 제가 너무 서둘러 포스팅을 하느라고 실수를 하였습니다.
댓글 입력 영역


facebook 프로필 위젯

트위터 위젯