C ++ 함수에서 정적 변수의 수명은 얼마입니까?


373

변수가 static함수 범위에서 선언 된 경우 한 번만 초기화되고 함수 호출간에 값이 유지됩니다. 수명이 정확히 무엇입니까? 생성자와 소멸자는 언제 호출됩니까?

void foo() 
{ 
    static string plonk = "When will I die?";
}

답변:


257

함수 static변수 의 수명은 프로그램 흐름 이 처음으로 선언을 만나고 프로그램 종료시 종료됩니다 [0]에 시작합니다 . 즉, 런타임은 실제로 구성된 경우에만 소장을 유지하기 위해 일부 장부 보관을 수행해야합니다.

표준 정적 객체의 소멸자가 완공의 역순으로 실행해야한다는 것을 말한다 있기 때문에 또한, [1] , 건설의 순서가 특정 프로그램의 실행에 따라 달라질 수 있습니다 건설의 순서가 고려되어야한다 .

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

산출:

C :> sample.exe
가 foo
에서 생성됨 foo에서 삭제됨

C :> sample.exe 1 foo로 작성된
경우에
생성됨 foo
에서
파기 된 경우

C :> SAMPLE.EXE 1 2
foo는에서 만든
경우에 만든
경우에 파괴
foo는 파괴

[0]이후 C ++ 98 [2] 이 멀티 스레드 환경 행동 얼마나 다수의 스레드에 대한 참조가없는 것은 불특정하고, 같은 문제가 될 수 로디를 언급한다.

[1] C ++ 98 섹션 3.6.3.1 [basic.start.term]

[2]C ++ 11에서 스태틱은 스레드 안전 방식으로 초기화되며 Magic Statics 라고도 합니다.


2
c'tor / d' tor 부작용이없는 단순 유형의 경우 전역 단순 유형과 동일한 방식으로 초기화하는 것이 간단한 최적화입니다. 이것은 분기, 플래그 및 파기 순서 문제를 피합니다. 그들의 수명이 다르다는 것은 아닙니다.
John McFarlane

1
여러 스레드에서 함수를 호출 할 수 있으면 C ++ 98에서 뮤텍스로 정적 선언을 보호해야한다는 의미입니까?
allyourcode

1
"글로벌 오브젝트의 소멸자 (destructor)"는 해당 오브젝트가 전역 적이 아니기 때문에 구성 완료의 역순으로 실행해야합니다. 정적 또는 스레드 저장 시간을 가진 로컬의 폐기 순서는 순수한 LIFO보다 상당히 복잡합니다. 섹션 3.6.3[basic.start.term]
Ben Voigt

2
"프로그램 종료시"라는 문구가 정확하지 않습니다. 동적으로로드 및 언로드되는 Windows dll의 정적 정보는 어떻습니까? 분명히 C ++ 표준은 어셈블리를 전혀 다루지 않지만 (그렇다면 좋을 것입니다) 표준이 말한 것을 정확하게 설명하면 좋을 것입니다. "프로그램 종료시"라는 문구가 포함되어 있으면 기술적으로 동적으로 언로드 된 어셈블리를 사용하여 C ++ 구현을 부적합하게 만듭니다.
Roger Sanders

2
@Motti 나는 표준이 명시 적으로 동적 라이브러리를 허용한다고 생각하지 않지만, 지금까지는 표준에 구현과 상충되는 무언가가 있다고 생각하지 않았습니다. 물론 여기에서 언어를 엄격하게 말하면 정적 객체를 다른 수단을 통해 일찍 파괴 할 수 없다고 말하지는 않습니다. 메인에서 돌아 오거나 std :: exit를 호출 할 때 파괴되어야합니다. 내가 생각하지만 꽤 좋은 라인.
Roger Sanders

125

Motti는 명령에 대해 옳지 만 고려해야 할 다른 사항이 있습니다.

컴파일러는 일반적으로 숨겨진 플래그 변수를 사용하여 로컬 정적이 이미 초기화되었는지 여부를 나타내며이 플래그는 함수의 모든 항목에서 확인됩니다. 분명히 이것은 약간의 성능 저하이지만, 더 중요한 것은이 플래그가 스레드로부터 안전하다는 보장이 없다는 것입니다.

위와 같이 로컬 정적이 있고 foo여러 스레드에서 호출 된 경우 경쟁 조건이 plonk잘못되거나 여러 번 초기화 될 수 있습니다. 또한이 경우 plonk구성한 스레드와 다른 스레드에 의해 파괴 될 수 있습니다.

표준이 말한 것에도 불구하고, 나는 로컬 정적 파괴의 실제 순서에 매우주의를 기울였습니다. 왜냐하면 정적이 파괴 된 후에도 여전히 유효한 정적에 의지 할 수 있기 때문에 추적하기가 실제로 어렵습니다.


68
C ++ 0x에서는 정적 초기화가 스레드로부터 안전해야합니다. 그러니 조심해야하지만 상황은 나아질 것입니다.
deft_code

약간의 정책으로 파괴 명령 문제를 피할 수 있습니다. 정적 / 글로벌 객체 (단일 등)는 메소드 본문의 다른 정적 객체에 액세스해서는 안됩니다. 메소드에서 나중에 액세스하기 위해 참조 / 포인터를 저장할 수있는 생성자에서만 액세스해야합니다. 이것은 완벽하지는 않지만 99 가지 경우를 수정해야하며 포착하지 못하는 경우는 분명히 비린내가 있으며 코드 검토에서 잡아야합니다. 정책을 언어로 시행 할 수 없기 때문에 여전히 완벽한 해결책은 아닙니다.
deft_code

나는 멍청한 놈이지만 왜이 정책을 언어로 시행 할 수 없습니까?
cjcurrie

9
C ++ 11부터는 더 이상 문제가되지 않습니다. Motti의 답변은 그에 따라 업데이트됩니다.
Nilanjan Basu 2014

10

기존 설명은 6.7에서 발견 된 표준의 실제 규칙이 없으면 실제로 완전하지 않습니다.

정적 스토리지 기간 또는 스레드 스토리지 기간으로 모든 블록 범위 변수를 0으로 초기화하면 다른 초기화가 수행됩니다. 적용 가능한 경우 정적 스토리지 기간으로 블록 범위 엔티티의 지속적인 초기화는 블록이 처음 입력되기 전에 수행됩니다. 구현시 네임 스페이스 범위에서 정적 또는 스레드 스토리지 기간으로 변수를 정적으로 초기화 할 수있는 것과 동일한 조건에서 정적 또는 스레드 스토리지 기간으로 다른 블록 범위 변수의 초기 초기화를 수행 할 수 있습니다. 그렇지 않으면 이러한 변수는 제어가 선언을 처음 통과 할 때 초기화됩니다. 이러한 변수는 초기화가 완료되면 초기화 된 것으로 간주됩니다. 예외가 발생하여 초기화가 종료되면 초기화가 완료되지 않았으므로 다음에 컨트롤이 선언에 들어갈 때 다시 시도됩니다. 변수가 초기화되는 동안 제어가 동시에 선언에 들어가면 동시 실행은 초기화가 완료 될 때까지 기다려야합니다. 변수가 초기화되는 동안 제어가 선언을 재귀 적으로 다시 입력하면 동작이 정의되지 않습니다.


8

FWIW, Codegear C ++ Builder는 표준에 따라 예상 순서대로 소멸되지 않습니다.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... 파괴 명령에 의존하지 않는 또 다른 이유입니다!


57
좋은 주장이 아닙니다. 이 컴파일러를 사용하지 않는 것이 더 많은 논쟁이라고 말하고 싶습니다.
Martin York

26
흠. 이론적으로 이식 가능한 코드가 아닌 실제 휴대용 코드를 생성하는 데 관심이 있다면 언어의 어떤 영역이 문제를 일으킬 수 있는지 아는 것이 유용하다고 생각합니다. C ++ Builder가 이것을 처리하지 못하는 독창적 인 경우 놀랐습니다.
Roddy

17
"컴파일러가 어떤 문제를 일으키는 지, 그리고 어떤 언어의 영역에서 일하는지"로 표현한다는 점을 제외하고는 동의합니다. ;-P
Steve Jessop

0

정적 변수는 한 번 활동하기 시작하는 프로그램 실행 시작 과 프로그램 실행 종료까지 계속 사용할 수 있습니다.

정적 변수는 메모리데이터 세그먼트 에서 생성됩니다 .


이 기능 범위에서 변수에 대해 사실이 아니다
awerries
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.