본문 바로가기

programming/C++

Release Mode vs Debug Mode

출처 : http://blog.naver.com/PostView.nhn?blogId=nawoo&logNo=80135622658

Check List

  • Pointer
    초기화 되지 않은 포인터의 경우 디버그모드에서는 임의값 0xCD로 초기화를 수행하지만 릴리즈에서는 초기화를 수행하지 않는다. 디버그 모드에서 컴파일러 옵션을 조정하여 초기화 하지 않은 포인터 변수를 사용하는것을 예방 할 수 있다. /GZ 컴파일러 옵션은 기본적으로 VC++ 프로젝트 셋팅에서 기본값이 아니므로 필요하다면 추가해서 초기화 되지 않는 포인터의 값을 0xCC로 채우도록 해줘야 한다. /GZ 컴파일러 옵션의 가장 큰 목적은 초기화 하지 않은 메모리 변수의 값을 0xCCCCCCCC로 채워서 디버깅중에 개발자가 초기화 하지 않은 값임을 알 수 있도록 하는것이다.
    • 디버그모드에서 deallocator에 의해 해제된 메모리에 채워지는 값은 0xDD 이다.
  • Heap
    디버그 모드에서 힙영역에 메모리를 할당하게 되면 guard byte( 0xFD로 초기화 )를 추가적으로 할당하여 가장 흔하게 범하는 zero-base의 arrary 인덱스를 잘못 계산하여 경계를 벗어난 영역을 접근하거나 지우려고 하는 코드를 작성했다고 하더라도 디버그 모드에서는 크래쉬가 발생하지 않을 수 있다. 이런 잠재적인 버그를 가진 프로그램을 릴리즈 모드에서 실행시키게 되면 크래쉬가 발생하게 된다.
  • ASSERT
    ASSERT 구문은 디버그 모드에서는 공백으로 대체되어지는 점을 잊고 아래와 같이 쓰게 되는 경우 이 코드는 릴리즈에서는 공백으로 대체되어 실제로 체크를 할 수 없게 된다.
    ASSERT (OpenMyWindow () != NULL);
    위의 코드를 다음과 같이 바꾸게 되면 디버그와 릴리즈 모드 모두 정상적으로 동작하게 된다.
    hWND = OpenMyWindow();ASSERT (hWND != NULL);
    이런 체크를 릴리즈에서도 하고 싶다면 VERITY 매크로를 사용하도록 하자.
  • Prototypes 사용자 정의 메시지 처리를 위해 ON_MESSAGE 매크로를 사용중이라면 메시지 핸들러의 원형을 요구하는 타입에 정확하게 맞게 선언해줘야 한다. 디버그 모드에서는 원형에 일치하지 않더라도 컴파일러가 수정하여 동작하도록 만들어줘 버그를 발견하기 힘들게 만든다.
    afx_msg LRESULT <class>::OnMyMessage (WPARAM wParam, LPARAM lParam);
  • Optimization Max Speed 옵션은 속도 최적화에 촛점을 맞추고 있기 때문에 최적화 과정에서 안전하지 않을수 있다. 기본적으로 릴리즈 모드에서는 Maximize Speed 옵션이 기본이지만 안전하게 속도 최적화를 보장하는 Minimize Size를 추천한다. 그리고, #pragma 지시자를 사용하여 특정 영역의 옵션을 설정 할 수 있다는것을 기억하자.
    #pragma optimize("", off)// some code here #pragma optimize("", on)

MSDN에서 권고하는 릴리즈 빌드 문제 해결

  • ASSERT 문 검사
    • CheckList의 ASSERT
  • 디버그 빌드를 사용한 메모리 덮어쓰기 확인
    1. InitInstance 함수의 맨 처음 부분에 다음 줄을 추가합니다. 이렇게 하면 디버그 메모리 할당자가 모든 할당된 메모리 주위에 보호 바이트를 배치합니다. 그러나 이 보호 바이트의 변경 여부(변경되었으면 메모리 덮어쓰기가 발생했음)를 확인하지 않으면 보호 바이트는 아무런 소용이 없습니다. 보호 바이트의 변경 여부를 확인하면 버퍼가 제공되어 메모리 덮어쓰기를 해결할 수 있습니다.

      afxMemDF |= checkAlwaysMemDF;

      checkAlwaysMemDF 변수를 설정하면 MFC에서는 new 또는 delete가 호출될 때마다 AfxCheckMemory 함수를 호출하게 됩니다. 메모리 덮어쓰기가 감지된 경우에는 다음과 같은 TRACE 메시지가 생성됩니다.

      Damage Occurred! Block=0x5533

      이러한 메시지가 표시되는 경우에는 코드를 단계별로 실행하여 손상된 부분을 확인해야 합니다. 메모리 덮어쓰기가 발생한 부분을 보다 정확하게 구별하려면 사용자가 AfxCheckMemory를 명시적으로 호출하면 됩니다. 예를 들면 다음과 같습니다.

      ASSERT(AfxCheckMemory());    DoABunchOfStuff();ASSERT(AfxCheckMemory());

      첫 번째 ASSERT는 성공하고 두 번째 ASSERT는 실패하는 경우에는 두 호출 사이의 함수에서 메모리 덮어쓰기가 발생했을 가능성이 큽니다.

      응용 프로그램의 특성에 따라 afxMemDF를 사용하면 프로그램 실행이 너무 느려져서 테스트조차 수행할 수 없는 경우도 있습니다. 왜냐하면afxMemDF 변수는 new 및 delete가 호출될 때마다 AfxCheckMemory가 호출되도록 하기 때문입니다. 이 경우에는 위의 예제와 같이 AfxCheckMemory( ) 호출을 분산시켜야 하며, 이러한 방법으로 메모리 덮어쓰기를 구별해야 합니다.

  • 릴리스 빌드에 대한 디버그 정보 생성 활성화
    1. 프로젝트의 속성 페이지 대화 상자를 엽니다. 자세한 내용은 Visual C++ 프로젝트 속성 설정을 참조하십시오.
    2. /Z7 또는 /Zi를 활성화합니다.
    3. /INCREMENTAL:NO를 선택합니다.
    4. /DEBUG:Yes를 선택합니다.
    5. /OPT:REF를 선택합니다.
    6. /OPT:ICF를 선택합니다.
    • 이제 릴리스 빌드 응용 프로그램을 디버깅할 수 있습니다. 문제를 찾으려면 오류가 발생한 부분을 찾을 때까지 코드를 단계별로 실행하거나, Just-In-Time 디버깅을 사용하여 올바르지 않은 매개 변수 또는 코드를 확인합니다.

      프로그램이 디버그 빌드에서는 작동하지만 릴리스 빌드에서 작동하지 않으면 소스 코드에서 컴파일러 최적화 중 하나에 결함이 있는 경우일 수 있습니다. 문제를 격리하려면 문제의 원인이 되는 최적화와 파일을 찾을 때까지 각 소스 코드 파일에 대해 선택한 최적화를 비활성화해야 합니다. 예를 들어, 파일을 두 그룹으로 나누고 한 그룹에서 최적화를 비활성화한 다음 문제가 파일 하나에서만 발생할 때까지 각 그룹을 계속 나눌 수 있습니다.

      디버그 빌드에서 그러한 버그를 노출시키려면 /RTC를 사용합니다.

  • 메모리 덮어쓰기 확인
    힙 조작 함수 호출 시 액세스 위반이 발생되면 프로그램이 힙을 손상시켰을 가능성이 있습니다. 이러한 경우의 일반적인 증상은 다음과 같습니다.
    Access Violation in _searchseg
    _heapchk 함수는 디버그 빌드와 릴리스 빌드 모두에서(Windows NT에만 해당) 런타임 라이브러리 힙의 무결성을 확인하는 데 사용할 수 있습니다. _heapchk는 AfxCheckMemory 함수를 사용하여 힙 덮어쓰기를 확인하는 것과 같은 방법으로 사용할 수 있습니다. 예를 들면 다음과 같습니다.
    if(_heapchk()!=_HEAPOK)   DebugBreak();
    이 함수가 실패하는 경우에는 힙이 손상된 시점을 확인해야 합니다.

Reference