Q : 왜 C++에는 가변길이 배열이 없나요?
https://stackoverflow.com/questions/1887097/why-arent-variable-length-arrays-part-of-the-c-standard
부족하게나마 직접 번역을 한 글입니다.
막히는 부분마다 큰 도움을 주신 파파고님, 구글님, Yun님에게 이 글을 헌정합니다.
Q : 저는 C를 그리 많이 써보지는 않았습니다만, 올라오는 질문글들을 읽다가 낯선 스타일의 C 구문을 몇개 보았어요.
보아하니 C99에서는 이런 문장이 유효한듯합니다.
void foo(int n)
{
int values[n];
//가변길이 배열 선언
}
이건 꽤 유용한 기능 같아 보이는데요.
그럴듯한 이유를 꼽자면 이런 것들이 있겠네요.
- 컴파일러 벤더가 표준의 다른 부분과 호환되지 않는 기능을 구현하기 어렵거나,
- 다른 C ++ 구조로 이 기능을 평가할 수 있기 때문이라는 가설입니다.
C++ 표준은 배열의 사이즈는 반드시 상수 표현식이 되어야 한다고 명시합니다. (8.3.4.1).
맞아요. 물론 std::vector를 쓰면 되죠. 하지만 이건 힙 메모리에 할당을 하는거지, 스택을 쓰는 건 아닙니다.
그리고 원한다면 이렇게 다차원의 배열을 쓸 수도 있죠.
void foo(int x, int y, int z)
{
int values[x][y][z];
// 가변길이 배열 선언.
}
이에 비해 벡터 버전은 좀 어색한 면이 있습니다.
void foo(int x, int y, int z)
{
vector< vector< vector
( /* 진짜 끔찍한 표현식이 여기에 옵니다.*/);
}
그리고 이건 벡터의 특성상 슬라이스들, 그리고 행과 열이 온갖 메모리에 분산될 수가 있습니다.
comp.std.c++에서 이 문제에 대한 것을 보니, 논쟁의 양쪽에 전부 유명인사들이 있어서 논란이 상당히 큰 것 같더군요.
std::vector가 항상 좋은 해결책이 아니라는 건 확실한 것 같습니다.
A : 근래에 usenet에서 이런 논의가 있었습니다.
"C++0x엔 왜 VLA(가변배열)가 없는가?"
저는 사용가능 공간이 작은 스택에 대용량 저장공간을 만드는 것은 좋지 않다고 보는 의견에 동의하는 편입니다.
사전에 그 사이즈를 안다면 정적 배열을 생성할 수가 있습니다.
그리고 만약 사전에 사이즈를 알지 못한다면 안전하지 않은 코드를 쓰게 될 것입니다.
C99 VLA는 공간 낭비 없이, 사용되지 않는 요소에 대한 생성자를 호출하지 않으면서도 작은 배열을 만들 수 있는 작은 혜택을 제공할 수 있지만, 이건 타입 시스템에 큰 변화를 가져옵니다.
(런타임 값에 의존적인 타입을 지정할 수 있어야 합니다. 이 타입은 아직 현재 C++에 존재하지 않지만, new 연산자 타입 지정자를 제외하고 특별히 처리되어 런타임의 범위를 벗어나지 않습니다.)
std::vector를 쓸 수도 있지만.
동적 메모리를 사용하는 것과 스택할당을 사용해서 만드는 것은 같지 않습니다.(정렬 문제도 꽤나 이슈죠.).
그리고 같은 문제를 풀수도 없습니다. vector는 사이즈 변경이 가능한 컨테이너고, VLA는 고정된 사이즈를 가지기 때문입니다.
C++의 동적 배열 제안은 언어 기반인 VLA 대신 라이브러리에 기반한 해결책을 내놓은 겁니다.
하지만 제가 아는 한, C++0x의 일부가 되지는 않을 것 같네요.
A2 : 일단 전 C/C++ 컴파일러 구현 경험이 있습니다.
C99의 가변길이 배열은 기본적으로, 실수입니다.
VLA을 지원하기 위해 C99은 다음과 같은 몇가지 규칙들을 포기해야 했습니다.
sizeof x는 더이상 항상 컴파일타임 상수가 아니게 됐습니다.
컴파일러는 때때로 sizeof 표현식을 런타임에 평가하기 위해 코드를 생성해야만 합니다.
2차원 VLA(int A[x][y])를 허용하려면 2차원 VLA를 매개 변수로 사용하는 함수를 선언하기 위한 새로운 구문이 필요합니다.
이런 식으로 말이죠. -> void foo(int n, int A[][*]
C++ 세계에서는 중요성이 낮습니다만,
C의 임베디드 시스템 프로그래머의 타겟 사용자들에겐 매우 중요하죠.
VLA를 선언하는 것은 스택에다가 제멋대로 묵직한 돌덩이를 던져놓는 것과 같으니까요.
VLA는 스택오버플로와 크래시를 보장합니다.
(int A[n]를 선언할 때마다 암시적으로 남는 스택이 2GB라고 단정합니다.
결국 "n이 확실히 1000보다 작다."는 것을 안다면 그냥 int A[1000]이라고 써도 되는 거죠.
32비트 정수 n을 1000으로 대체하면 프로그램이 어떻게 동작할지 알 수 없습니다.)
그래요. 그럼 이제 C++로 주제를 옮겨봅시다.
C++가 "타입 시스템"과 "값 시스템" 사이에 큰 차이를 갖고 있는 것은 C89와 마찬가지입니다.
하지만 C++에서는 C가 가지고 있지 않은 방식으로 여기에 의존하기 시작했습니다.
예를 하나 들어보죠.
template
struct S { ... };
int A[n];
S<decltype(A)> s; // equivalently, S<int[n]> s;
만약 n이 컴파일 타임 상수가 아니라면, (즉, 가변적으로 변경이 되는 타입이라면),
S의 타입은 어떻게 되는 걸까요?
S의 타입은 런타임에만 결정이 될까요?
그럼 이건 어떨까요?
template
bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);
컴파일러는 myfunc의 구체화를 위해 코드를 생성해야만 합니다.
저 코드는 어떻게 될 것 같아보이나요?
어떻게 하면 저 코드를 정적으로 생성할 수 있을까요? A1의 타입을 컴파일타임에 알지 못하더라도요.
게다가 n1!=n2인 런타임에서 !std::is_same<decltype(A1), secltype(A2)>()
이 나타나면 어떻게 될까요?
이 경우 템플릿 타입 연역이 실패해야 하므로 myfunc에 대한 호출도 컴파일하면 안 됩니다!
어떻게 하면 런타임에 행동을 평가할 수 있을까요?
기본적으로, C++는 점점 더 많은 결정을 컴파일 타임에 밀어넣는 방향으로 움직이고 있습니다.
템플릿 코드 일반화나, constexpr 함수 평가 등등..
한편, C99는 전통적으로 sizeof 같은 컴파일 타임 결정을 런타임으로 밀어넣기에 바빴습니다.
이 점을 염두에 두고, C++에 C99 스타일 VLA를 넣으려고 애를 쓰는 것이 정말 이치에 맞을까요?
다른 모든 답변자분들이 이미 지적했듯이, C++는 "필요한 RAM 용량을 알 수 없다."는 아이디어를 실제로 전달하고자 할 때 필요한 힙 할당 메커니즘들을 제공합니다.
(std::unique_ptr<int[]> A = new int[n];
or std::vector
이것들은 아주 명백한 구조를 지닙니다.)
게다가 C++는 필요한 RAM 용량이 RAM 크기보다 큰 불가피한 상황을 처리하기 위한 멋진 예외 처리 모델을 제공합니다.
이 답변이 어째서 C99 스타일 VLA가 C++에 적합하지 않고 C99에도 잘 맞지 않았는지를 잘 설명해주었으면 합니다.
또 괜찮은 토픽으로, N3810 October 2013 "Alternatives for Array Extensions"라는 Bjarne Stroustrup 선생의 VLAs에 대한 논문을 보는 것을 추천합니다.
Bjarne 선생의 관점은 저와는 매우 다릅니다. N3810은 C++스러운 구문을 찾고 C++에서 로우 배열의 사용을 방지하는 데 초점을 맞추고 있습니다.
반면, 저는 그 메타프로그래밍과 타입시스템에 영향을 주는지에 대해서 초점을 더 맞췄습니다.
메타프로그래밍/타입시스템에 대한 영향이 해결되었는지, 해결이 가능한 건지, 아니면 그저 선생이 그에 대해 관심이 없는 것인지는 모르겠습니다.
...