어쨌든 VLA의 의미는 무엇입니까?
가변 길이 어레이가 무엇이고 어떻게 구현되는지 이해합니다.이 질문은 왜 그들이 존재하는지에 대한 것입니다.
VLA는 기능 블록(또는 프로토타입) 내에서만 허용되며 기본적으로 스택 외에는 다른 곳에 있을 수 없습니다(정상적인 구현을 가정할 때).C11, 6.7.6.2-2:
식별자가 가변적으로 수정된 유형을 갖는 것으로 선언되는 경우, 그것은 (6.2.3에서 정의한 대로) 일반 식별자여야 하며, 링크가 없어야 하며 블록 범위 또는 기능 프로토타입 범위 중 하나를 가져야 합니다.식별자가 정적 또는 스레드 저장 기간을 가진 객체로 선언된 경우, 가변 길이 배열 유형을 가지지 않아야 합니다.
작은 예를 들어 보겠습니다.
void f(int n)
{
int array[n];
/* etc */
}
다음과 같은 두 가지 경우를 처리해야 합니다.
n <= 0:f이를 방지해야 합니다. 그렇지 않으면 동작이 정의되지 않습니다. C11, 6.7.6.2-5(마인 강조):식이 : 하는 경우: 함수 원형 범위의 선언으로 됩니다.
*그렇지 않으면 평가할 때마다 0보다 큰 값을 가져야 합니다.가변 길이 배열 유형의 각 인스턴스 크기는 해당 수명 동안 변경되지 않습니다.여기서 크기 표현식은 피연산자의 일부입니다.sizeof연산자와 크기 식의 값을 변경하면 연산자의 결과에 영향을 주지 않으며, 크기 식이 평가되는지 여부는 지정되지 않습니다.n > stack_space_left / element_size남아 있는 스택 공간의 양을 확인하는 표준 방법은 없습니다(표준에 관한 한 스택과 같은 것은 없기 때문).그래서 이 시험은 불가능합니다.합리적인 유일한 해결책은 다음에 대해 가능한 최대 크기를 미리 정의하는 것입니다.n,말합니다N스택 오버플로가 발생하지 않도록 합니다.
다른 말로 하면, 프로그래머는 확실히 해야 합니다.0 < n <= N어떤 사람들에게는N임의의하지만, 그 프로그램은 다음과 같이 작동해야 합니다.n == N어쨌든, 따라서 일정한 크기로 어레이를 선언하는 것이 좋습니다.N가변길이 보다는n.
VLA를 대체하기 된 것으로 .alloca(이 답변에서 언급한 바와 같이), 그러나 실제로는 동일합니다(스택에 가변 크기 메모리가 있음).
그래서 문제는 왜 그랬냐는 것입니다.alloca결과적으로 VLA가 존재하며 왜 그들은 더 이상 사용되지 않습니까?VLA를 안전하게 사용하는 유일한 방법은 제한된 크기를 사용하는 것으로 보이며, 이 경우 최대 크기의 일반 어레이를 사용하는 것이 항상 실행 가능한 솔루션입니다.
제가 완전히 알지 못하는 이유로, 거의 매번 토론에서 C99 VLA라는 주제가 나올 때마다 사람들은 런타임 크기의 어레이를 로컬 개체로 선언할 가능성에 대해 이야기하기 시작합니다(즉, "스택에" 생성).이러한 VLA 기능의 측면(로컬 어레이 선언 지원)은 VLA에서 제공하는 보조 기능이기 때문에 이는 다소 놀랍고 오해의 소지가 있습니다.이는 VLA가 수행할 수 있는 작업에 중요한 역할을 하지 못합니다.대부분의 경우, 지역 VLA 선언과 그에 수반되는 잠재적 함정의 문제는 VLA 비평가들이 논의를 탈선시키고 거의 관련이 없는 세부 사항들 사이에서 교착 상태에 빠뜨리려는 의도를 가진 "strawman"으로 사용함에 따라 표면화됩니다.
C에서 VLA 지원의 본질은 무엇보다도 언어의 유형 개념의 혁명적인 질적 확장입니다.그것은 가변적으로 변형된 유형과 같은 근본적으로 새로운 유형의 도입을 포함합니다.실제로 VLA와 관련된 모든 중요한 구현 세부 정보는 VLA 개체 자체가 아니라 해당 유형에 첨부됩니다.그것은 유명한 VLA 케이크의 대부분을 구성하는 언어에 가변적으로 수정된 유형을 도입하는 것이지만, 그러한 유형의 개체를 로컬 메모리에 선언하는 기능은 케이크에 대한 중요하지 않고 상당히 중요하지 않은 착화에 지나지 않습니다.
이를 고려하십시오. 코드에서 이와 같은 내용을 선언할 때마다
/* Block scope */
int n = 10;
...
typedef int A[n];
...
n = 5; /* <- Does not affect `A` */
의 A (예: 가치)의 값n이 위 는 컨트롤이 위 형식 정의를 통과하는 정확한 순간에 완료됩니다.의 모변화의 이 있을 n (으)ㄹ 수 있다라는 하여) 더 .A을 주지 않습니다A잠시 멈춰서 그것이 무엇을 의미하는지 생각해 보세요.이는 구현이 다음과 관련되어야 한다는 것을 의미합니다.A배열 유형의 크기를 저장할 숨겨진 내부 변수입니다.는 이숨진내변다초서기다니됩화음에서 됩니다.n이 제가선통런에의 A.
이것은 위의 형식 def-declaration에 흥미롭고 특이한 속성을 제공합니다. 이 형식 def-declaration은 실행 코드(!)를 생성합니다.게다가, 그것은 실행 코드를 생성할 뿐만 아니라, 매우 중요한 실행 코드를 생성합니다.이러한 유형의 def-declaration과 관련된 내부 변수를 초기화하는 것을 잊어버린 경우 "중단"/초기화되지 않은 유형의 defailed가 발생합니다.그 내부 코드의 중요성은 언어가 그러한 가변적으로 수정된 선언에 몇 가지 비정상적인 제한을 가하는 이유입니다: 언어는 범위 밖에서 그들의 범위로 통제권을 전달하는 것을 금지합니다.
/* Block scope */
int n = 10;
goto skip; /* Error: invalid goto */
typedef int A[n];
skip:;
위의 코드는 VLA 어레이를 정의하지 않습니다.이것은 단순히 가변적으로 수정된 유형에 대해 겉보기에 순수한 별칭을 선언합니다.하지만, 그러한 형식의 def 선언을 건너뛰는 것은 불법입니다.(우리는 이미 C++에서 그러한 점프 관련 제한 사항에 익숙하지만, 다른 맥락에서도 마찬가지입니다.)
생성 코드성typedef,atypedef런타임 초기화를 요구하는 것은 무엇보다 중요합니다.typedef"의 채택 이 되기도 (VLA의 C++ 채택 방식에 있어서도 입니다.)
실제 VLA 개체를 선언하면 컴파일러는 실제 어레이 메모리를 할당할 뿐만 아니라 해당 어레이의 크기를 포함하는 하나 이상의 숨겨진 내부 변수도 생성합니다.이러한 숨겨진 변수는 배열 자체가 아니라 가변적으로 수정된 유형과 관련이 있다는 것을 이해해야 합니다.
이 접근 방식의 중요하고 주목할 만한 결과는 다음과 같습니다. VLA와 관련된 어레이 크기에 대한 추가 정보는 VLA의 객체 표현에 직접 포함되지 않습니다.실제로는 배열 외에 "사이드카" 데이터로 저장됩니다.즉, (다차원일 수 있음) VLA의 객체 표현은 동일한 차원 및 동일한 크기의 일반적인 컴파일 시간 크기 배열의 객체 표현과 완전히 호환됩니다.예를들면
void foo(unsigned n, unsigned m, unsigned k, int a[n][m][k]) {}
void bar(int a[5][5][5]) {}
int main(void)
{
unsigned n = 5;
int vla_a[n][n][n];
bar(a);
int classic_a[5][6][7];
foo(5, 6, 7, classic_a);
}
위 코드의 두 함수 호출은 완벽하게 유효하며 "고전적" 배열이 필요한 VLA를 통과하고 그 반대의 경우에도 불구하고 동작은 언어에 의해 완전히 정의됩니다.해당 유형 중 적어도 하나가 런타임 크기이기 때문에 컴파일러는 이러한 호출에서 유형 호환성을 제어할 수 없습니다.그러나 원하는 경우 컴파일러(또는 사용자)는 코드의 디버그 버전에서 런타임 검사를 수행하는 데 필요한 모든 것을 갖추고 있습니다.
(참고: 일반적으로 배열 유형의 매개 변수는 항상 암시적으로 포인터 유형의 매개 변수로 조정됩니다.이는 "classic" 어레이 매개 변수 선언에 적용되는 것과 동일하게 VLA 매개 변수 선언에 적용됩니다.이것은 위의 예제 매개변수에서a실제로 유형이 있습니다.int (*)[m][k]은 이유은다값영받않지습다니의 을 받지 .n런타임 값에 대한 의존성을 유지하기 위해 의도적으로 어레이에 몇 가지 차원을 추가했습니다.)
VLA와 "클래식" 어레이 간의 호환성은 컴파일러가 가변적으로 수정된 파라미터와 함께 크기에 대한 추가적인 숨겨진 정보를 함께 제공할 필요가 없다는 사실에서도 지원됩니다.대신, 언어 구문은 사용자가 이 추가 정보를 공개적으로 전달하도록 합니다.위의 예에서 사용자는 먼저 매개 변수를 포함하도록 강제되었습니다.n,m그리고.k함수 매개 변수 목록으로 이동합니다.을 선언하지 n,m그리고.k는 " ", " " " 를 선언할 수 입니다.a에 대한 위의 하십시오.n한 이러한a.
다른 예로, VLA 지원을 활용하여 다음 코드를 작성할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
void init(unsigned n, unsigned m, int a[n][m])
{
for (unsigned i = 0; i < n; ++i)
for (unsigned j = 0; j < m; ++j)
a[i][j] = rand() % 100;
}
void display(unsigned n, unsigned m, int a[n][m])
{
for (unsigned i = 0; i < n; ++i)
for (unsigned j = 0; j < m; ++j)
printf("%2d%s", a[i][j], j + 1 < m ? " " : "\n");
printf("\n");
}
int main(void)
{
int a1[5][5] = { 42 };
display(5, 5, a1);
init(5, 5, a1);
display(5, 5, a1);
unsigned n = rand() % 10 + 5, m = rand() % 10 + 5;
int (*a2)[n][m] = malloc(sizeof *a2);
init(n, m, *a2);
display(n, m, *a2);
free(a2);
}
이 코드는 다음과 같은 사실에 주의를 기울이기 위한 것입니다. 이 코드는 가변적으로 수정된 유형의 귀중한 속성을 많이 사용합니다.VLA 없이는 우아하게 구현할 수 없습니다.이것이 이전에 그들의 자리에서 사용되었던 추악한 해킹을 대체하기 위해 C에서 이러한 속성이 절실히 필요한 주된 이유입니다.그러나 동시에 위 프로그램의 로컬 메모리에는 단 한 개의 VLA도 생성되지 않습니다. 즉, 이 인기 있는 VLA 비판 벡터는 이 코드에 전혀 적용되지 않습니다.
기본적으로 위의 두 가지 마지막 예는 VLA 지원의 요점이 무엇인지 간략하게 보여줍니다.
댓글과 답변을 보면 일반적으로 입력이 너무 크지 않다는 것을 알 때(재귀가 너무 깊지 않다는 것을 아는 것과 유사) VLA가 유용하지만 실제로 상한선이 없습니다.그리고 일반적으로 스택 오버플로가 발생하지 않기를 바라며 스택 오버플로를 무시합니다(재귀로 무시하는 것과 유사).
스택 크기가 무제한인 경우와 같이 실제로는 전혀 문제가 되지 않을 수도 있습니다.
즉, 스택에서 메모리를 실제로 할당하지는 않지만 동적 다차원 어레이로 작업하는 것을 더 쉽게 하는 또 다른 용도를 발견했습니다.간단한 예를 들어 설명하겠습니다.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t n, m;
scanf("%zu %zu", &n, &m);
int (*array)[n][m] = malloc(sizeof *array);
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
(*array)[i][j] = i + j;
free(array);
return 0;
}
VLA에 대해 언급한 모든 사항에도 불구하고 VLA의 가장 좋은 점은 컴파일러가 컴파일 시간 상수가 아닌 어레이의 스토리지 관리 및 인덱스 계산 복잡성을 자동으로 처리한다는 것입니다.
로컬 동적 메모리 할당을 원하는 경우 유일한 옵션은 VLA입니다.
이것이 VLA가 C99에 채택된 이유일 수 있다고 생각합니다(C11의 경우 옵션).
한 가지 분명히 하고 싶은 것은 와 VLA 사이에 몇 가지 현저한 차이가 있다는 것입니다.이 게시물은 다음과 같은 차이점을 지적합니다.
- 그억
alloca()반환값은 현재 함수가 지속되는 한 유효합니다.VLA가 사용하는 메모리의 수명은 VLA의 식별자가 범위 내에 있는 한 유효합니다.- 넌 할 수 있다.
alloca()예를 들어 루프 내의 메모리와 루프 외부의 메모리를 사용하면 루프가 종료될 때 식별자가 범위를 벗어났기 때문에 VLA가 사라집니다.
당신의 주장은 바인딩된 VLA의 크기를 확인해야 하므로 최대 크기만 할당하고 런타임 할당을 수행하는 것이 어떻겠느냐는 것입니다.
이러한 주장은 메모리가 시스템에서 많은 프로세스 간에 공유되는 제한된 리소스라는 사실을 간과합니다.한 프로세스에서 낭비적으로 할당된 메모리는 다른 프로세스에서 사용할 수 없습니다(또는 사용할 수 있지만 디스크로 스왑하는 비용이 발생합니다).
동일한 주장으로 필요한 최대 크기를 정적으로 할당할 수 있는 런타임에 어레이를 malloc할 필요가 없습니다.결국 힙 소진은 스택 오버플로보다 약간 더 선호됩니다.
VLA는 메모리를 할당하거나 메모리를 스택할 필요가 없습니다.그들은 프로그래밍의 많은 측면에서 매우 유용합니다.
몇 가지 예
- 함수 매개 변수로 사용됩니다.
int foo(size_t cols, int (*array)[cols])
{
//access as normal 2D array
prinf("%d", array[5][6]);
/* ... */
}
- 2D(또는 그 이상) 어레이를 동적으로 할당
inr foo(size_t rows, size_t cols)
{
int (*array)[cols] = malloc(rows * sizeof(*array));
/* ... */
//access as normal 2D array
prinf("%d", array[5][6]);
/* ... */
스택 할당(따라서 VLA 할당)은 매우 빠르기 때문에 스택 포인터(일반적으로 단일 CPU 명령)를 빠르게 수정해야 합니다.고가의 힙 할당/할당 해제가 필요하지 않습니다.
대신에 크기가 일정한 어레이를 사용하면 어떨까요?
고성능 코드를 작성하는 경우 가변 크기 버퍼가 필요하다고 가정합니다. 예를 들어 8~512개의 요소가 필요합니다.512개의 요소 배열만 선언할 수 있지만 대부분 8개의 요소만 필요한 경우 스택 메모리의 캐시 인접성에 영향을 미치기 때문에 오버로케이션이 성능에 영향을 미칠 수 있습니다.이제 이 기능이 수백만 번 호출되어야 한다고 상상해 보십시오.
또 다른 예로, (로컬 VLA를 사용하는) 함수가 재귀적으로 할당된 모든 VLA의 총 크기가 제한된다는 것을 미리 알고 있습니다(즉, 어레이의 크기는 가변적이지만 모든 크기의 합계는 제한됨).이 경우 최대 가능한 크기를 고정 로컬 어레이 크기로 사용하면 다른 경우보다 훨씬 많은 메모리를 할당하여 캐시 누락으로 인해 코드 속도가 느려지고 스택 오버플로가 발생할 수 있습니다.
언급URL : https://stackoverflow.com/questions/22530363/whats-the-point-of-vla-anyway
'programing' 카테고리의 다른 글
| 도커 합성 실행을 사용하여 로그 출력을 보는 방법은 무엇입니까? (0) | 2023.08.15 |
|---|---|
| Angular 2에서 라우팅을 추적하는 방법은 무엇입니까? (0) | 2023.08.15 |
| 도커에서 여러 터미널을 여는 방법은 무엇입니까? (0) | 2023.08.15 |
| Android TextView에서 maxLength를 프로그래밍 방식으로 설정하는 방법은 무엇입니까? (0) | 2023.08.15 |
| 파일 수 x개 유지 및 다른 모든 파일 삭제 - Powershell (0) | 2023.08.15 |