[C++] bool 변수는 항상 true 혹은 false 인가

일하다가 경험한 일이다. 동료가 실행이 이상하다며 같이 보자고 했다. bool 변수 하나가 visual studio watch 에서는 true 값을 갖는데 if 문에서 true 와 비교하는 쪽으로 가지 않았다. 예를 들면

void function(bool isValid)
{
    //isValid : IDE의 watch 창에서는 true 였다.
    if (isValid == true)
    {
    }
    else
    {
         // 이곳으로 온다!!!
    }
}

리빌드를 해봐도 IDE를 재시작해도 같은 결과였다. 혹시나 하여 함수에 bool 값을 넘긴 callstack을 따라 가며 watch를 봤지만1 계속 true 였다. 직접 이 1byte bool 변수의 메모리 값을 봤고 매우 놀랐다. 1 이나 0이 아니다!

보통, bool 변수에는 어떤 판정의 결과를 저장한다. int*변수 p 를 bool 변수 isNotNull 에 저장한다면 경고 2 가 발생하겠지만 p의 nullptr 여부에 따라 isNotNull이 true 또는 false 값을 갖는다.

000000007FFF3B3A  cmp         qword ptr [p],0  
000000007FFF3B40  je          wmain+49h (7FFF3B49h)  
000000007FFF3B42  mov         byte ptr [rsp+50h],1  
000000007FFF3B47  jmp         wmain+4Eh (7FFF3B4Eh)  
000000007FFF3B49  mov         byte ptr [rsp+50h],0  
000000007FFF3B4E  movzx       eax,byte ptr [rsp+50h]  
000000007FFF3B53  mov         byte ptr [isNotNull],al

p가 0이면 7FFF3B49h 로 점프, 1이면 7FFF3B42로 점프 각각 점프 한 다음 rsp+50h가 가리키는 영역에 0 또는 1을 저장. 이것을 eax 에 다시 저장한 후 isNotNull에 저장한다. (visual studio 2010 sp1, x64, debug build)

하지만 bool 값이 네트워크 버퍼에서 가져온 값이라면 어떨까? 1byte 를 읽어서 어떤 bool 변수 x에 강제로(?) 넣는다면, x 가 다른 bool 변수에 전달하는 값은 true or false 값이 아니다. x에 강제로 넣어진 값이 계속 전달된다. 아래는 이를 표현하고자 만들어본 코드다.

void function(bool result)
{
	if (result == true)
	{
		cout<<"hello"<<endl;
	}
	else
	{
		cout<<"world!"<<endl;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	bool isValid;
	memset(&isValid, 11, sizeof(bool));

	bool next = isValid;
	function(next);

	return 0;
}

16번 라인에 의해 isValid 영역에는 11값이 저장된다. 이를 next에 다시 대입하지만 18번 라인은 어셈블리 코드로

000000007FFF3B36  movzx       eax,byte ptr [isValid]  
000000007FFF3B3B  mov         byte ptr [next],al  

이것 뿐이기 때문에 값을 복사만 한다. 하여 11 값을 갖는 isValid는 next 에 같은 값을 복사 result에도 11값을 넘겨준다. result는 3번라인에서 true 가 아니기 때문에 9번 라인으로 진행된다.

문제를 찾기 힘들었던 것 중 하나는 위의 코드를 예를 들자면 watch 창에서 result 값이 true 로 표시된 것이었다. 아마도 watch 에서는 값을 표시할 때 bool 변수의 값을 보여줄 때는 1byte의 값이 0이 아니면 true 로 표시하고, 0이면 false 로 표시하는 것으로 추정된다. 0 도 1도 아닌 값이라고 표시해주면 좋았을 텐데 그게 좀 아쉽네. watch 에서 해당 변수의 실제 값을 보려면 watch 이름을 result 가 아니라

*((char*)(&result)),d

로 변경해주면 된다.

  1. 어둠의 다크니스가 생각나는군 -_-; 운명의 데스티니
  2. warning C4800: ‘int *’ : forcing value to bool ‘true’ or ‘false’ (performance warning)

Address Space Sandbox and /LARGEADDRESSAWARE

64bit 윈도우즈, 64bit cpu, 비주얼 스튜디오 x64 빌드 환경에서 링커에 “/LARGEADDRESSAWARE” 옵션 없이 그냥 빌드를 하면 2gb 이상의 주소에 접근할 수 없다.

x64 빌드에서 귀찮게 왜 저 옵션을 설정해야 2gb 이상 주소에 접근하는 걸까 궁금했는데, windows via c/c++ 5th 보다가 찾았다. 이유는
/LARGEADDRESSAWARE 가 없는 디폴트 x64 빌드 환경에서, 32bit에서 만들었던 코드가 다른 수정 없이 (그런게 없겠냐만은 조금은 수고를 덜어주겠지.) 작동하는 것을 보장하기 위해서다.

저 옵션을 활성화하지 않으면 시스템 (예를 들어 malloc) 에서 생성하는 포인터 주소 값들이 0x 7fff ffff 이하 값만 갖는다. (이런 이유로 윈도우즈 운영체제에서 2GB로 제한 된 가용 주소 공간을 address space sandbox라고 한다.) 32bit 에서 pointer 크기를 4byte 로 단정해서 쓴 경우도 많고, 2gb만 쓰니까 4byte pointer의 MSB 1 를 자기맘대로 이용한 경우가 있어서, 32bit 이상 주소를 시스템에서 반환하면 짤라먹을 까봐 저렇게 해준 것이다.

32bit 에서 /3GB 옵션 쓸 때 /LARGEADDRESSAWARE 도 같이 해줘야 한다고 해서 그때만 쓰는 걸로 헷갈렸는데 이제 정리됨.

결론. win32, x64 상관없이 2GB 이상의 주소 접근을 원한다면 무조건 /LARGEADDRESSAWARE 을 설정할 것. (‘무조건’에 대한 내용은 아래 업데이트 내용으로 대신합니다.)

UPDATE: leafbird 님 답글을 통해 2010 이상에서는 프로젝트 프로퍼티에 아무값도 안 넣어놓더라도 x64에서 /LARGEADRESSAWARE 가 적용됨을 알게 되었습니다.
아래는 제가 아무것도 없는 빈 솔루션을 vs2010 sp1 x64 debug 에서 빌드한 실행파일을 dumpbin 에서 확인한 내용입니다.

FILE HEADER VALUES
8664 machine (x64)
7 number of sections
543E854F time date stamp Wed Oct 15 23:31:43 2014
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
22 characteristics
Executable
Application can handle large (>2GB) addresses

보시는 바와 같이 /LARGEADDRESSAWARE 가 적용될 경우 마지막 문장이

  1. most significant bit

AMD Radeon 개발 비화

2010년에 이미 다른 분들 블로그를 통해서 소개가 되었지만 나는 이제 읽었다. parkoz 에서 번역된

에서 얻은 내용 정리. 참고로 RV870 은 Radeon HD 58XX, 5970 을 말한다.(Evergreen (GPU family) 의 codename cypress)
ParkPD블로그(ATI 의 Radeon HD 5870 인 RV870의 개발 비화 )에도 일부 내용이 소개되어 있음.

1. 그래픽 카드의 성수기는? : 4분기 연말 휴일 시즌이 보통이고 특별하게 최신 DirectX 버젼업이나 윈도우즈 새 버젼 발매일

2. AMD(정확하게는 그래픽부서)의 제품 개발 철학

최근 인텔에서는 제품 개발 정책을 전력 소비율 1% 증가할 때마다 성능 2% 향상시키는 것으로 바꾼 바 있다. ATI의 철학은 칩에 들어갈 기능들은 제품 출시일을 늦춰서는 안된다는 것이다.

AMD나 NVIDIA 모두 전통적으로 당시 개발조건의 한계에 가까운 단일 GPU의 플래그십 제품을 만들고 스펙다운을 시켜 주류시장의 제품들을 출시해왔다. amd는 주변 여건이나 한계에 끝까지 돌진하지 않고, 적당한 성능의 gpu를 만들고 2개의 gpu를 하나의 그래픽카드에 실는 방식을 사용했다. 빠른 시장 대응을 얻어냈다고. 조금 더 자세한 내용은 공정으로 압도하는 RV770. ATI Radeon HD 4800 시리즈를 읽어보면 된다.

3. 공정개선을 하면 수율이 올라가서 돈을 쉽게 벌 수 있겠네 했는데 그것도 아니더라. 공정이 바뀌면 웨이퍼당 단가도 같이 올라간다고.

4. ‘도약’이라는 메타포

도약(jump)라고 봐야 한다. 도약을 위해서 미리 준비를 열심히 하고, 원하는 지점에 착지하려고 안간힘을 쓰지만, 한번 발이 땅에서 떨어지게 되면 어디에 착지할지 마음대로 정할 수 없다.

5. 2015년에는 어떤 제품이 우리를 기다릴까

바로 6년 내로 최초의 홀로덱을 만드는 것이다. 최초의 홀로덱은 180도의 반원통형 디스플레이로 양방향으로 구성되어 정확한 입체 사운드를 구축하게 된다. 그리고 이 모든 것이 현실적으로 돌아가려면 강력한 그래픽 성능이 필요할 것이다. 이는 최소한 100M 픽셀의 해상도이며, 이건 30인치 모니터의 해상도의 25배나 되는 수치이다.

줄거리를 요약하지 않았다. 실제 내용은 매우 흥미진진함. 🙂

[C++] unsigned and signed

int _tmain(int argc, _TCHAR* argv[])
{
	int a	       = -3;
	unsigned int b = 2;

	if ( b + a < 0)
	{
		cout<<"hello"<<endl;
	}
	else
	{
		cout<<"world"<<endl;
	}
	return 0;
}

무조건 world 가 출력된다. vs2010 에서는 warning level 4 설정을 하더라도 경고는 출력되지 않는다. 언어의 명세라서 경고에 넣지 않은 듯. (명세 이름이 암시적 타입 변환이라니 OTL ‘명세’와 ‘암시’는 안 어울린다고…) a 가 unsigned int 로 promotion 되는 것이 원인.

위의 문제는 잘 들어나지 않다가 약간의 수정으로 발생할 수 있다. 6번 라인이 원래 b + a == -1 였다면 기대한 대로 hello 가 출력된다.

좀 더 자세한 것은 The C++ Programming Language 부록 C 6.1, C 6.3 을 보자. C의 경우는 C99 Standard 6.3.1.8을 보자.

유사한 디버깅 사례로 민장님 블로그1를 참고하자.

pvs-studio 설치 후 바로 작동이 안될 때 (v008 error, Microsoft was unexpected at this time)

회사에서 PT문서를 만들 일이 있었다. 내 IDE는 놀고 나만 일하는 게 억울해서(?), 얼마 전에 슥 봤었던 code analyzer – pvs-studio(http://www.viva64.com/) 를 깔아봤다. 트라이얼 배포이고, 산 사람은 시리얼 키를 등록하면 원래 기능이 다 되는 듯 하다.

일단 설치했더니 pvs-studio 메뉴가 생겼음. check solution 을 하니 아래와 같은 메시지가파일 개수만큼 뜬다.

Microsoft was unexpected at this time

덧붙여 에러가 하나(v008) 더 발생하긴 하는데 ‘이럴 때는 파일이 락이 걸렸던지 어쩌구 저쩌구’ 지만 그런 일이 없으므로 위의 메시지로 구글링했다.

http://www.blinnov.com/2010/06/04/microsoft-was-unexpected-at-this-time/en/

 

내 증상은 위의 링크와 같아서 문제를 해결했다. 시스템 변수1 중에 path 값에 “” 로 경로가 래핑된 게 있었다. 그 경로에서 “” 를 지워주니 해결됨. 아마도 pvs-studio 에서 “”로 래핑된 path 값 파싱을 제대로 못 하는 듯. 저 블로그 저자는 저 문제로 2일이나 고생했다고. 고마워요. vital 씨. (블로그 저자)

 

 

  1. 윈7 기준으로 컴퓨터->속성->환경 변수-> path 항목