서로 다른 DB 머신에서 데이터 트랜잭션 처리

오랫만에 이곳에 글을 쓴다. 신입사원으로 지원시 열심히 썼었던 잊고 있던 오답노트를 다시 정리해본다. 경력이 3년이 넘었는데 아직도 이러한 문제조차 모르는 내 자신을 반성하며 다시 정리한다.

판교의 N 게임사에서 받은 질문.

Q. DB 머신이 물리적으로 서로 다른 머신일 때 어떠한 DB 작업시 트랜잭션 처리를 어떻게 하는가?

A. 하나의 단일 머신에서 처리해서 그러한 것에 신경 쓰지 못했다.

역시 정답을 말하지 못했다. 사실 이러한 처리가 필요하다는 것을 꽤 오래전에 인지했지만 실제로 해본적이 없어서 제대로 대답을 할 수 없었다. 실제로 3년간 했던 프로젝트들 전부 단일 DB 머신에서 처리했던게 사실이고.

다시 찾아본 정답은 다음과 같다.

출처 : 네이버지식인 (http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=10205&docId=68228257)

MSSQL을 기준으로하며, 서로 링크드 서버로 연결되어 있어야 한다는 전제가 붙는다.

변경할 테이블에 트리거를 생성한다.

CREATE        TRIGGER dbo.트리거명 ON dbo.A서버테이블명
FOR UPDATE
AS

DECLARE @I_NO char(4) --변경 전
DECLARE @D_NO char(4) --변경 후

Select  @D_NO =NO --변경 전 값
From  DELETED

Select  @I_NO =NO --변경 후 값
From  INSERTED

--분산트랜잭션 처리(링크드 서버 UPDATE시 에러 없으면 에러 나는 경우가 존재합니다.)
SET XACT_ABORT ON

UPDATE [220.123.123.120].B서버DB명.DBO.B서버테이블명
SET NO = @I_NO
WHERE ID = @D_NO

SET XACT_ABORT OFF

결론적으로 문제의 해답은 변경할 테이블에 트리거 처리를 한다. 이고 그것을 위해 XACT_ABORT_ONXACT_ABORT_OFF 두개의 명령어를 사용해야한다는 것이다.

그렇다면 방금 처음 본 XACT_ABORT_ON/OFF가 도대체 뭔지 찾아보았다. 이에 대한 내용을 가장 잘 설명해준 글이 있었다.

http://fbshot.blog.me/80162695844

가장 중요한 차이점은 다음과 같다.

만약 프로시져 안에 INSERT 쿼리가 두개가 있는 상태에서 두번째 INSERT 쿼리에서 오류가 난다면,

  • XACT_ABORT_ON을 해놓으면 트랜잭션 안에 묶인 여러 쿼리 중 하나만 실패해도 전부다 실패처리하고 롤백한다.
  • XACT_ABORT_OFF일 때에는 첫번째 INSERT는 적용되고 두번째 INSERT 쿼리만 롤백한다.

이것을 이용해 배치쿼리에서 트랜잭션 처리에 대한 문제를 해결할 수 있다.

면접하면서 배치쿼리를 사용해봤냐는 질문도 받았는데 이 내용도 관련되어 있던 내용이었다.

배치쿼리와 트랜잭션에 대해 앞으로 잘 알아두어야 할 것 같다.

 

2016년 4월 4일 추가.

회사 동료 광연씨와 얘기해본 결과, 메시지큐나 웹서버 등의 미들웨어를 두고 여기에서 트랜잭션 처리를 하는게 더 좋지 않느냐는 결론에 이르렀다. 링크드서버로 연결된 DB를 이용해 트랜잭션을 이용하는 것은 적절하지 않은 해답일 것 같다. 메시지큐나 다른 미들웨어를 이용하여 이 문제를 해결하는 방법을 찾아보고 있다.

쓰레드 처리를 위한 Auto Lock/Unlock 클래스

오늘 공부의 목표는 Lock/Unlock의 자동화였다.

CRITICAL_SECTION을 사용할 때 이것을 초기화, 삭제해주는 것뿐만 아니라 EnterCriticalSection/LeaveCriticalSection을 일일히 수동으로 해준다는 것은 귀찮을 뿐만 아니라 사람의 실수에 의하여 문제가 생길 소지가 있는 부분이기 때문. 만약 언락을 안하면 데드락 상황에 빠질 수 있기 때문에 반드시 언락을 해줘야하는데 어떤 프로그램 로직상 혹은 작성자의 실수로 이런 경우가 생길 수 있다. 그래서 그걸 아예 자동화해주는게 Auto Lock/Unlock 클래스.

두개의 헤더파일로 구성된다.

lock.h 파일

#include <iostream>
#include <Windows.h>
#include <process.h>

class Lock
{
public :
	Lock()
	{
		// std::cout << "INIT" << std::endl;
		InitializeCriticalSection(&cs_);
	}

	~Lock()
	{
		// std::cout << "DELETE" << std::endl;
		DeleteCriticalSection(&cs_);
	}

	void lock()
	{
		// std::cout << "LOCK" << std::endl;
		EnterCriticalSection(&cs_);
	}

	void unlock()
	{
		// std::cout << "UNLOCK" << std::endl;
		LeaveCriticalSection(&cs_);
	}
private :
	CRITICAL_SECTION cs_;
};

크리티컬 섹션을 생성하고 초기화와 삭제, 락과 언락을 해주는 메서드로 이루어져있다.

그다음은 AutoLockUnlock.h 파일

#include <iostream>

#include "lock.h"

class AutoLockUnlock
{
private :
	static Lock lock_;
public :
	AutoLockUnlock()
	{
		// std::cout << "AutoLockUnlock() 생성자" << std::endl;
		lock_.lock();
	}

	~AutoLockUnlock()
	{
		lock_.unlock();
		//std::cout << "AutoLockUnlock() 소멸자" << std::endl;
	}
protected :
private :
};

Lock AutoLockUnlock::lock_;

실제사용은 autolockunlock.h 파일을 인클루드하고 스코프를 만들어주기 위해 락이 필요한 부분을 {, }로 감싼 다음 해당 블록 안 제일 첫줄에 AutoLockUnlock autolockunlock; 처럼 하나 선언해준다. 블록이 시작되면 AutoLockUnlock의 생성자에 의해 크리티컬섹션의 초기화와 락이 시작되고 코드 블록이 종료되면 AutoLockUnlock의 소멸자에 의하여 언락이 되고 크리티컬섹션 삭제까지 된다.

아래와 같은 쓰레드 함수가 있다면 맨처음에 AutoLockUnlock 타입을 같은 변수를 생성함으로써 초기화/락/언락/삭제를 한번에 할 수 있다.

unsigned int WINAPI ThreadFunc(void * arg)
{
	AutoLockUnlock autolock; // 여기서 오토락을 건다. 생성만 해주면 알아서 만들어지고 알아서 삭제된다.
	std::cout << "쓰레드 시작 :: " << target << std::endl;

	for (int i = 0; i < 50000; i++)
	{
		target = target + 1;
	}

	for (int i = 50000; i < 100000; i++)
	{
		target = target - 1;
	}
	std::cout << "쓰레드 종료 :: " << target << std::endl;
	return 0;
}

중요한건 {, }에 의한 코드블록에 의해서 AutoLockUnlock 클래스의 생성자와 소멸자가 호출된다는 점. 이것을 이용하여 자동화된 Lock/Unlock을 구현한다.

쓰레드를 쓸 때는 항상 이러한 자동화된 락과 언락을 사용함으로써 데드락에 빠지는 사태를 막을 수 있다. 쓰레드를 쓸 때는 무조건 사용할 것.

cin을 쓸 때 입력을 한번 지나가는 현상을 해결하는 방법

오늘 공부하던 중 발견한 부분.

cin을 통해 문자 하나를 입력 받은 후 getline으로 다른 문자열을 입력 받으려고 할 때 문제가 생긴다. 입력을 한번 건너뛰는 현상.

cin으로 입력 받은 다음 다른 이벤트(?)가 더 오는 것 때문이라고 한다. 이 문제를 해결하기 위해서 std::fflush(stdin);을 이용하면 이 현상이 사라진다. cin을 이용할 때는 반드시 기억해놔야할듯.

std::string temp;
std::cin >> temp;
std::cout << "start" << std::endl;
int rtn = std::fflush(stdin);

std::string msg;
std::getline(std::cin, msg);
std::cout << "end" << std::endl;

C++로 만든 계산기

회사 스터디 커리큘럼 중 만들어 본 C++로 구현된 사칙연산 계산기이다.

사실은 TCP 서버/클라이언트를 공부하는 와중에 책에 나온 예제가 너무 쉽다하여 계산기 기능을 가진 서버와 클라이언트로 바꾸기로 해서 만들게 되었다. 책에는 클라이언트에서 입력값 3개를 받도록 한다. 첫번째로는 상수, 두번째는 연산자, 세번째로는 상수를 받아서 서버로 보내면 서버는 두 상수를 연산자에 따라 계산하고 클라이언트로 다시 전송하는 방식이다. 이거야 뭐….;;

이 방법도 너무 쉽기 때문에 아예 수식으로 입력 받도록 만들었다.

최소한 인터넷에 떠도는 대부분의 사칙연산 계산기보다는 내가 만든게 훨씬 더 정교하고 정확하다고 말할 수 있을 것 같다.

만드는 과정에서 어려웠던 점과 해결하는 알고리즘 몇가지에 대해 기억해두려고 여기에 적어둔다. 혹시 누가 이 글을 보게 된다면, 내가 생각했던 부분이나 내 코드에 문제가 있다면 지적해주면 좋겠다.

 

일단 수식을 입력 받은 후 제일 처음 할일은 수식의 빈칸을 모두 제거하는 일이다. 수식의 처음부터 끝까지 검색하며 빈칸을 모두 제거하는 함수를 하나 만들어 적용한다.

1. 일단 괄호 문제

사칙연산에서 괄호가 들어가면 계산의 우선순위가 바뀐다는 것이 문제인데 이에 대한 해결책은 후위 연산과 스택을 응용하는 방법이다. 여기에 대해서는 인터넷에 이미 많은 글들이 있어서 따로 여기에 적을 필요는 없는 것 같다. 내 경우에는 vector와 stack을 둘다 응용하며 썼다.

보통 인터넷에서 찾은 사칙연산 계산기는 딱 여기까지만 해법이 나와있는 경우가 대부분이다. 난 여기에 보완하여 아래의 문제사항들을 대응할 수 있도록 바꾼다.

2. 음수 연산자 문제

위에 괄호 문제를 해결하고 나니 음수가 문제가 되었다. 예를 들면, -2+3 같은 식은 2 앞에 붙은 -를 연산자가 아니라 해당 상수와 같이 묶어서 봐야한다는 것. 그리고 여기에는 괄호에 마이너스가 붙는 경우까지 생겨난다. 예를 들면, -(2+3) 같은 수식이다. 위에 1번에서 괄호문제를 풀기 위해 후위 연산으로 변경하고 상수 스택과 연산자 스택으로 만들었다면 – 기호를 하나의 연산자로 취급하기 때문에 같이 계산할 대상 상수가 없어서 오류가 나게 된다. 이 문제를 해결하기 위해서는 음수를 0-x와 같은 식으로 바꿔줘야한다. 예를 들면 -2+3은 0-2+3으로, -(2+3)과 같은 경우는 0-(2+3)과 같이 바꿔줘야한다. 이렇게 하면 후위연산으로 전환 후에 스택으로 계산할 때 같이 비교할 대상이 있기 때문에 정상적으로 계산이 된다.

하나 주의할 것은, 만약 음수 기호 앞에 0이 아닌 다른 상수가 왔다면 이 경우에는 정상적으로 계산이 되므로 더이상 어떤 처리를 하면 안된다. 아래에 나오는 이중연산자 문제를 미리 처리하고 이 음수 연산자 문제를 처리하도록 만들었기 때문에 아래와 같은 정리할 수 있다.

  1. 괄호 앞에 -가 있는 경우 [ex : -(3+4) 라면]
  2. 0 – x로 변경한다. [ex : 0-(3+4)]
  3. 하지만 해당 부분 앞에 0이 아닌 상수가 있는 경우에는 [ex : …3-(3+4)] 변경하지 않는다.
  4. 그러므로 n번째에 ‘(‘가 오고 n-1번째에 -가 왔다면 n-2번째에는 반드시 0이 아닌 상수이어야하며 그렇지 않은 경우에는 0-x로 변경한다.
  5. 그런데 이미 이중연산자는 이전에 걸러져서 올 것이므로 위와 같은 상황이 오는 경우는 n-2번째가 ‘)’이거나 ‘(‘인 경우 밖에 없다.
  6. 따라서 이러한 경우는 해당 부분이 ‘)-(‘이거나 혹은 ‘(-(‘인 경우이므로, 이런 경우에는 각각 ‘)+0-(‘와 ‘(0-(‘으로 변경한다.

위의 6가지 규칙을 차례대로 따라 수식을 처리하면 된다. string의 find와 replace 메서드를 가지고 위 처리를 할 수 있다.

정말 사람 애먹인 작업이었다. 인터넷으로 찾아봐도 이 문제를 해결하는 방법에 대해서는 아무 도움을 받을 수 없어서 혼자 생각했다. 한참 골치아팠던 문제.

3. 소수점 문제

위에 문제들을 해결하고나니 이번엔 정수형 상수만 처리가 되었다. 소수점 처리도 하기 위해 후위연산 변환 후에는 double 형으로 처리할 수 있도록 변경한다. double 형으로 하면 소수점 이하 6자리까지는 나온다. 만약 이보다 더 강력한 정밀도를 원한다면 char 배열을 응용하여 계산하면 가능하다. (…지만 너무 귀찮아서 내가 해보진 않았다.) 소수점이 들어가게 되면 처리하고 가공할 때 숫자, 사칙연산자 뿐만 아니라 . 까지도 처리한다는 것에 유의해야한다.

4-1. 잘못된 수식에 대한 문제 – 수식의 맨마지막에 연산자가 오는 경우

수식계산 중 처음부터 아예 잘못된 수식이 입력될 가능성이 있다. 예를 들면 수식의 맨마지막에 연산자가 붙는 경우, 예를 들면, ‘2+3-‘와 같은 경우. 이 역시도 후위연산 전환후 계산과정에서 계산할 대상이 없어지게 되므로 프로그램이 죽는 문제가 발생한다. 수식의 맨마지막에 숫자가 아닌 다른 기호가 왔는지 찾아서 처리한다.

4-2. 잘못된 수식에 대한 문제 – 수식의 괄호의 갯수가 맞지 않거나 짝이 맞지 않는 경우

정상적인 수식이라면 여는 괄호와 닫는 괄호의 갯수가 맞아야하며 서로 짝이 맞아야한다. 이 상황을 만족시키기 위해서 난 다음과 같이 생각했다.

  1. 수식에 맨처음부터 검색했을 시 나오는 괄호가 닫는 괄호이거나, 수식의 맨뒤에서부터 검색했을 시 나오는 괄호가 여는 괄호라면 이 수식은 잘못되었다. 따라서 find나 find_first_of 등의 메서드를 이용해서 여는 괄호와 닫는 괄호가 처음 나오는 인덱스를 구한 다음 두 인덱스를 검사해보면 된다.
  2. 수식의 괄호의 갯수를 맞춰보기 위해서는 flag를 하나 설정하고 초기값을 0으로 둔다. 앞에서부터 검색해가며 여는 괄호 ‘(‘가 나왔을 시 flag를 1 더하고, 닫는 괄호가 ‘)’가 나왔을 시 flag를 1 뺀다. 수식의 끝까지 다 돌았을 때 여는 괄호와 닫는 괄호의 갯수가 같았다면 flag는 0이 되어야한다. 만약 0보다 크다면 여는 괄호가 더 많은 것이고 0보다 작다면 닫는 괄호가 더 많은 수식이다.

4-3. 잘못된 수식에 대한 문제 – 수식에 문자가 들어온 경우

간단하게 하나씩 검색해가며 문자가 들어있는지 판단한다. 문자열 검색은 너무 간단해서 적어둘 필요가 없을 것 같다. C/C++을 해본 사람이라면 누구나 다 간단하게 할 수 있다.

4-4. 잘못된 수식에 대한 문제 – 이중연산자가 있는 경우

한줄로 수식을 입력받는 경우 연산자가 이중으로 입력될 수 없다. 예를 들면 3+-2, 3*-2 등의 경우에는 처리할 수 없다. 한줄로 입력받는 사칙연산의 경우 연산자가 겹칠 경우에는 항상 괄호로 묶어야하며 그렇지 않고 연산자가 이중으로 온 경우에는 계산할 수 없다. 수식이 입력되었을 때 현재 글자와 이전 글자를 비교함으로써 이중연산자가 있는지 검색하고 있다면 오류로 판단한다.

 

위 프로그램을 클라이언트와 서버로 나누었다. TCP로 통신하게 되며 여기에 스레드를 통한 코드는 없다. 그냥 한번 1:1 대응 통신이다.

서버 프로그램 다운로드 :: CalcServer

클라이언트 프로그램 다운로드 :: CalcClient

여기까지 하면 대부분의 계산을 다 처리할 수 있다. 남은건 서버에서 계산한 결과를 클라이언트로 보내는 것. 나같은 경우는 클라이언트로 패킷을 보낼 때 처음 한바이트를 에러코드로 넣었다. 때문에 클라이언트에서 잘못된 수식을 입력하면 서버가 무슨 이유로 계산이 안됐는지 알려주고 클라이언트는 그 이유를 화면에 보여준다.

여기까지 하고 테스트 해보니 한줄로 입력가능한 사칙연산은 대부분 다 처리할 수 있었다. 거의 한 98%? 후…

간단한 사칙연산 계산기 하나 만드는게 얼마나 어려운지 몸소 체험했다. 이 문제의 핵심은 첫째로 중위연산을 후위연산으로 전환하고 스택을 이용하여 계산해가는 것. 그리고 둘째로 후위연산으로 전환할 때 나오는 문제점을 해결해나가는 것이다. 후위연산과 스택을 응용한 계산을 처리하지 못한다면 아무 진행도 할 수가 없다. 이 원리를 꼭 이해해야만 다음 과정들을 해결해나갈 수 있다.

네트워크 공부하다가 어쩌다보니 문자열과 수식연산에 대해 많이 공부하게 된것 같다.

 

위에 98%는 해결했다고 한 이유… 그 2%. 아직 해결하지 못한 문제가 남아있어서 이것도 적어둔다.

1. 수식에 0/x가 입력되거나 x/0이 입력되는 경우 혹은 계산과정 중 이런 경우가 나오는 경우

일단 수학에서는 0 나누기 x는 0이고 x 나누기 0은 무한대다. 컴퓨터에서는 0 나누기 x를 0, x 나누기 0은 오류로 판단한다.

입력된 수식 중 0 / x가 있는지 검색하거나 x / 0이 있는지 검색하는건 그리 어렵지 않다. 문자열에서 해당하는 부분을 검색하면 되니까.

문제는 입력된 수식을 계산하는 과정중 이러한 부분이 나오면 문제가 된다. 예를 들면, 100+3/(3-3)*(3-3) 이런 수식의 경우에는 처음부터 0 / x나 x / 0이라는 부분은 없기 때문에 단순히 문자열검색으로는 문제가 없다. 하지만 계산하는 과정 중에 이런 부분이 나오기 때문에 계산 과정 중 오류가 나고 프로그램이 죽게 된다. 이 문제를 해결하려면 후위연산으로 전환한 후에 스택을 처리하며 연산자 스택에 나누기가 있을 때 계산 스택에서 0이 있는지 확인해보는 수 밖에 없다.

이 문제는 스택 구조만 좀더 생각해보면 충분히 해결할 수 있을 것으로 보이는데 현재는 지쳐서 중지.

2. 괄호 앞에 상수가 나오는 경우

이건 만들다가 깜빡 잊고 안 넣은건데 나중에서야 생각났다. 수학의 규칙에서는 괄호 앞에 상수와 상수끼리는 연산자를 생략할 수 없다. 예를 들면, 2*3을 23이라고 쓸 수 없는 것과 마찬가지다. 연산자를 생략가능한 경우는 문자 즉, 변수와 같이 쓸 경우네는 2*x를 2x라고 쓸 수 있다. 지금 내가 만든 것은 상수항만 입력 받은 수식이므로 당연히 연산자는 생략될 수 없다.

중요한건 괄호가 있는 경우에도 연산자는 생략할 수 없다. 따라서 2(3+4)라는 수식은 실제로는 이렇게 써서는 안되는 수식이다.

이 문제에 대해서는 예전에 이게 되는거다 안되는거다 한번 인터넷에서 논란이 되었었는데, 난 이 경우에 대해서는 상수끼리는 연산자를 생략할 수 없다.라고 생각하고 그렇게 규정하기 때문에 잘못된 수식으로 간주한다. 그리고 C++ 컴파일러도 이런식의 수식은 잘못되었다고 판단하기 때문이다. 2(3+4)가 2*(3+4)라는 것은 인간이 쓰기 편하게 하려고 그렇게 약속한 것일뿐 엄밀히 기계적으로 따졌을 때는 틀렸다고 생각한다.

어찌되었던 괄호앞에 상수가 나오는 경우에는 잘못된 수식으로 봐야하는데 이 경우에 대한 처리를 넣지 못했다. 이것도 스택 구조만 조금 생각하면 충분히 해결할 수 있을 것 같다.

3. 불완전한 소수로 입력된 경우

소수를 입력할 때 0.1, 3.0처럼 입력하지 않고 .1, 3. 처럼 불완전하게 입력하는 경우에 대한 문제다. 테스트 해본 결과 double 형으로 변경하는 과정에서 이러한 숫자들은 전부 완전한 소수형태로 자동전환이 되었다. 하지만 수학에서 이렇게 쓰는 경우는 없으므로 체크하는게 좋을 것 같다. 이건 구현하기 쉬운 부분이라 일단 패쓰.

PF_INET과 AF_INET의 차이

네트워크 프로그래밍 책을 둘러보던 중 소켓 생성할 때 PF_INET으로 생성하는 것을 보고 이게 뭔가 싶었다. 내가 배울 때는 항상 AF_INET으로 배웠기 때문. 책에도 이에 대한 내용이 부족해서 검색해보니 여기에 대한 정보가 많았다.

굳이 정리하자면 PF_INET과 AF_INET은 둘다 상수값 2로 같은 값을 나타내지만 PF_INET은 프로토콜 설계에, AF_INET은 주소체계에 쓰이는 것이 바람직하다고 한다. 그래서 socket() 함수에서는 PF_INET을 sockaddr_in 구조체의 sin_family에는 AF_INET을 쓰는 것이 좋다고 한다.

예전에 socket() 함수를 설계할 때 전문가들은 하나의 프로토콜 안에서 서로 다른 주소체계를 쓸 수 있게 될것이라 생각하여 PF_INET을 만들었는데 현재의 IPV4에서도 아직까지 바뀐게 없어 그냥 그대로 사용되고 있다고 한다.

socket() 함수에서도 AF_INET을 써도 잘 작동되긴 하는데 어쨌튼 의미적으로는 다른 것이라고 하니 왠만하면 지켜가야할 듯.

참고자료 :: 네이버에 검색하면 부지기수로 있다;;;

string 타입에 빈칸을 포함하는 문자열을 입력 받는 방법

string 타입의 변수 str에 문자열을 넣을 때

cin >> str;

처럼 하면 문자열은 들어가는데 문자열에 빈칸이 들어가면 입력이 안되었다. 정확히는 안된다기 보다는 되는 것처럼 보이고 다음 cin으로 입력을 받을 때 그냥 무시처리 된다는 것이지만. 문자열 중간의 공백 때문에 생기는 것인데 이것을 해결하기 위해 인터넷을 찾아본 결과 다음처럼 하니까 해결되었다.

getline(cin, str, 'n');

getline 함수가 아닌 cin만 가지고는 공백이 들어간 문자열은 받을 수 없다고 한다.

자바나 액션스크립트에서는 이런 고민할 필요가 없었는데 C++에 오니 참 이런 것 하나 처리하기에도 민감할 필요성이 있는 것 같다.

힙과 스택

내가 면접 때 많이 들어봤던 질문 중의 하나였다.

“힙과 스택에 대하여 설명해보세요.”

C/C++을 공부하면서 당연히 배우는 과정 중의 하나인데 문제는 이런 내용은 C 처음 배울 때 나온 내용이라 기억의 저편으로 멀리 가버렸다는 점. 기억을 되살리기 위해 다시 정리해본다.

스택의 특징은…

  • 프로그램이 자동적으로 할당한 메모리 영역이다.
  • 자동변수와 파라미터들이 생성되고 스코프가 끝나면 사라지는 메모리 영역.
  • 함수가 실행된 뒤 원래 실행위치로 돌아오는 리턴값 역시도 스택에 저장된다.
  • 함수 안에서 선언된 변수는 모두 스택으로 들어간다.
  • 함수를 호출했을 때 현재 상태에 대한 값도 스택으로 들어간다.
  • LIFO 구조를 가진다. 먼저 들어간 자료가 가장 나중에 나오게 되며, 가장 나중에 들어간 자료가 가장 처음에 나오게 된다.
  • 윈도우 프로그램의 경우 기본 스택메모리의 크기는 1MByte이다.
  • 스택의 크기를 넘어가게 되면 Page Fault 인터럽트(즉, 스택오버플로우)가 발생하게 된다.
  • 모든 포인터 역시도 스택에 올라간다. 포인터 역시도 하나의 변수라는 것을 잊지말 것.
  • 사용할 스택의 크기는 컴파일 하는 순간 모두 정해진다. 따라서 실행 도중 메모리의 크기를 변경할 수 없다.
  • 스레드가 여러개라면 각 스레드마다 고유한 스택영역이 준비된다.
  • 하나의 메소드를 실행하기 위한 스택메모리의 묶음을 스택프레임이라고 부른다.
  • 어떤 변수를 선언했다면 변수명 자체는 스택에 변수 내용은 힙에 저장된다.

힙의 특징은…

  • 프로그래머가 스스로 할당한 메모리 영역이다.
  • 힙은 프로그램이 동적으로 할당받아 사용되는 메모리 영역. C에서의 malloc을 통해 생성하고 free를 통해 해제하며 C++에서는 new를 통해 생성하고 delete를 통해 메모리를 해제한다.
  • 힙 메모리가 부족하게 되면 메모리 이상현상으로 프로그램은 종료되게 된다.
  • 사용할 힙의 크기는 프로그램 실행 중 정해진다.
  • 동적 메모리할당은 프로그램 사용자들이 얼마나 데이터를 입력할지 예측할 수 없는 경우에 사용한다.
  • 여러 스레드가 있는 경우 힙 영역은 각 스레드가 공통으로 사용할 수도 있다.

이외에도 데이터영역과 텍스트영역(코드영역)이 있다.

데이터영역의 특징은…

  • 프로그램이 실행되고 난 후 종료될 때 까지 지워지지 않을 메모리 공간이다.
  • 따라서, 전역변수, Static 변수 등이 저장된다.
  • 프로그램당 한개의 영역이 할당된다.

텍스트영역(코드영역)의 특징은…

  • 프로그램의 실행코드를 저장한다. 프로그래머가 만든 코드, 함수는 모두 여기에 저장된다.
  • 역시 프로그램당 한개의 영역이 할당된다.

 

위의 내용들을 찾을 수 있었다. 다시금 찾아보니 생각보다 내용도 많고 차이점도 여러가지인 것 같다. 앞으로 더 찾아보며 여기에 추가해야겠다.

 

<참고자료>

  • http://blog.naver.com/brosvaby?Redirect=Log&logNo=165225008
  • http://panic.kldp.org/node/199
  • http://blog.naver.com/ysjin1212?Redirect=Log&logNo=110128934379
  • http://blog.naver.com/0bloodwind0?Redirect=Log&logNo=20127908176
  • http://blog.naver.com/zelet7?Redirect=Log&logNo=140015294688

랜덤확수의 확률 변경과 분포에 대한 문제

이 질문 역시 내가 면접에서 질문 받았던 문제이다. 틀렸던 문제를 다시 한번 살펴보고 공부하는 차원에서 오답노트에 적어둔다. (Q는 면접관님, A는 내가 답변한 것이다. 존칭은 생략한다.)

Q. 랜덤함수에서 0과 1이 나올 확률이 같은가?
A. 같다. 내가 알기로는 0~1까지의 소수가 나오는 것으로 알고 있다. (여기서 대답을 잘못했다. 플래시의 랜덤함수와 C에서의 랜덤함수를 헷갈렸던 것이다.)
Q. 정말? 그렇다면 0~10까지 나오게 하려면 어떻게 해야하는가?
A. r이 그 랜덤함수값이라고 한다면 (int)(r * 10)으로 구할 수 있다.
Q. 그렇게 한 경우에는 0~10까지 모든 수가 나올 확률은 같은가?
A. 같다고 알고 있다.
Q. 만약 1~5까지의 어떤 수를 뽑는 랜덤함수가 있다면 확률을 바꾸고 싶다. 1,5는 가장 적게 나오도록, 2,4는 그다음으로 많이 나오도록 3은 제일 많이 나오도록 결과적으로는 확률분포가 삼각형 모양이 되게 하고 싶다. 어떻게 하면 되겠는가?

여기까지가 질문이었는데 확률을 바꾸라는 말에 제대로 대답을 못했다.

일단 첫번째는 랜덤함수는 0~1까지의 소수가 나오는 것이 아니었다. 그리고 랜덤함수의 확률 분포는 생각보다 그렇게 고르지 않다고 한다. (이에 대한 자료는 http://agebreak.tistory.com/49 와 http://ljh131.tistory.com/131 에 있다.) 내 생각에는 랜덤을 반복할 수록 점점 확률이 고르게 분포하지 않을까 싶다.

위 면접관분이 질문했던 내용을 찾아보니 정확하게는 균등분포를 정규분포로 바꾸는 문제였다. 이에 대한 자료는 http://tadis.tistory.com/14 를 참조하면 될것 같다. 그런데 이 코드를 봐도 sqrt는 알겠지만 log 등을 사용하는 구간에서 잘 이해가 안된다. 더군다나 이 코드는 내가 확률을 조정하기보다는 정규분포에 맞추어 바꿔주는 역할을 하는 코드인 것 같다.

다음은 간단히 1부터 5까지 랜덤한 수를 100000개 뽑아보는 코드다.

#include <iostream>
#include <time.h>

using namespace std;
#define MAX_TEST 100000
int main()
 {
 int count[5] = {0};
 int percent[5] = {10, 20, 40, 20, 10};
srand((unsigned)time(NULL));
 for (int i = 0; i < MAX_TEST; i++)
 {
 int r = rand() % 5 + 1;
 count[r - 1]++;
 }
float total = 0.0;
 for (int i = 0; i < 5; i++)
 {
 cout << i + 1 << "의 갯수 :: " << count[i] << " - " << count[i] / (float)(MAX_TEST / 100) << "%" << endl;
 total += (count[i] / (float)(MAX_TEST / 100));
 }
return 0;
 }

여기서 알 수 있는건 분포가 정확하게 20%씩은 아니라는 점이었다.

이 코드를 1부터 5까지 랜덤한 수를 정해진 비율에 맞도록 100000개 뽑아내는 코드로 변경한 것이다.

#include <iostream>
#include <time.h>
using namespace std;
#define MAX_TEST 100000
int main()
 {
 int count[5] = {0};
 int percent[5] = {10, 20, 40, 20, 10};
srand((unsigned)time(NULL));
 for (int i = 0; i < MAX_TEST; i++)
 {
 int r = rand() % 100;
if (r < 10)
 {
 count[0]++;
 }
 else if (r < 30)
 {
 count[1]++;
 }
 else if (r < 70)
 {
 count[2]++;
 }
 else if (r < 90)
 {
 count[3]++;
 }
 else if (r < 100)
 {
 count[4]++;
 }
 }
 for (int i = 0; i < 5; i++)
 {
 cout << i + 1 << "의 갯수 :: " << count[i] << " - " << count[i] / (float)(MAX_TEST / 100) << "%" << endl;
 }
return 0;
 }

단순히는 이렇게 하면 정해진 비율에 유사하게 값이 나온다.

추가로 난수발생에 대한 좋은 글들은 http://oops.kldp.org/node/103420 , http://agebreak.tistory.com/49 , http://terzeron.net/wp/?p=1006 에서 찾을 수 있었다.

비율이 유사하게가 아니라 정확한 비율대로 나오려면 배열에 미리 가능한 수를 넣어놓고 랜덤하게 위치를 변경시키는 방법 밖에는 아직 생각나지 않는다. 조금 더 생각하고 공부해봐야 할 문제인 것 같다.

데드락 현상이란?

이 질문은 내가 여기저기 입사지원을 하고 면접을 보고 시험을 볼 때마다 매번 나왔던 질문과 문제들이다. 처음 한번은 이 문제에 대답을 못했고 그다음 다시 공부한 다음 다음번부터는 잘 대답했다.

신기한건 이 질문은 내가 면접을 본 몇군데 회사의 기술면접, 실기, 필기시험 때마다 매번 나온 문제였다. 프로그래머라면 반드시 알아야할 문제라고 생각해두자.

데드락 현상은 교착 상대라고도 하는데 예를 들어, A와 B라는 두개의 스레드가 있을 때 A 스레드는 B의 실행결과를 받아야만 종료할 수 있고 B 스레드는 A 스레드의 실행결과를 받아야만 종료할 수 있다면 두개의 스레드는 영원히 종료될 수 없고 죽지 않는 스레드로 영원히 남게 된다. 이런 현상을 데드락 현상이라고 한다.

한줄로 대답하자면 두 스레드가 서로의 실행결과를 무한히 기다리는 현상이라고 답할 수 있을 것 같다.

특히 모 게임회사에서 네트워크 게임을 만들 때 데드락 현상에 빠지지 않았느냐고 물어봤는데 아직까지는 그런적이 없다고 말했더니 면접관분이 갸우뚱하셨다. 이 문제에 대해 선생님께 질문 해본 결과 윈도우소켓이 알아서 처리하기 때문에 그런 현상이 안 생겼던 것이라고 답해주셨는데 아직 잘 이해가 가지 않는다. 검색해봐도 이런 내용에 대한 답은 찾을 수 없는데 계속 더 공부해봐야 할만한 내용인 것 같다.

오버로딩과 오버라이딩의 차이

이 질문도 내가 면접을 보며 나왔던 문제인데 막연히 알고 있던 내용이라 더 정확히 알고 나중에 다시 공부하기 위해 적어놓는다.

오버로딩과 오버라이딩의 차이에 대해 설명하여 쓰라는 질문이었다.

사실은 플래시와 자바를 쓸 때 인터페이스 구현 때문에 오버라이딩만 계속 써왔고 C++를 할 때는 연산자 오버로딩 외에는 오버로딩에 대해 아는게 없기도 하고 용어가 헷갈려서 정확하게 답변하지 못했었다. 두개를 비교설명하자면 복잡하지만 다음의 두 문장으로 정리할 수 있을 것 같다.

  • 오버로딩은 클래스 안에서 파라미터가 다른 여러개의 같은 이름을 가진 함수를 정의하는 것.
  • 오버라이딩은 어떤 클래스를 상속 받은 경우, 부모클래스의 함수를 다른 내용으로 재정의 하는 것.

이렇게 짧게 정리할 수 있을 것 같다.

참고한 자료는 http://blog.naver.com/jwlee0208?Redirect=Log&logNo=10136772809 , http://zzing8300.tistory.com/127 에서 볼 수 있다. 이 두 페이지만 봐도 도움이 많이 될 것 같다.