C ++ 언어와 관련된 문제
우선, C ++에서 요구하는 소위 "스택"또는 "힙"할당이 없습니다 . 블록 범위의 자동 객체에 대해 이야기하는 경우 "할당"되지도 않습니다. (BTW, C의 자동 저장 기간은 확실히 "할당 된"과 동일 하지 않으며 , 후자는 C ++ 용어에서 "동적"입니다.) 동적으로 할당 된 메모리는 반드시 "힙"이 아니라 빈 저장소 에 있습니다. 후자는 종종 (기본) 구현입니다 입니다.
당으로하지만 추상 기계 의미 규칙, 자동 객체가 여전히 메모리를 차지, 순응 C ++ 구현 (이 프로그램의 관찰 동작을 변경하지 않는 경우)이이 문제가되지 않습니다 증명할 수있는 경우이 사실을 무시하도록 허용된다. 이 권한은 ISO C ++ 의 as-if 규칙 에 의해 부여되며 , 이는 일반적인 최적화를 가능하게하는 일반적인 조항이기도합니다 (또한 ISO C에도 거의 동일한 규칙이 있습니다). as-if 규칙 외에도 ISO C ++에는 복사 제거 규칙이 있습니다.특정 객체 생성을 생략 할 수 있습니다. 따라서 생성자와 소멸자 호출이 생략됩니다. 결과적으로 소스 코드에 의해 암시 된 순진한 추상 시맨틱과 비교하여 이러한 생성자와 소멸자의 자동 객체 (있는 경우)도 제거됩니다.
반면, 무료 매장 할당은 설계 상 "할당"입니다. ISO C ++ 규칙에서 이러한 할당은 할당 함수를 호출하여 수행 할 수 있습니다 . 그러나 ISO C ++ 14부터는 특정 경우에 전역 할당 함수 (예 :) 호출을 병합 할 수 있는 새로운 (있는 경우가 아닌) 규칙 이 ::operator new
있습니다. 따라서 자동 할당의 경우와 같이 동적 할당 작업의 일부도 작동하지 않을 수 있습니다.
할당 기능은 메모리 자원을 할당합니다. 할당자를 사용한 할당에 따라 객체를 추가로 할당 할 수 있습니다. 자동 객체의 경우 기본 메모리에 액세스하여 다른 객체에 메모리를 제공하는 데 사용할 수 new
있지만 (배치하여 ), 무료 저장소로 큰 의미가 없습니다. 다른 곳의 자원.
다른 모든 문제는 C ++의 범위를 벗어납니다. 그럼에도 불구하고 여전히 중요 할 수 있습니다.
C ++ 구현 정보
C ++은 통합 된 활성화 레코드 나 일종의 일류 연속체 (예 : 유명한 것 call/cc
)를 노출하지 않으므로 구현시 자동 객체를 배치해야하는 활성화 레코드 프레임을 직접 조작 할 방법이 없습니다. 기본 구현 (인라인 어셈블리 코드와 같은 "기본"비 이식 가능 코드)과 (휴대 불가) 상호 운용이 없으면 프레임의 기본 할당을 생략하는 것이 매우 간단 할 수 있습니다. 예를 들어, 호출 된 함수가 인라인되면 프레임을 다른 프레임에 효과적으로 병합 할 수 있으므로 "할당"이 무엇인지 표시 할 방법이 없습니다.
그러나 일단 interops가 존중되면 상황이 복잡해지고 있습니다. C ++의 일반적인 구현은 네이티브 (ISA- 레벨 머신) 코드와 공유되는 이진 경계로서 일부 호출 규칙 을 사용하여 ISA (명령 세트 아키텍처)에서 interop의 기능을 노출합니다 . 스택 포인터를 유지할 때 특히 비용이 많이 들며 , 이는 종종 ISA 수준 레지스터에 의해 직접 유지됩니다 (종종 특정 기계 명령에 액세스해야 함). 스택 포인터는 (현재 활성화 된) 함수 호출의 상단 프레임 경계를 나타냅니다. 함수 호출이 입력되면 새 프레임이 필요하고 스택 포인터는 필요한 프레임 크기 이상의 값으로 (ISA 규칙에 따라) 더하거나 뺍니다. 그런 다음 프레임이 할당됩니다작업 후 스택 포인터. 함수에 대한 매개 변수는 호출에 사용 된 호출 규칙에 따라 스택 프레임으로 전달 될 수도 있습니다. 프레임은 C ++ 소스 코드에 의해 지정된 자동 객체 (아마도 매개 변수 포함)의 메모리를 보유 할 수 있습니다. 이러한 구현의 의미에서, 이러한 객체는 "할당"됩니다. 컨트롤이 함수 호출을 종료하면 더 이상 프레임이 필요하지 않습니다. 일반적으로 스택 포인터를 호출 이전의 상태로 다시 복원하여 호출합니다 (이전에 호출 규칙에 따라 저장 됨). 이것은 "할당 해제"로 볼 수 있습니다. 이러한 작업은 활성화 레코드를 LIFO 데이터 구조로 효과적으로 만들므로 종종 " (호출) 스택 "이라고합니다.
대부분의 C ++ 구현 (특히 ISA 수준 기본 코드를 대상으로하고 어셈블리 언어를 즉시 출력으로 사용하는 구현)은 이와 유사한 전략을 사용하므로 혼동되는 "할당"체계가 널리 사용됩니다. 이러한 할당 (및 할당 해제)은 머신 사이클을 소비하며, (최적화되지 않은) 호출이 자주 발생할 때 비용이 많이들 수 있지만, 최신 CPU 마이크로 아키텍처는 일반적인 코드 패턴 (예 : 구현 / 지침 에 스택 엔진 ).PUSH
POP
어쨌든, 일반적으로 는 스택 프레임 할당의 비용이 (가 완전히 떨어져 최적화는 제외) 무료 저장소를 운영 할당 함수를 호출보다 훨씬 적은 것이 사실이다 의 수백 (그렇지 않으면 수백만을 가질 수있는 자체 :-) 스택 포인터 및 기타 상태를 유지하기위한 작업. 할당 기능은 일반적으로 호스팅 된 환경에서 제공하는 API (예 : OS에서 제공하는 런타임)를 기반으로합니다. 함수 호출을 위해 자동 객체를 유지하는 목적과는 달리 이러한 할당은 범용이므로 스택과 같은 프레임 구조를 갖지 않습니다. 일반적으로 풀 스토리지에서 힙 (또는 여러 힙) 이라는 공간을 할당합니다 . "스택"과 달리 여기서 "힙"개념은 사용중인 데이터 구조를 나타내지 않습니다.그것은 수십 년 전에 초기 언어 구현에서 비롯된 것입니다 . (BTW, 호출 스택은 일반적으로 프로그램 또는 스레드 시작 환경에서 힙에 의해 고정 또는 사용자 지정 크기로 할당됩니다.) 사용 사례의 특성상 힙에서의 할당 및 할당 해제가 푸시 또는 팝보다 훨씬 복잡합니다. 스택 프레임) 및 하드웨어에 의해 직접 최적화되는 것은 거의 불가능합니다.
메모리 액세스에 대한 영향
일반적인 스택 할당은 항상 새 프레임을 맨 위에 배치하므로 상당히 좋은 지역성을 갖습니다. 이것은 캐시에 친숙합니다. OTOH, 무료 저장소에 무작위로 할당 된 메모리에는 그러한 속성이 없습니다. ISO C ++ 17부터는에서 제공하는 풀 리소스 템플릿이 있습니다 <memory>
. 이러한 인터페이스의 직접적인 목적은 연속 할당 결과가 메모리에서 서로 가깝게되도록하는 것입니다. 이는이 전략이 일반적으로 최신 구현에서 성능에 적합하다는 사실을 인정합니다. 그러나 이것은 할당 보다는 액세스 성능에 관한 것 입니다.
동시성
메모리에 대한 동시 액세스 기대는 스택과 힙간에 다른 영향을 미칠 수 있습니다. 호출 스택은 일반적으로 C ++ 구현에서 하나의 실행 스레드에 의해 독점적으로 소유됩니다. OTOH, 힙은 종종 프로세스의 스레드간에 공유 됩니다. 이러한 힙의 경우 할당 및 할당 해제 기능은 공유 내부 관리 데이터 구조를 데이터 경쟁으로부터 보호해야합니다. 결과적으로 힙 할당 및 할당 해제에는 내부 동기화 작업으로 인해 추가 오버 헤드가있을 수 있습니다.
공간 효율
사용 사례 및 내부 데이터 구조의 특성으로 인해 힙은 내부 메모리 조각화로 인해 어려움을 겪을 수 있지만 스택은 그렇지 않습니다. 이는 메모리 할당 성능에 직접적인 영향을 미치지 않지만 가상 메모리 가있는 시스템 에서 낮은 공간 효율성으로 인해 메모리 액세스의 전체 성능이 저하 될 수 있습니다. HDD가 실제 메모리의 스왑으로 사용될 때 특히 끔찍합니다. 대기 시간이 길어질 수 있으며 때로는 수십억 번의주기가 발생할 수 있습니다.
스택 할당의 한계
실제로 스택 할당이 힙 할당보다 성능이 우수하지만 스택 할당이 항상 힙 할당을 대체 할 수있는 것은 아닙니다.
첫째, ISO C ++를 사용하여 휴대용 방식으로 런타임에 지정된 크기로 스택에 공간을 할당 할 수있는 방법이 없습니다. alloca
G ++의 VLA (가변 길이 배열)와 같은 구현에서 제공하는 확장이 있지만이를 피해야 할 이유가 있습니다. (IIRC, Linux 소스는 최근에 VLA 사용을 제거합니다.) 또한 ISO C99는 VLA를 의무화했지만 ISO C11은 지원을 옵션으로 설정합니다.
둘째, 스택 공간 소모를 감지 할 수있는 안정적이고 휴대 가능한 방법은 없습니다. 이것을 종종 스택 오버플로 (hmm,이 사이트의 어원)라고 하지만,보다 정확하게는 스택 오버런이라고 할 수 있습니다. 실제로, 이것은 종종 유효하지 않은 메모리 액세스를 유발하고 프로그램의 상태가 손상됩니다 (또는 아마도 보안 허점). 실제로 ISO C ++에는 "스택"이라는 개념이 없으며 리소스가 소진 될 때 정의되지 않은 동작을합니다 . 자동 물체를 위해 얼마나 많은 공간을 남겨 두어야하는지주의하십시오.
스택 공간이 부족하면 스택에 할당 된 객체가 너무 많아서 너무 많은 함수 호출 또는 자동 객체의 부적절한 사용으로 인해 발생할 수 있습니다. 이러한 경우에는 올바른 종료 조건이없는 재귀 함수 호출과 같은 버그가있을 수 있습니다.
그럼에도 불구하고 때로는 깊은 재귀 호출이 필요합니다. 언 바운드 활성 호출 (통화 깊이가 총 메모리에 의해서만 제한됨)을 지원해야하는 언어 구현에서는 일반적인 C ++ 구현과 같은 대상 언어 활성화 레코드로 (현대) 기본 호출 스택을 직접 사용할 수 없습니다 . 이 문제를 해결하려면 활성화 레코드를 구성하는 대체 방법이 필요합니다. 예를 들어, SML / NJ는 명시 적으로 힙에 프레임을 할당하고 선인장 스택을 사용합니다 . 이러한 활성화 레코드 프레임의 복잡한 할당은 일반적으로 호출 스택 프레임만큼 빠르지 않습니다. 그러나 그러한 언어가 적절한 꼬리 재귀를, 객체 언어의 직접 스택 할당 (즉, 언어의 "객체"는 참조로 저장되지 않지만 공유되지 않은 C ++ 객체에 일대일로 매핑 될 수있는 기본 프리미티브 값)은 더 복잡합니다. 일반적으로 성능 저하. C ++를 사용하여 이러한 언어를 구현할 때는 성능 영향을 추정하기가 어렵습니다.