답변:
스택 해제는 일반적으로 예외 처리와 관련하여 설명됩니다. 예를 들면 다음과 같습니다.
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
여기 pleak
에서 예외가 발생하면 할당 된 메모리 가 손실되고 할당 된 메모리 는 어떤 경우에도 소멸자에 s
의해 올바르게 해제됩니다 std::string
. 범위가 종료되면 스택에 할당 된 객체는 "풀림"상태입니다 (여기서는 범위가 함수 func
입니다). 이는 컴파일러가 자동 (스택) 변수의 소멸자에 대한 호출을 삽입하여 수행됩니다.
이제 이것은 C ++에서 메모리, 데이터베이스 연결, 열린 파일 디스크립터 등과 같은 리소스를 관리하는 데 도움 이 되는 RAII 라는 기술 ( 리소스 획득이 초기화 임)로 이어지는 매우 강력한 개념 입니다.
이제 우리는 예외 안전 보장 을 제공 할 수 있습니다 .
delete [] pleak;
x == 0 인 경우에만 도달합니다.
이 모든 것은 C ++과 관련이 있습니다.
정의 : 힙 메모리에 객체를 할당하는 대신 스택에 객체를 정적으로 만들고 함수 호출을 수행하면 "스택"됩니다.
범위 ( {
및로 구분 된 }
)가 종료 return XXX;
되면 (를 사용 하여 범위의 끝에 도달하거나 예외를 throw하여) 해당 범위 내의 모든 항목이 삭제됩니다 (소멸자가 모든 것을 호출 함). 로컬 객체를 파괴하고 소멸자를 호출하는 이러한 프로세스를 스택 해제라고합니다.
스택 해제와 관련하여 다음과 같은 문제가 있습니다.
(아무것도 동적으로 유출 될 로컬 객체에 의해 관리되고 소멸자에서 정리되지 않도록 할당) 메모리 누수를 방지 - RAII는 참조 라고 니콜라이에 의해, 그리고 부스트 :: scoped_ptr를 설명서 또는 사용의 예 부스트 :: 뮤텍스를 :: scoped_lock .
프로그램 일관성 : C ++ 사양에 따르면 기존 예외를 처리하기 전에 예외를 throw해서는 안됩니다. 이것은 스택 해제 프로세스가 예외를 발생시키지 않아야 함을 의미합니다 (소멸자를 던지지 않도록 보장 된 코드 만 사용하고 try {
및 으로 소멸자의 모든 것을 둘러싸십시오 } catch(...) {}
).
스택 해제 중에 소멸자가 예외를 throw하면 정의되지 않은 동작 이 발생하여 프로그램이 예기치 않게 종료되거나 (가장 일반적인 동작) 유니버스가 종료됩니다 (이론적으로 가능하지만 실제로는 아직 관찰되지 않음).
일반적으로 스택 "언 와인드 (unwind)"는 함수 호출의 끝과 그에 따른 스택의 팝핑과 거의 동의어입니다.
그러나 특히 C ++의 경우 스택 해제는 C ++이 코드 블록이 시작된 이후 할당 된 객체에 대해 소멸자를 호출하는 방법과 관련이 있습니다. 블록 내에서 생성 된 개체는 할당 순서와 반대로 할당이 해제됩니다.
try
블록 에는 특별한 것이 없습니다 . 모든 블록에 할당 된 스택 객체 try
는 블록이 종료 될 때 풀릴 수 있습니다.
스택 해제는 주로 C ++ 개념으로, 범위가 종료 될 때 (일반적으로 또는 예외를 통해) 스택 할당 된 객체가 소멸되는 방식을 처리합니다.
이 코드 조각이 있다고 가정 해보십시오.
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
이 글을 아직 읽었는지 모르겠지만 , 콜 스택에 관한 Wikipedia의 기사 에는 적절한 설명이 있습니다.
풀기 :
호출 된 함수에서 리턴하면 스택에서 맨 위 프레임이 튀어 나와 리턴 값이 남습니다. 프로그램의 다른 곳에서 실행을 재개하기 위해 스택에서 하나 이상의 프레임을 팝하는보다 일반적인 동작을 스택 해제 라고합니다. 하며 예외 처리에 사용되는 것과 같이 로컬이 아닌 제어 구조를 사용할 때 수행해야합니다. 이 경우 함수의 스택 프레임에는 예외 핸들러를 지정하는 하나 이상의 항목이 포함됩니다. 예외가 발생하면 처리 된 예외 유형을 처리 (캐치) 할 수있는 핸들러가 발견 될 때까지 스택이 풀립니다.
일부 언어에는 일반적인 풀기를 필요로하는 다른 제어 구조가 있습니다. 파스칼을 사용하면 전역 goto 문이 중첩 된 함수에서 이전에 호출 된 외부 함수로 제어를 전송할 수 있습니다. 이 작업을 수행하려면 스택을 풀어야하는데, 외부 컨텍스트 내에서 대상 명령문으로 제어를 전송하기 위해 적절한 컨텍스트를 복원하는 데 필요한만큼의 스택 프레임을 제거해야합니다. 마찬가지로 C에는 로컬이 아닌 gotos로 작동하는 setjmp 및 longjmp 함수가 있습니다. Common Lisp을 사용하면 unwind-protect 특수 연산자를 사용하여 스택이 풀릴 때 발생하는 상황을 제어 할 수 있습니다.
연속을 적용 할 때 스택은 (논리적으로) 풀린 다음 연속 스택으로 다시 감습니다. 이것이 연속을 구현하는 유일한 방법은 아닙니다. 예를 들어, 여러 개의 명시 적 스택을 사용하여 연속 적용은 단순히 스택을 활성화하고 전달할 값을 감을 수 있습니다. Scheme 프로그래밍 언어는 연속이 호출 될 때 제어 스택의 "풀기"또는 "되감기"의 지정된 지점에서 임의의 썽크가 실행될 수 있도록합니다.
검사 [편집]
이해하는 데 도움이되는 블로그 게시물을 읽었습니다.
스택 풀기 란 무엇입니까?
재귀 함수를 지원하는 모든 언어 (예 : Fortran 77 및 Brainf * ck를 제외한 거의 모든 언어)에서 언어 런타임은 현재 실행중인 함수의 스택을 유지합니다. 스택 해제는 해당 스택을 검사하고 수정하는 방법입니다.
왜 그렇게 하시겠습니까?
답은 명백해 보일 수 있지만 풀리는 것이 유용하거나 필요한 몇 가지 관련되어 있지만 미묘하게 다른 상황이 있습니다.
- 런타임 제어 흐름 메커니즘으로 (C ++ 예외, C longjmp () 등)
- 디버거에서 사용자에게 스택을 표시합니다.
- 프로파일 러에서 스택 샘플을 가져옵니다.
- 프로그램 자체에서 (예 : 스택을 표시하는 충돌 처리기에서)
이들은 미묘하게 다른 요구 사항이 있습니다. 이들 중 일부는 성능이 중요하고 일부는 그렇지 않습니다. 일부는 외부 프레임에서 레지스터를 재구성하는 기능이 필요하지만 일부는 그렇지 않습니다. 그러나 우리는 잠시 후에 모든 것을 시작할 것입니다.
여기 에서 전체 게시물을 찾을 수 있습니다 .
모든 사람들이 C ++의 예외 처리에 대해 이야기했습니다. 그러나 스택 풀림에 대한 또 다른 의미가 있다고 생각하며 디버깅과 관련이 있습니다. 디버거는 현재 프레임 이전의 프레임으로 이동해야 할 때마다 스택 해제를 수행해야합니다. 그러나 이것은 현재 프레임으로 돌아올 때 되감기해야하는 일종의 가상 풀기입니다. 이에 대한 예는 gdb의 up / down / bt 명령 일 수 있습니다.
이 기사 에서 아래에 주어진 다이어그램 인 IMO 는 다음 명령의 경로에서 스택 해제가 미치는 영향을 아름답게 설명합니다 (예외가 발생하면 실행되지 않음).
그림에서 :
두 번째 경우 예외가 발생하면 함수 호출 스택에서 예외 핸들러를 선형으로 검색합니다. 검색은 예외 처리기 (예 : main()
둘러싸는 try-catch
블록)로 함수에서 끝나지만 함수 호출 스택에서 모든 항목을 제거 하기 전에는 끝나지 않습니다 .
C ++ 런타임은 throw와 catch 사이에 생성 된 모든 자동 변수를 파괴합니다. 아래의 간단한 예에서 f1 () throws 및 main () catch는 B와 A 유형의 객체 사이에서 스택에 순서대로 생성됩니다. f1 ()이 발생하면 B와 A의 소멸자가 호출됩니다.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
이 프로그램의 출력은
B's dtor
A's dtor
f1 ()이 발생했을 때 프로그램의 콜 스택이 다음과 같기 때문입니다.
f1()
f()
main()
따라서 f1 ()이 터지면 자동 변수 b가 파괴되고 f ()가 터지면 자동 변수 a가 파괴됩니다.
이것이 도움이되기를 바랍니다. 행복한 코딩!
예외가 발생하고 제어가 try 블록에서 핸들러로 전달되면 C ++ 런타임은 try 블록의 시작 이후 구성된 모든 자동 객체에 대해 소멸자를 호출합니다. 이 과정을 스택 해제라고합니다. 자동 객체는 구성 순서와 반대로 파괴됩니다. (자동 객체는 자동 또는 등록으로 선언되었거나 정적 또는 extern으로 선언되지 않은 로컬 객체입니다. 자동 객체 x는 프로그램이 x가 선언 된 블록을 종료 할 때마다 삭제됩니다.)
하위 객체 또는 배열 요소로 구성된 객체를 구성하는 동안 예외가 발생하면 예외가 발생하기 전에 해당 하위 객체 또는 배열 요소가 성공적으로 생성 된 경우에만 소멸자가 호출됩니다. 로컬 정적 객체의 소멸자는 객체가 성공적으로 생성 된 경우에만 호출됩니다.
Java 스택에서 풀기 또는 풀기 기능은 중요하지 않습니다 (가비지 수집기 사용). 많은 예외 처리 논문에서 나는이 개념 (스택 풀림)을 보았습니다. 특별히 그 작성자는 C 또는 C ++의 예외 처리를 처리합니다. 로 try catch
블록을 우리는 잊지 shouln't : 지역 블록 이후의 모든 개체에서 무료로 스택 .