programing

C/C++ 컴파일러가 컴파일 시 배열의 크기를 알아야 하는 이유는 무엇입니까?

padding 2023. 7. 17. 20:43
반응형

C/C++ 컴파일러가 컴파일 시 배열의 크기를 알아야 하는 이유는 무엇입니까?

C99 이전의 C 표준(및 C++)은 스택의 어레이 크기를 컴파일 시 알아야 한다고 말합니다.하지만 왜 그런 것일까?스택의 어레이는 런타임에 할당됩니다.그렇다면 컴파일 시간에 크기가 중요한 이유는 무엇입니까?컴파일러가 컴파일할 때 크기로 무엇을 하는지 누가 설명해주길 바랍니다.감사해요.

이러한 배열의 예는 다음과 같습니다.

void func()
{
    /*Here "array" is a local variable on stack, its space is allocated
     *at run-time. Why does the compiler need know its size at compile-time?
     */
   int array[10]; 
}

가변 크기 어레이의 구현이 더 복잡한 이유를 이해하려면 자동 스토리지 지속 시간("로컬") 변수가 일반적으로 구현되는 방식에 대해 조금 알아야 합니다.

로컬 변수는 런타임 스택에 저장되는 경향이 있습니다.스택은 기본적으로 로컬 변수에 순차적으로 할당되고 단일 인덱스가 현재 "높은 물 표시"를 가리킵니다.이 인덱스는 스택 포인터입니다.

함수를 입력하면 스택 포인터가 한 방향으로 이동하여 로컬 변수에 대한 메모리를 할당하고, 함수가 종료되면 스택 포인터가 다른 방향으로 다시 이동하여 할당을 해제합니다.

즉, 메모리에서 로컬 변수의 실제 위치는 함수1 입력 시 스택 포인터의 값을 기준으로만 정의됩니다.함수의 코드는 스택 포인터의 오프셋을 통해 로컬 변수에 액세스해야 합니다.사용할 정확한 오프셋은 로컬 변수의 크기에 따라 달라집니다.

이제 모든 로컬 변수가 컴파일 시 고정된 크기를 가질 때 스택 포인터의 이러한 오프셋도 고정되므로 컴파일러가 내보내는 명령어로 직접 코딩할 수 있습니다.예를 들어, 이 함수에서는 다음과 같습니다.

void foo(void)
{
    int a;
    char b[10];
    int c;

a 할수있다니습세▁be▁as▁accessed다▁might니로 액세스할 수 있습니다.STACK_POINTER + 0,b 할수있다니습세▁be▁as▁accessed다▁might니로 액세스할 수 있습니다.STACK_POINTER + 4,그리고.c 할수있다니습세▁be▁as▁accessed다▁might니로 액세스할 수 있습니다.STACK_POINTER + 14.

그러나 가변 크기 배열을 도입하면 컴파일 시에 이러한 오프셋을 더 이상 계산할 수 없습니다. 일부 오프셋은 이 함수를 호출할 때 배열의 크기에 따라 달라집니다. 더 만듭니다. 왜냐하면 ▁that▁accesses▁writers▁this▁write▁code▁now,▁must▁they다니복▁makes합▁because이▁things▁compiler잡것▁signific▁complic▁forated더▁moreantly훨하에 접근하는 코드를 작성해야 하기 때문입니다.STACK_POINTER + N 그이로후 이후로N그 자체는 다양하며, 또한 어딘가에 저장되어야 합니다.종종 이것은 두 번의 액세스를 수행하는 것을 의미합니다 - 하나는STACK_POINTER + <constant>기싣위를 N관심 있는 실제 로컬 변수를 로드하거나 저장할 수 있습니다.


실제로 "함수 엔트리에서 스택 포인터의 값"은 자체 이름인 프레임 포인터를 가지고 있고 많은 CPU가 프레임 포인터를 저장하는 전용 레지스터를 제공할 정도로 유용한 값입니다.실제로는 일반적으로 스택 포인터 자체가 아니라 로컬 변수의 위치가 계산되는 프레임 포인터입니다.

지원하는 것이 극히 복잡한 것이 아니기 때문에 C89가 이를 허용하지 않는 이유는 당시에는 불가능했기 때문이 아닙니다.

그러나 C89에 없는 두 가지 중요한 이유가 있습니다.

  1. 컴파일 시 배열 크기를 알 수 없으면 런타임 코드의 효율성이 떨어집니다.
  2. 이것을 지원하는 것은 컴파일러 작성자들의 삶을 어렵게 만듭니다.

역사적으로 C 컴파일러는 (상대적으로) 쓰기 쉬워야 한다는 것이 매우 중요했습니다.또한 컴파일러를 일반 컴퓨터 시스템에서 실행할 수 있을 정도로 간단하고 작게 만드는 것이 가능해야 합니다(80년대 기준).C의 또 다른 중요한 특징은 생성된 코드가 예상치 못한 결과 없이 지속적으로 매우 효율적이어야 한다는 것입니다.

이러한 가치들이 더 이상 C99를 유지하지 못하는 것은 유감스러운 일이라고 생각합니다.

컴파일러는 스택에 배열 및 기타 로컬 변수를 저장할 프레임 공간을 만들기 위해 코드를 생성해야 합니다.이를 위해서는 어레이의 크기가 필요합니다.

배열을 할당하는 방법에 따라 다릅니다.

로컬 변수로 생성하고 길이를 지정하는 경우 컴파일러가 배열 요소에 대해 스택에 할당할 공간을 알아야 하므로 중요합니다.배열의 크기를 지정하지 않으면 배열 요소에 대해 지정할 공간을 알 수 없습니다.

배열에 포인터만 만들면 포인터 자체에 공간을 할당하기만 하면 실행 시간 동안 동적으로 배열 요소를 만들 수 있습니다.그러나 이러한 형태의 어레이 생성에서는 스택이 아닌 힙의 어레이 요소에 공간을 할당합니다.

C++에서는 스택에 저장된 변수가 예외 발생 시 또는 주어진 함수 또는 범위에서 반환될 때 소멸자를 호출해야 하기 때문에 구현하기가 더욱 어려워집니다.파괴할 변수의 정확한 수/크기를 추적하면 오버헤드와 복잡성이 추가됩니다.C에서는 프레임 포인터와 같은 것을 사용하여 VLA를 암묵적으로 해제할 수 있지만 C++에서는 이러한 소멸자를 호출해야 하기 때문에 도움이 되지 않습니다.

또한 VLA로 인해 서비스 거부 보안 취약성이 발생할 수 있습니다.사용자가 VLA의 크기로 사용되는 값을 제공할 수 있는 경우에는 충분히 큰 값을 사용하여 프로세스에서 스택 오버플로(따라서 오류)를 발생시킬 수 있습니다.

마지막으로, C++는 이미 안전하고 효과적인 변수 길이 배열을 가지고 있습니다.std::vector<t>), 따라서 C++ 코드에 대해 이 기능을 구현할 이유가 거의 없습니다.

스택에 가변 크기 배열을 생성한다고 가정합니다.함수에 필요한 스택 프레임의 크기는 컴파일 시 알 수 없습니다.따라서 C는 일부 런타임 환경에서 이를 사전에 알려야 한다고 가정했습니다.따라서 제한이 있습니다.C는 1970년대 초로 거슬러 올라갑니다.그 당시의 많은 언어들은 "정적인" 모양과 느낌을 가지고 있었습니다(Fortran과 같은).

언급URL : https://stackoverflow.com/questions/4341570/why-does-a-c-c-compiler-need-know-the-size-of-an-array-at-compile-time

반응형