2009-12-04 5 views
1

C에서 응용 프로그램의 충돌과 관련된 정적 초기화 순서 실패에 대해 읽었습니다. 나는 그것을 이해할 수 있지만 여전히 몇 가지 질문이 있다고 생각한다.
1)이 문제를 재현하려면 어떻게해야합니까 (프로그램이 충돌 할 수 있습니까?)? 충돌을 재현 할 테스트 프로그램을 작성하고 싶습니다. 가능한 경우 소스 코드를 제공해 주시겠습니까?
2) 나는이 C++ FAQ Lite 문서를 읽고 두 개의 다른 파일에 두 개의 정적 객체 x와 y가 있고 y가 x의 메서드를 호출한다고 말합니다. 전역 정적 멤버가 파일 수준 범위를 가질 수있는 방법은 무엇입니까?
3)이 문제는 매우 위험합니다. 컴파일러 수준에서이 문제를 해결하기위한 시도가 있습니까?
4) C++ 전문가가 실생활 생산에서이 문제에 몇 번이나 직면 했습니까?C++에서 정적 초기화 순서 실패를 재현

+0

_static_ _ storage_와 (와) _static_이며 _static C variable_가 아닙니다. a) 전역 변수 (export, 선언 된 정적 또는 익명 네임 스페이스), b) 클래스의 정적 데이터 멤버 (가상 함수 테이블 및 rtti 정보를 포함한 일부 구현에서), c) 정적 로컬 변수 함수의 호출 순서에 따라 초기화 순서가 결정되므로 단일 스레드 프로그램에서는 초기화 순서에 문제가 발생하지 않습니다. –

답변

2

1) 런타임 시작 코드를 검사하여 초기화 순서를 선택하는 방법을 살펴 보거나 비트를 실험해야합니다. 당신은 아마도 두 개 이상의 물체 사이에 3 또는 4를 초기화 의존성을 작성하여 오류를 갖는 확률을 향상시킬 수

2) 그냥 파일 레벨에서 객체 인스턴스화 :

OBJECT_TYPE x; 

3) 아니오 컴파일러 내가 아는이 문제를 해결하십시오. 연결시 또는 연결 후 감지가 필요합니다.

4) 실제로는 피할 수 있습니다. 모든 초기화를 자체 포함하십시오.

+0

# 2에서 왜 FAQ가 "정적"이라고 항상 언급하고 있습니까? –

+0

"정적 연결"을 의미하는 것이 아니라 "정적 저장소"- 모든 글로벌 데이터를 의미합니다. – sharptooth

+0

알았어, 고마워. 나는 "static"이라는 단어의 형식이 나를 혼란스럽게 만들었다 고 추측한다. –

3

편집 : 주석의 관점에서보다 정확하도록 조정.

훌륭한 예는 다음과 같습니다

// A.cpp 
#include "A.h" 
std::map<int, int> my_map; 

// A.h 
#include <map> 
extern std::map<int, int> my_map; 

// B.cpp 
#include "A.h" 
class T { 
public: 
    T() { my_map.insert(std::make_pair(0, 0)); } 
}; 

T t; 

int main() { 
} 

문제는 예를 tmy_map 객체 전에 구성 될 수 있다는 것이다. 따라서 삽입은 아직 생성되지 않은 객체에서 발생할 수 있습니다. 사고가 발생했습니다.

간단한 해결책이 대신 같은 것을 할 것입니다 : 기능 범위의 정적이 처음 사용시 초기화되기 때문에

// A.h 
#include <map> 
std::map<int, int> &my_map() 

// A.cpp 
#include "A.h" 
std::map<int, int> &my_map() { 
    // initialized on first use 
    static std::map<int, int> x; 
    return x; 
} 

// B.cpp 
#include "A.h" 

class T { 
public: 
    T() { my_map().insert(std::make_pair(0, 0)); } 
}; 

T t; 

int main() { 
} 

기능을 통해 정적 객체에 액세스하여, 우리는 초기화의 순서를 보장 할 수 있습니다. 따라서 t 개체가 먼저 생성되고 my_map()이 호출되어 첫 번째 실행시 정적지도 개체가 만들어지고 그에 대한 참조가 반환됩니다.

+3

이것은 "정적 초기화 순서 실패"는 아닙니다. 이 코드에서,'my_map'은 확실히't' 전에 생성 될 것입니다.문제는 서로를 참조하는 * 분리 된 번역 단위로 정의 된 두 개의 전역 객체가있을 때만 발생합니다. –

+0

좋은 점, 그 것들이 별도의 파일에 속한다는 것을 설명하기위한 주석을 추가했습니다. –

1

"1)이 문제를 재현하려면 어떻게해야합니까 (프로그램이 중단되도록) 할 수 있습니까? 충돌을 재현 할 테스트 프로그램을 작성하고 싶습니다. 소스 코드를 제공하십시오. 가능한?"

휴대용 테스트 케이스를 작성할 수 없습니다. 정적 초기화 순서 실패는 순서가 정의되지 않았 음을 의미합니다. 이 문제는 누군가가 하나의 합법적 인 순서로 초기화 되었더라도 작동 할 것이지만 다른 합법적 인 순서로 초기화된다면 실패 할 코드를 작성할 때 발생합니다. 그래서 당신이 그것을 보장 할 수 없다는 동일한 이유로, 당신은 그것이 실패 할 것이라고 보장 할 수 없습니다. 그게 전부 요점입니다.

링커가 다른 모든 전역 변수보다 먼저 하나의 번역 단위에서 모든 전역 변수를 초기화합니다. 따라서 두 개의 소스 파일 A와 B를 설정합니다. A1과 A2는 A에, B1과 B2는 B에 있습니다.그런 다음 A1의 생성자에서 B1 (즉, "B1이 초기화되지 않은 경우 실패합니다")을 사용하고 B2의 생성자에서 A2를 사용합니다. 또한 A2의 생성자에서 A1을 사용합니다 (A에서이 순서로 선언). 그런 다음 실패하지 않는 유일한 순서는 B1, A1, A2, B2입니다. 구현할 수있는 가능성은 매우 희박합니다. 특정 구현에서 어떤 식 으로든 성공하면 A2가 A2 대신 B2 대신 B2를 사용하고 초기화 순서를 변경하지 않기를 바란다.

물론 B1의 생성자에서 B2를 사용하고 (물론 B에서이 순서로 선언 할 수도 있음) 초기화 순서에 관계없이 실패를 보장 할 수 있습니다. 그러나 정적 초기화 순서 실패가 아니라 근본적으로 중단 된 순환 의존성 일 것입니다.

"2)이 C++ FAQ Lite 기사를 읽었을 때 두 개의 다른 파일에 두 개의 정적 개체 인 x와 y가 있고 y 메서드에 x 메서드가 있다고 말합니다. 전역 정적 멤버가 파일 수준 범위를 가질 수있는 방법은 무엇입니까? "

예를 들어 두 번역 단위 모두에서 extern을 선언 할 수 있습니다 (일반적으로 공통 헤더 사용). 범위, 연결 및 보관 기간은 모두 다릅니다.

"3)이 문제는 매우 위험합니다. 컴파일러 수준에서이 문제를 해결하기위한 시도가 있습니까?"

나는 그렇지 않습니다. 나는 그것이 객체 X가 생성자에서 객체 Y를 ("위에서 정의한) 의미를 사용하여"사용 "하는지 여부를 결정할 수있는 중단 문제이다. 그래서 링크 시간에 의존성 그래프를 구성하고 t- 정렬하면된다. 가장 좋은 부분 척도.

"4) C++ 전문가가 실제 생활에서이 문제에 몇 번이나 직면 했습니까?"

절대로 (a) 전역 변수를 놓아 두지 않고 (b) 내가 사용했던 곳에서는 이니셜 라이저에서 아무 것도하지 않는 것을 피했습니다. 기본적으로 클래스를 디자인하고 전역 인스턴스를 결정하지 마십시오. 전역 개체를 사용하려는 경우 전역 개체로 디자인하십시오. 그리고 가능한 한 전역 적 통계가 아닌 지역 스코프 된 통계를 사용하십시오. 전역처럼 보이는 것을 제공해야하는 경우 객체에 대한 참조를 반환하는 함수로 또는 스택에서 생성 할 수있는 객체로 게시하고 해당 함수를 호출 한 다음 프록시로 작동합니다. (또는 원할 경우 처리하십시오). 여전히 스레드 안전성에 대해 걱정할 필요가 있지만 스레드 환경은이를 관리 할 수있는 방법을 제공하는 반면, 호출자로 하여금 번역 단위에서 전역을 정의하도록하는 것 이외의 실패를 관리 할 방법은 없습니다.

std::out과 같이 전역을 정의하는 API를 구현하는 경우에만 어려워집니다. 여기에는 전역을 선언하는 동일한 헤더에서 더미 파일 범위 변수를 정의하는 트릭을 사용할 수 있습니다. 이름은 기억이 안나네.