VC와 gcc가 함수static 변수 라인의 안전성을 확보하는 데 있어서의 차이
VC와 gcc는 다르기 때문에 정적 변수의 라인 안전성을 보장할 수 없습니다.이것은 우리의 절차에 매우 큰 안전 위험과 많은 불편을 가져왔다.이 점은 마땅히 우리의 중시를 불러일으켜야 한다!특히 구조 함수가 비교적 오래 걸릴 때 프로그램에 예상치 못한 결과를 가져올 가능성이 높다.본고는 테스트 코드부터 원리를 점차적으로 분석하고 마지막으로 해결 방안을 제시한다.
다중 스레드 상태에서 VC는 함수의 정적 변수를 사용할 때 구조 함수가 실행되었음을 보장할 수 없습니다. 다음은 테스트 코드입니다.
class TestStatic
{
public:
TestStatic()
{
Sleep(1000*10);
m_num = 999;
}
public:
int m_num;
};
DWORD WINAPI TestThread( LPVOID lpParam )
{
static TestStatic test;
printf("Thread[%d] Num[%d]
", lpParam, test.m_num);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwThreadId;
for (int i=1; i<=3; i++)
{
CreateThread(NULL,0,TestThread,(LPVOID)i,0,&dwThreadId);
}
for (int i =0; i<10; i++)
{
Sleep(1000*10000);
}
return 0;
}
테스트 코드는 고의로 구조 함수에서 비교적 긴 시간 지연을 만들었고 프로그램이 실행된 결과는 다음과 같다.
Thread[2] Num[0]
Thread[3] Num[0]
Thread[1] Num[999]
그 결과 스레드 2와 스레드 3은 정적 변수의 구조 함수가 실행되지 않았을 때 이미 이 변수의 실례를 사용해서 잘못된 결과를 얻었다.
아래 나열된 TestThread 함수의 어셈블러 코드에서 문제점을 쉽게 파악할 수 있습니다.정적 변수 실례가 존재하지 않을 때 프로그램은 실례를 만들고 구조 함수를 호출합니다.실례가 존재할 때 실례 생성과 구조 함수 호출 두 단계를 직접 건너뛴다.
위의 출력 결과와 결합하여 스레드 1은 함수TestThread를 가장 먼저 호출하기 때문에 실례test를 생성하고TestStatic 클래스 구조 함수를 호출하기 시작했고 구조 함수는sleep에 끼었다.그 다음에 스레드 2와 스레드 3은 TestThread 함수를 호출했지만 이때 구조 함수가 실행되지 않았지만 정적 변수의 실례가 존재했기 때문에 생성 실례와 조정 함수를 건너뛰고 printf 함수의 호출에 가서 초기화되지 않은 변수 값을 출력했다(이곳은 0).sleep가 완성된 후 구조 함수가 실행되고 변수 값은 999로 설정되며 라인 1만 정확한 결과를 얻었습니다 999.
static TestStatic test;
00D48A7D mov eax,dword ptr [$S1 (0D9EA94h)]
00D48A82 and eax,1
00D48A85 jne TestThread+6Ch (0D48AACh)
00D48A87 mov eax,dword ptr [$S1 (0D9EA94h)]
00D48A8C or eax,1
00D48A8F mov dword ptr [$S1 (0D9EA94h)],eax
00D48A94 mov dword ptr [ebp-4],0
00D48A9B mov ecx,offset test (0D9EA98h)
00D48AA0 call TestStatic::TestStatic (0D2DF6Dh)
00D48AA5 mov dword ptr [ebp-4],0FFFFFFFFh
printf("Thread[%d] Num[%d]", lpParam, test.m_num);
00D48AAC mov esi,esp
00D48AAE mov eax,dword ptr [test (0D9EA98h)]
00D48AB3 push eax
00D48AB4 mov ecx,dword ptr [ebp+8]
00D48AB7 push ecx
00D48AB8 push offset string "thread[%d] num[%d]"(0D8A0A0h)
00D48ABD call dword ptr [MSVCR90D_NULL_THUNK_DATA (0DA0B3Ch)]
……
비슷한 코드입니다. 우리는 linux에서 gcc 컴파일러를 사용하여 효과가 어떠한지 봅시다.
class TestStatic
{
public:
TestStatic()
{
sleep(10);
m_num = 999;
}
public:
int m_num;
};
static void* TestThread( void* lpParam )
{
static TestStatic test;
printf("Thread[%d] Num[%d]
", lpParam, test.m_num);
return 0;
}
int main (int argc, char *argv[])
{
pthread_attr_t ThreadAttr;
pthread_attr_init(&ThreadAttr);
pthread_attr_setdetachstate(&ThreadAttr, PTHREAD_CREATE_DETACHED);
pthread_t tid;
for (int i=1; i<=3; i++)
{
pthread_create(&tid, &ThreadAttr, TestThread, (void*)i);
}
sleep(60*60*24);
return(0);
}
최종 결과에 따르면 gcc가 컴파일한 프로그램과 VC는 서로 다른 결과를 보였고 모든 라인은 정확한 수치를 얻었다. 이를 통해 알 수 있듯이 gcc는 함수 내부 정적 변수의 라인 안전성을 진정으로 보장했다. 프로그램 운행 결과는 다음과 같다.
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
마찬가지로 TestThread 함수의 어셈블리 코드 코드에서 문제를 분석합니다.gcc와 VC의 가장 큰 차이점은call 0x400a50
0x40195a push rbp
0x40195b mov rbp,rsp
0x40195e push r12
0x401960 push rbx
0x401961 sub rsp,0x10
0x401965 mov QWORD PTR [rbp-0x18],rdi
0x401969 mov eax,0x6031f0
0x40196e movzx eax,BYTE PTR [rax]
0x401971 test al,al
0x401973 jne 0x4019a2
0x401975 mov edi,0x6031f0
0x40197a call 0x400a50 <__cxa_guard_acquire@plt>
0x40197f test eax,eax
0x401981 setne al
0x401984 test al,al
0x401986 je 0x4019a2
0x401988 mov r12d,0x0
0x40198e mov edi,0x6031f8
0x401993 call 0x401b06
0x401998 mov edi,0x6031f0
0x40199d call 0x400ae0 <__cxa_guard_release@plt>
0x4019a2 mov edx,DWORD PTR [rip+0x201850] # 0x6031f8 <_ZZL10TestThreadPvE4test>
0x4019a8 mov rax,QWORD PTR [rbp-0x18]
0x4019ac mov rsi,rax
0x4019af mov edi,0x401d9c
0x4019b4 mov eax,0x0
0x4019b9 call 0x400a40
0x4019be mov eax,0x0
0x4019c3 add rsp,0x10
0x4019c7 pop rbx
0x4019c8 pop r12
0x4019ca pop rbp
0x4019cb ret
0x4019cc mov rbx,rax
0x4019cf test r12b,r12b
0x4019d2 jne 0x4019de
0x4019d4 mov edi,0x6031f0
0x4019d9 call 0x400b40 <__cxa_guard_abort@plt>
0x4019de mov rax,rbx
0x4019e1 mov rdi,rax
0x4019e4 call 0x400b70 <_Unwind_Resume@plt>
모두들 Singleton 모드를 즐겨 사용하는데 사용할 때 그림이 편리하고 함수 안에서 직접 정적 변수를 사용하는 것도 좋아한다.프로그램이 종료될 때 분석 함수를 실행해야 하는 경우도 정적 변수를 사용해야 한다.
그러나 다중 스레드 상태에서 VC와 gcc는 다르기 때문에 정적 변수의 스레드 안전성을 보장할 수 없다.VC의 이 결함으로 인해singleton 모드를 사용할 때 gcc처럼 정적 함수 구성원 변수를 직접 사용할 수 없습니다.이것은 우리의 절차에 매우 큰 안전 위험과 많은 불편을 가져왔다.이 점은 마땅히 우리의 중시를 불러일으켜야 한다!특히 구조 함수가 비교적 오래 걸릴 때 프로그램에 예상치 못한 결과를 가져올 가능성이 높다.우리는 반드시 변통된 방법을 사용하여 클래스의 초기화 과정을 스스로 통제해야 한다.
이전에 내가 이 문제를 해결할 때 전역 변수의 자물쇠를 직접 정의했지만 전역 변수 코드를 정의하는 것은 아름답지 않다. 왜냐하면 좋은 스타일이 아니기 때문이다.이 동시에 잠금 해제도 효율에 상당한 영향을 미친다.
다음은 함수 내부의 기본 유형 변수를 사용하여 복잡한 인스턴스의 생성을 제어하는 데 사용할 수 있는 샘플 코드를 보여 줍니다.
class ClassStatic
{
public:
ClassStatic()
{
Sleep(1000*10);
m_num = 999;
}
public:
int m_num;
};
DWORD WINAPI TestThread( LPVOID lpParam )
{
static volatile long single = 1;
while(single != 0)
{
if (1 == _InterlockedCompareExchange(&single, 2, 1))
{
break;
}
else
{
for ( unsigned int i = 0; i < 1024; i++ )
{
_mm_pause();
}
while (single != 0)
{
Sleep(1);
}
}
}
static ClassStatic test;
single && (single = 0);
printf("Thread[%d] Num[%d]
", lpParam, test.m_num);
return 0;
}
이번 운영 결과는 정확합니다.
Thread[3] Num[999]
Thread[2] Num[999]
Thread[1] Num[999]
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
정적 변수와 함수하나의 함수를 정의한 후 점호“.”를 통해 추가된 속성과 함수는 대상 자체를 통해 여전히 접근할 수 있지만 그 실례는 접근할 수 없다. 이런 변수와 함수는 각각 静态变量와静态函数라고 불린다....
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.