[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

[subversion] ahnksvn 을 버리다.

tortoisesvn 을 사용하는데 1.7버전의 업데이트 속도가 빠르다고 하여 클라이언트만1.7로 (tortoisesvn 1.7.5, svn1.7을 지원하는 ankhsvn버전) 로 버전업하였다. (1.6인 서버를 1.7로 올리는 것은 건의해보았으나 이런저런 사정으로 안될것같음 -_-; 밀어붙이면 왠지 이 기사1 에서 언급한 문제아가 될 것 같은 느낌이랄까 ) 문서에는 svn server 1.6 과 svn client 1.7 도 상관이 없다고 했음. (하위 호환성을 지원한다…)

문제는 그때부터 발생했음. svn:keywords 를 property 에 설정한 파일을 이곳저곳에서 externals 로 당겨 쓰는데 update 할 때마다 keywords property가 자주(매번은 아니고!) 삭제됐다!!! 이걸 해결하려면 externals 로 당겨온 working copy 에서 다시 property 를 설정해서 commit 해야 하는데.. 이때 본문을 조금이라도 수정하지 않고 property 만 변경해서 넣으면, 이 파일을 update 받은 모든 사람들이 또 keywords 가 삭제되는것이다. –; 무슨 탁구도 아니고 민폐의 핑퐁… 그래서 1.7 로 올린 사람들 중 1.6으로 눈물의 downgrade 를 하는 사람들이 생기고, 마지막까지 버티던 tt군과 나…

그런데 어느 순간부터 tt군은 문제없다고 하여 역학조사(?)에 들어갔으니 밝혀진 재현법은 keywords 가 들어있는 파일을 commit 한 후, visual studio svn add-in 인 ahnksvn 에서 update 를 받으면 해당 문제가 발생하는 것이었음. add-in인 만큼 쓰기 편했지만 삭제하였다.

tortoisesvn 만 쓸까 하다가 찾아보니2

대안

  1. visual svn 을 산다. (tortoisesvn api 를 쓰는지라 경험했던 문제는 없을 듯),  유료 49$
  2. vs external tools 로 등록해서 쓸 수 있게 하는 vbs 와 vssetting 이 있더라. 세팅이 약간 귀찮지만 공짜 (http://stackoverflow.com/a/463049 요 댓글로 입수함. garry씨 블로그글은 http://garrys-brain.blogspot.com/2007/07/tortoisesvn-and-visual-studio.html 이것) 여기다가 추가로 요것(TUsvnAddIn – TortoiseSVN addin for Visual Studio) 까지 깔면 그럭저럭 쓸만하다.

자기돈으로 ssd 설치한 사람이 있어서 나도 그냥 살까 했지만 아 이럼 너무 그리스시민군3 같은 느낌이 들잖아 -_-; 싶어서  2번으로 선택. 끝~

 

  1.  http://kblog.popekim.com/2011/12/blog-post_16.html
  2.  뭔가 대안을 찾을 때 http://alternativeto.net/ 이 괜찮은듯~
  3.  고대 그리스 폴리스의 시민들은 직접 무장을 준비하여 유사시 중장보병(hoplites)으로 참전했다. from http://bit.ly/zoEJFt

[TEAMCITY] 빌드 도중 Process exited with code -1073741515 에러가 발생

새로 만든 서버 솔루션을 팀시티에 붙였는데 빌드가 끝나고 유닛테스트를 실행하다가 위와 같은 에러가 발생했다. 검색해보니 agent 가 권한이 약한 거라고 한다. 윈도우즈 서비스 페이지로 확인했으나 권한 문제는 아니었다.

teamcity agent 가 유닛테스트를 하려고 실행하다가 난 문제라서 아예 문제가 발생한 agent 컴퓨터로 접속하여 문제가 발생한 파라미터대로 실행을 해보았다.

솔루션에서 사용하는 sqlncli10.dll 이 없다는 결과. 다행히 금방 해결될 듯하다. 검색해보았더니1  sqlncli10.dll 은 SQLServer CD에 있는 인스톨러를 이용하면 sql server 나 client 를 깔지 않아도 설치할 수 있다. x64 에서는 x64/cd/x64 폴더에 sqlncli.msi 파일을 실행하면 된다.

문제해결 참고용 마인드맵

iThoughtHD 어플 구매 기념으로, 책을 읽고 개인위키에 모아놨던 자료를 마인드맵으로 구성했다. 참고된 책으로는 HowToSolveIt, 컨설팅의 비밀, 대체 뭐가 문제야 등임. 실용적으로 쓰일 수 있으면 좋겠다. 계속 업데이트를 할 수 있게 되기를.

(PDF버전)

PS : 트래픽이 많아져서 미디어(png, pdf) 링크를 드랍박스 public 폴더도 변경하였습니다.

[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

[ios] 동기화 세션이 시작되지 않았기 때문에

동기화 세션이 시작되지 않았기 때문에 iTunes가 xxx 으로 동기화할 수 없습니다.

라고 뜨고 itunes를 이용한 ipad 동기화가 계속 진행되지 않았다.

한글 검색에서는 해결책을 못찾아서 대충 영문으로 검색하여 발견. 영문으로는 “Sync session fails to start …” 메시지다. itunes 끄고, ipad 케이블에서 뺐다가 끼우고 itunes 켜서 다시 하면 잘될꺼라고. NedNC 님의 답 1 대로 했더니 잘된다.

포스팅하다보니 결국에는 이 링크(http://support.apple.com/kb/TS2529?viewlocale=ko_KR&locale=ko_KR) 를 찾았지만 한글로 검색이 힘들었다. 이럴때는 검색 효율을 위해 에러 메시지에 에러코드도 병기를 잘했으면 싶네.

  1. https://discussions.apple.com/thread/3384205?start=0&tstart=0

[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