programing

가변 길이 어레이가 C++ 표준의 일부가 아닌 이유는 무엇입니까?

magicmemo 2023. 5. 16. 22:29
반응형

가변 길이 어레이가 C++ 표준의 일부가 아닌 이유는 무엇입니까?

저는 지난 몇 년 동안 C를 잘 사용하지 않았습니다.오늘 이 질문을 읽었을 때 익숙하지 않은 C 구문을 발견했습니다.

C99에서는 다음 구문이 유효합니다.

void foo(int n) {
    int values[n]; //Declare a variable length array
}

이것은 꽤 유용한 기능인 것 같습니다.C++ 표준에 추가하는 것에 대한 논의가 있었고, 만약 그렇다면 왜 그것이 생략되었습니까?

몇 가지 잠재적인 이유:

  • 컴파일러 공급업체가 구현해야 하는 번거로움
  • 표준의 일부 다른 부분과 호환되지 않음
  • 다른 C++ 구조체를 사용하여 기능을 에뮬레이트할 수 있습니다.

C++ 표준에서는 배열 크기가 상수 식(8.3.4.1)이어야 한다고 규정합니다.

저는 에서 예, 물, 론, 난, 용, 다, 는, 알, 습, 있, 니를 사용할 수 있습니다.std::vector<int> values(m);그러나 이렇게 하면 스택이 아닌 힙에서 메모리를 할당할 수 있습니다.다음과 같은 다차원 배열을 원하는 경우:

void foo(int x, int y, int z) {
    int values[x][y][z]; // Declare a variable length array
}

그자리의 vector버전이 상당히 서툴러집니다.

void foo(int x, int y, int z) {
    vector< vector< vector<int> > > values( /* Really painful expression here. */);
}

또한 슬라이스, 행 및 열이 메모리 전체에 퍼질 수 있습니다.

의토을 보다있니에서 하는 것을 보고 .comp.std.c++이 질문은 논쟁의 양쪽에 매우 중요한 이름들로 꽤 논란이 많은 것이 분명합니다.그것은 확실히 명백하지 않습니다.std::vector항상 더 나은 해결책입니다.

(배경:저는 C와 C++ 컴파일러를 구현한 경험이 있습니다.)

C99의 가변 길이 어레이는 기본적으로 실수였습니다.VLA를 지원하기 위해 C99는 상식적으로 다음과 같은 양보를 해야 했습니다.

  • sizeof x는 더 항상 상수가 . 컴일러더컴시아상간다닙니를 해야 합니다. 컴파일러는 때때로 다음을 평가하기 위해 코드를 생성해야 합니다.sizeof-실행 시 표시됩니다.

  • 허용(2차원 VLA 허용)int A[x][y]매개 변수로 했습니다.void foo(int n, int A[][*]).

  • C++ 세계에서는 덜 중요하지만, 임베디드 시스템 프로그래머의 C 대상 독자에게는 매우 중요합니다. VLA를 선언하는 것은 스택의 임의로 큰 덩어리를 잘라내는 것을 의미합니다.이것은 보장된 스택 오버플로 및 충돌입니다. (언제든지 선언)int A[n] 용량이 입니다.2GB의 스택 여유 용량이 있다고 암묵적으로 주장하는 것입니다.결국, 만약 당신이 알고 있다면 "n서 는 라고 선언하면 .라고 선언하면 됩니다.int A[1000] 정수 n위해서1000프로그램의 동작이 무엇이어야 하는지 전혀 모른다는 것을 인정하는 것입니다.)

자, 이제 C++에 대해 이야기해 보겠습니다.C++에서 우리는 C89가 하는 것과 같은 "유형 시스템"과 "가치 시스템" 사이의 강한 차이를 가지고 있습니다. 하지만 우리는 실제로 C가 하지 않는 방식으로 그것에 의존하기 시작했습니다.예:

template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s;  // equivalently, S<int[n]> s;

한다면n상수가 컴시상즉아니다니습었가수간에만파일약즉, (다에▁(▁weren'니i만t▁a).A으로 변형된 의 가적으로변형유도어될요유까떤그형대이다체렇변면었다형이된▁were▁then▁be),?▁of▁on▁type▁would될▁of▁earth▁the▁what요까▁type▁variably유▁modified형이어가그?S 것럴?S유형도 런타임에만 결정됩니까?

다음은 어떻습니까?

template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);

컴파일러는 다음의 인스턴스화를 위한 코드를 생성해야 합니다.myfunc그 코드는 어떻게 보여야 합니까?다음 유형을 모르면 어떻게 정적으로 코드를 생성할 수 있습니까?A1컴파일 시간에?

더 나쁜 것은, 만약 런타임에 그것이 밝혀진다면?n1 != n2, 도록하록!std::is_same<decltype(A1), decltype(A2)>() 경우에는, 그경우는, 화전런으로 .myfunc 템플릿 유형 공제가 실패해야 하기 때문에 컴파일조차 할 수 없습니다!어떻게 하면 런타임에 그 행동을 모방할 수 있을까요?

기본적으로 C++은 컴파일 시간에 점점 더 많은 결정을 하는 방향으로 나아가고 있습니다: 템플릿 코드 생성,constexpr기능 평가 등이 있습니다.한편, C99는 전통적인 컴파일 시간 결정(예:sizeof)를 런타임에 입력합니다.이러한 점을 염두에 두고 C99 스타일의 VLA를 C++에 통합하기 위해 노력하는 것이 정말 말이 됩니까?

했듯이, 힙 할당 mechanism)을 합니다.std::unique_ptr<int[]> A = new int[n];또는std::vector<int> A(n);"램이 얼마나 필요한지 전혀 모르겠습니다."라는 아이디어를 전달하고 싶을 때 사용합니다.또한 C++은 필요한 RAM 용량이 RAM 용량보다 더 큰 상황을 처리할 수 있는 훌륭한 예외 처리 모델을 제공합니다.하지만 이 답변을 통해 C99 스타일의 VLA가 C++에 적합하지 않은 이유와 C99에도 적합하지 않은 이유를 알 수 있기를 바랍니다.;)


이 주제에 대한 자세한 내용은 VLA에 대한 Bjarne Stroustrup의 2013년 10월 논문인 N3810 " 어레이 확장을 위한 대안"을 참조하십시오.Bjarne의 POV는 저와 매우 다릅니다. N3810은 사물에 대한 좋은 C++ 구문을 찾는 것과 C++에서 원시 배열의 사용을 막는 것에 더 중점을 둔 반면, 저는 메타 프로그래밍과 유형 시스템에 대한 시사점에 더 중점을 두었습니다.그가 메타 프로그래밍/유형 시스템의 의미를 해결할 수 있다고 생각하는지, 해결할 수 있다고 생각하는지, 아니면 단순히 흥미롭지 않다고 생각하는지 모르겠습니다.


이와 같은 많은 점을 히트시킨 좋은 블로그 게시물은 "변수 길이 어레이의 합법적인 사용"입니다(Chris Wellons, 2019-10-27).

최근에 인터넷에서 시작된 이 문제에 대한 논의가 있었습니다.C++0x에 VLA가 없는 이유.

일반적으로 사용 가능한 공간이 거의 없는 스택에 잠재적인 대규모 어레이를 생성하는 것은 좋지 않다는 것에 동의하는 사람들의 의견에 동의합니다.미리 크기를 알고 있다면 정적 배열을 사용할 수 있습니다.그리고 만약 당신이 크기를 사전에 모른다면, 당신은 안전하지 않은 코드를 쓸 것입니다.

C99 VLA는 공간을 낭비하거나 사용하지 않는 요소에 대한 생성자를 호출하지 않고 작은 어레이를 만들 수 있다는 작은 이점을 제공할 수 있지만, 형식 시스템에 다소 큰 변화를 가져올 것입니다(런타임 값에 따라 유형을 지정할 수 있어야 함 - 이것은 현재 C++에는 존재하지 않습니다.newtype-specifier이지만 됩니다.new연산자)를 선택합니다.

사용할 수 있습니다.std::vector그러나 동적 메모리를 사용하기 때문에 완전히 같지는 않으며, 자신의 스택 할당기를 사용하는 것도 쉽지 않습니다(정렬도 문제입니다).또한 VLA가 고정 크기인 반면 벡터는 크기 조정 가능한 컨테이너이기 때문에 동일한 문제를 해결하지 못합니다.C++ Dynamic Array 제안은 언어 기반 VLA의 대안으로 라이브러리 기반 솔루션을 도입하기 위한 것입니다.하지만 제가 알기로는 C++0x에 포함되지 않을 것으로 알고 있습니다.

다음을 원할 경우 언제든지 alloca()를 사용하여 런타임에 스택에 메모리를 할당할 수 있습니다.

void foo (int n)
{
    int *values = (int *)alloca(sizeof(int) * n);
}

스택에 할당된다는 것은 스택이 풀리면 자동으로 해제된다는 것을 의미합니다.

빠른 노트:alloca(3)에 대한 Mac OS X man 페이지에서 언급한 바와 같이, "alloca() 함수는 기계와 컴파일러에 따라 다르며, 사용을 권장하지 않습니다."너도 알다시피.

저는 제 작업을 통해 가변 길이 자동 어레이나 할당()과 같은 것을 원할 때마다 메모리가 CPU 스택에 물리적으로 위치하고 있는지 여부에 대해 별로 신경 쓰지 않는다는 것을 깨달았습니다. 단지 메모리가 일반 힙으로 느리게 이동하지 않는 스택 할당기에서 나왔기 때문입니다.따라서 가변 크기 버퍼를 푸시/팝할 수 있는 메모리를 소유한 스레드 단위 개체가 있습니다.일부 플랫폼에서는 mmu를 통해 확장할 수 있습니다.다른 플랫폼은 크기가 고정되어 있습니다(일반적으로 mmu가 아니기 때문에 고정 크기 CPU 스택도 함께 제공됩니다).제가 일하는 한 플랫폼(핸드헬드 게임 콘솔)은 희귀하고 빠른 메모리에 상주하기 때문에 어쨌든 귀중한 작은 CPU 스택을 가지고 있습니다.

가변 크기의 버퍼를 CPU 스택에 밀어넣을 필요가 없다는 것은 아닙니다.솔직히, 저는 이것이 표준이 아니라는 것을 알았을 때 놀랐습니다. 왜냐하면 그것은 확실히 그 개념이 그 언어에 충분히 들어맞는 것처럼 보이기 때문입니다.하지만 저는 "변수 크기"와 "물리적으로 CPU 스택에 위치해야 함"이라는 요구사항이 함께 제시된 적이 없습니다.속도에 관한 것이었기 때문에 저는 일종의 "데이터 버퍼용 병렬 스택"을 만들었습니다.

힙 메모리를 할당하는 데는 수행된 작업에 비해 비용이 매우 많이 드는 경우가 있습니다.예를 들어 행렬 수학이 있습니다.만약 당신이 5~10개의 요소들을 가지고 작업하고 많은 산술을 한다면, malloc 오버헤드는 정말로 중요할 것입니다.동시에 크기를 컴파일 시간을 일정하게 만드는 것은 매우 낭비적이고 융통성이 없어 보입니다.

저는 C++ 자체가 너무 안전하지 않아서 "더 이상 안전하지 않은 기능을 추가하지 않도록 노력하라"는 주장이 그다지 강하지 않다고 생각합니다.반면에, C++은 틀림없이 가장 런타임 효율적인 프로그래밍 언어 기능이기 때문에 항상 유용합니다. 성능에 중요한 프로그램을 작성하는 사람들은 C++을 많이 사용할 것이며, 가능한 한 많은 성능이 필요합니다.힙에서 스택으로 항목을 이동하는 것은 그러한 가능성 중 하나입니다.힙 블록의 수를 줄이는 것도 또 다른 방법입니다.VLA를 개체 구성원으로 허용하는 것도 이를 위한 한 가지 방법입니다.저는 그런 제안을 하고 있습니다.물론 구현하기에는 다소 복잡하지만, 꽤 할 수 있을 것 같습니다.

C++14에서 사용할 수 있을 것으로 보입니다.

https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays

업데이트 : C++14로 제작되지 않았습니다.

이것은 C++/1x에 포함되는 것으로 고려되었지만, 삭제되었습니다(이것은 제가 앞에서 말한 것에 대한 수정입니다).

어쨌든 우리는 이미 가지고 있기 때문에 C++에서 덜 유용할 것입니다.std::vector이 역할을 수행할 수 있습니다.

VLA는 가변 수정 유형의 대규모 제품군에 속합니다.이 유형의 제품군은 런타임 구성 요소를 가지고 있기 때문에 매우 특별합니다.

코드:

int A[n];

컴파일러에서 다음과 같이 표시됩니다.

typedef int T[n];
T A;

는 "" " " " " " " " .A변수 유형으로 이동합니다.

이 유형의 새 변수를 만드는 것을 막을 수 있는 것은 없습니다.

T B,C,D;

또는 포인터 또는 배열

T *p, Z[10];

또한 포인터를 사용하면 동적 스토리지로 VLA를 생성할 수 있습니다.

T *p = malloc(sizeof(T));
...
free(p);

이는 VLA를 스택에서만 할당할 수 있다는 일반적인 통념을 불식시킵니다.

다시 질문으로 돌아갑니다.

이 런타임 구성 요소는 C++ 타이핑 시스템의 기본 중 하나인 유형 공제에서 잘 작동하지 않습니다.템플릿, 차감 및 오버로드를 사용할 수 없습니다.

C++ 타이핑 시스템은 정적이므로 컴파일하는 동안 모든 유형을 완전히 정의하거나 추론해야 합니다.VM 유형은 프로그램 실행 중에만 완료됩니다.이미 매우 복잡한 C++에 VM 유형을 도입하는 추가적인 복잡성은 정당하지 않은 것으로 간주되었습니다.주로 실용적인 주요 응용 분야가 자동 VLA이기 때문입니다.int A[n];)의 있는 것입니다std::vector.

VM 유형은 다차원 어레이를 처리하는 프로그램에 대해 매우 우아하고 효율적인 솔루션을 제공하기 때문에 조금 아쉽습니다.

C에서는 다음과 같이 간단히 쓸 수 있습니다.

void foo(int n, int A[n][n][n]) {
  for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
      for (int k = 0; k < n; ++k)
        A[i][j][k] = i * j * k;
}

...

int A[5][5][5], B[10][10][10];
foo(5, A);
foo(10, B);

이제 C++에서 효율적이고 우아한 솔루션을 제공해 보십시오.

이와 같은 배열은 C99의 일부이지만 표준 C++의 일부는 아닙니다. 다른 사람들이 말했듯이 벡터는 항상 훨씬 더 나은 솔루션입니다. 이것이 가변 크기 배열이 C++ 표준(또는 제안된 C++0x 표준)에 없는 이유일 것입니다.

참고로, C++ 표준이 "왜" 그런 것인지에 대한 질문은 온건한 Usenet 뉴스 그룹 comp.std.c++로 이동하십시오.

이 경우 std:: 벡터를 사용합니다.예:

std::vector<int> values;
values.resize(n);

메모리는 힙에 할당되지만 이는 작은 성능 단점만 가지고 있습니다.또한 대용량 데이터 블록은 크기가 다소 제한되므로 스택에 할당하지 않는 것이 좋습니다.

언급URL : https://stackoverflow.com/questions/1887097/why-arent-variable-length-arrays-part-of-the-c-standard

반응형