[C++] array to pointer decay

C++은 그 근본이 C에서 나왔기 때문에 C에서 많은 문제점을 물려받았다. 대표적으로 implicit conversion이 그런데, 그중에서 array가 pointer로 implicit conversion 되는 것을 decay. 혹은 다른 decay들과 구분하기 위해 array to pointer decay라고 부른다. 이는 말 그대로 T[N] 타입으로 선언된 변수의 값이 T* 타입의 변수에 바인딩 될 때 implicit casting이 되는 것을 말한다. 이는 컴파일 타임 정보인 배열의 크기를 잃는다는 문제도 있지만, 그보다 더 큰 문제는 포인터에 대해서 upcasting 또한 implicit conversion이 가능하다는 데서 생긴다.

위와 같은 코드가 있다고 해보자. A를 상속받은 B 클래스가 있고, B instance의 크기는 A instance의 크기보다 크다(정확히 따지자면 사용하는 alignment의 크기가 B 클래스보다 작은지 따져봐야 하지만, 여기서는 설명을 간단히 하기 위해 int 크기로 align 된다고 가정하고 진행하도록 하겠다). 이때 f 함수가 하는 일은 다음과 같다.

  1. B(3, 4)를 생성한다.
  2. 1에서 생성한 객체의 A 부분만 slice 한 A(3)을 인자로 받은 a 위치에 넣는다.
  3. B(5, 6)를 생성한다.
  4. 3에서 생성한 객체의 A 부분만 slice 한 A(5)를 인자로 받은 a에 1 더한 주소에 저장한다.

앞에서 말했듯이 위의 코드는 일단 포인터는 실제 배열의 크기를 알지 못하기 때문에 f 함수가 받은 a의 길이가 2 이상인지 알 수 없다는 문제가 있다. 하지만 이보다 더 큰 문제가 있다. 만약 f 함수를 아래와 같이 사용했다고 해보자.

B[4] 타입인 aB* 타입으로 decay 되고, B*는 부모 타입인 A*로 upcasting 되어 f 함수에 넘어간다.

f 함수를 실행한 뒤 사용자가 원했던 모습은 아마 위와 같은 모습일 것이다. 혹은 object slice가 일어난다는 것을 생각하지 않고 아래와 같은 결과를 생각했을 수도 있다.

하지만 실제 실행한 결과는 다음과 같은 값을 가지게 된다.

이는 실제 정의된 메모리 레이아웃과 f 함수가 해석한 메모리 레이아웃이 다르기 때문이다.

실제 레이아웃
f가 해석한 레이아웃

우리는 분명히 B의 배열을 선언하였다. 따라서 실제 메모리에는 B의 인스턴스가 연속으로 존재한다. 하지만 f 함수가 인자로 받은 타입은 A의 포인터 타입이기 때문에 이를 B가 4개 존재하는 메모리가 아닌 A가 8개 존재하는 메모리로 해석하게 된 것이다. 따라서 a[1] = B(5, 6)를 실행하였을 때 2번째 B가 있는 메모리가 아닌 첫 번째 Bint b에 해당하는 영역에 두 번째 A 인스턴스가 존재한다고 생각하고 A(5)를 할당하게 된 것이다.

이 문제를 해결하는 방법은 크게 2가지 있다. 첫 번째 방법은 함수를 정의할 때 배열을 넘기기 위해서 포인터를 사용하지 않는 것이다. 이는 함수에 넘겼을 때 배열의 크기라는 정보를 잃지 않기 위해서이기도 한데 C++ Core Guidelines에서 추천하는 방법이기도 하다.

두 번째 방법은 배열을 선언할 때 C 스타일의 []가 아닌 std::array를 사용하는 것이다. std::array는 C++ 11에 추가된 배열을 표현하기 위한 타입으로, C 스타일 배열과 다르게 이터레이터와 begin, end 등이 정의돼있어서 다른 표준 컨테이너들과 같은 코드를 공유할 수 있다는 장점이 있다. 하지만 여기서는 코드를 재사용할 수 있다는 것보다 이는 템플릿 클래스이고 따라서 AB의 부모일 때, std::array<A, N>std::array<B, N>의 부모가 아니라는 특성이 더 중요하다. 따라서 template<int N> void f(std::array<A, N>& a)라는 함수를 호출할 때 인자로 Bstd::array를 넘길 수 없기 때문에 위와 같은 문제가 발생하지 않는다.

댓글

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

RAII는 무엇인가

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API