[C++] __VA_ARGS__ 를 유니코드로 변경하기

대충 요약해서 아래와 같은 매크로가 있었다.


#define MAKE_STRING0(str1) L#str

#define MAKE_STRING(str1, str2) L#str1 L#str2

MAKE_STRING0 를 없애려고 variadic macro 1 를 써보려고 했음.

 

#define MAKE_STRING(str1, ...) #str1 #__VA_ARGS__ //non-unicode

MAKE_STRING(any_token) 이런 식으로 2번째 파라미터가 없을 때 non-unicode 버전으로 만들면 문제 없으나

 

#define MAKE_STRING(str1, ...) L#str1 L#__VA_ARGS__ //unicode 

unicode 버전에서는 __VA_ARGS__ 가 사라지면서  “L” 만 남는 불상사가!

macro overloading 뭐 비슷한게 없을 까 해서 stackoverflow 를 찾아보니 http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments  흐익 이런 트릭이…

여기서 아이디어를 얻어와서 다음과 같은 방식으로 해결!

#define TO_UNICODE_IMPL(first, ...) L#first
#define TO_UNICODE(...) TO_UNICODE_IMPL(__VA_ARGS__, "")
#define MAKE_STRING(str1, ...) L#str1 TO_UNICODE(__VA_ARGS__)
  1. http://msdn.microsoft.com/ko-kr/library/ms177415(v=vs.100).aspx

[ide] visual studio에서 static library 에 있는 전역객체 강제 사용하기

http://thetweaker.wordpress.com/2013/04/06/forcing-construction-of-global-objects-in-static-libraries/

링킹하는 라이브러리에서 전역으로 생성되는 객체가 있을 때, 라이브러리를 가져다 쓰는 솔루션에서 해당 객체에 대한 레퍼런스가 없다면 링킹하는 중에 객체 생성 코드를 삭제한다. 위의 아티클은 객체 생성 코드를 삭제하지 않도록 하는 방법에 대해 알려준다. 한 가지 참고해야 할 것은 기사에 언급한 “Use Library Dependency Inputs” 항목은 project properties 의 configuration-> linker->general 에 위치한 항목이 아니라 project properties -> common properties -> Add New Reference 로 .lib 를 참조 등록한 다음 생성되는 Project Reference Properties -> Use Library Dependency Inputs 값을 활성화해야 한다. 한참 찾았다 -_-; 그나저나 다른 사람에게 전달하는 .lib 에 전역 객체 설정을 하는 거 자체가 틀려먹은 게 아닌가 싶긴한데… 나 같음 명시적으로 전역객체를 생성하는 함수를 호출하도록 문서화해서 전달하겠어. 기사에서는 유용하다고 하는데 언제 써먹을지는 아직 모르겠음.

[C++] lambda, capture by value 주의사항

capture by value 가 lambda 식에 언급된 외부 변수의 값을 무조건 복사한다고 생각하면, 잘못된 코드를 만들 수 있다. 아래 코드와 같이 m_value 에 대해 capture by value 를 했다고 생각하지만

class BadClosureMaker
{
public:
	BadClosureMaker() : m_value(2012) {}

	std::function<int (void)> CreateClosure()
	{
		auto f = [=](void)
		{
			return m_value;
		};
		return f;
	}

private:
	int m_value;
};

실제로는 m_value를 capture 한 것이 아니라 BadClosureMaker 의 this 를 capture 한다. 만약 CreateClosure 에 의해 생성된 closure가 사용되기 전에 BadClosureMaker 가 delete 된다면, 잘못된 값에 접근한다.

[C++] Lambda의 Recursive

lambda 는 자체적으로 recursive 를 사용할 수 없다. overview of the new C++1, slide 80 에서 다음과 같은 구현을 제시한다.

std::function<int(int)> factorial =
    [&](int x) { return (x==1) ? 1 : (x * factorial(x-1)); };

한 가지 주의할 점이 있다. factorial object 를 다른 std::function<> object 로 복사 한 후에 factorial object 가 파괴되면 bad_function_call 에러가 발생한다.

std::function<int(int)> f;
{
	std::function<int(int)> factorial = 
		[&](int x) { return (x==1) ? 1 : (x * factorial(x-1)); };
	f = factorial;
}

cout<<f(4)<<endl; //bad_function_call!! 
  1. http://www.artima.com/shop/overview_of_the_new_cpp

[C++] bit storage 작성 시 주의사항

pvs-studio 1 를 이용하여 레거시 코드를 검사했을 때 발견한 경고를 기반으로 구성했다.

다음 코드는 8byte bitSet 변수가 for 문의 loop를 돌 때마다 이진수로 표현했을 때 1, 11, 111, 1111, ….. 값이 되길 바라는 코드다. 64번째 돌았을 때 bitSet 변수가 2진수로 1이 64개가 있기를 바랬다.

	
typedef unsigned __int64 BitSetType;

	BitSetType bitSet = 0;
	for (int i = 0; i < 64; ++i)
	{
		BitSetType temp = 1 << i;
		bitSet |= temp;
	}

하지만 i 가 31이 된 루프에서 bitSet 변수는 1이 64개 모두 채워진다. 왜일까.

어셈블리 코드로 확인을 해보니

	
BitSetType temp = 1 << i;
 mov         eax,dword ptr [i]  
 mov         ecx,1  
 mov         dword ptr [rsp+18h],ecx  
 movzx       ecx,al  
 mov         eax,dword ptr [rsp+18h]  
 shl         eax,cl  
 cdqe  
 mov         qword ptr [temp],rax

7번 라인을 실행한 후 eax 가 0x80 00 00 00 값으로,
8번 라인의 cdqe2 명령이 eax(4바이트)를 rax(8바이트)로 변환하면서 sign extension3이 일어나 rax 의 상위 4바이트가 모두 1로 채워졌다.

sign extension 을 막기 위해 처음 코드로 돌아가 6번 라인을 다음과 같이 고쳐보았다.

BitSetType temp = 1u << i;

31번째 루프에서 더 이상 sign extension이 일어나지는 않는다. 하지만 64번 루프까지 모두 마쳤더라도 bitSet 변수는 모두 1로 채워지지 않는다. 이미 위의 어셈블리 코드에서 보고 알았겠지만 unsigned int (위에서는 1u) 를 갖고는 상위 4바이트를 1로 채울 수가 없다. 다음과 같이 변경해야 한다.

BitSetType temp = 1ull << i;

64번의 루프 동안 한 비트씩 잘 채워나가고 생성된 어셈블리 코드도 옳게 보인다.

BitSetType temp = 1ull << i;
 mov         eax,dword ptr [i]  
 mov         ecx,1  
 mov         qword ptr [rsp+18h],rcx  
 movzx       ecx,al  
 mov         rax,qword ptr [rsp+18h]  
 shl         rax,cl  
 mov         qword ptr [temp],rax

결론 : 웬만하면 std::bitset 을 사용하고, 필요하다면 shift 연산자의 왼쪽 값의 타입 지정할 때 주의하자.

  1.  http://www.viva64.com/en/pvs-studio/
  2. http://siyobik.info/main/reference/instruction/CBW%2FCWDE%2FCDQE
  3. http://en.wikipedia.org/wiki/Sign_extension

[C++] 가위 규칙 Scissors rule

Clean Code 를 읽다가 가위 규칙이라는 말이 나와서 찾아보았다.

가위 규칙(Scissors rule)이란?

Write your C++ class header so that the public part is first and the private part is last. You then take a pair of metaphoric scissors and snip off the tail end of a class header file and give the results to users of the class. Everything before the private part is what you need to use the class. This makes it a tad bit easier for readers of a class header to focus in on what they need to know about the class. 

from http://tech.groups.yahoo.com/group/extremeprogramming/message/2405

[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)

[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를 참고하자.