프로그래밍/C++2011. 3. 22. 09:56

operator 변환타입()
{
           본체
}

키워드 operator 다음에 변환하고자 하는 타입의 이름을 밝히고 본체에는 변환 방법을 작성한다. 변환함수는 인수를취하지 않으며 리턴 타입도 지정하지 않는다. 왜냐하면 연산 대상은 자기 자신으로 고정되어 있고 변환 결과는 지정한 타입임을 이미 알고 있기 때문이다. 객체 자신을 다른 타입으로 변환하는 동작을 하므로 작업거리와 결과가 이미 정해져있는 것이다.

즉, 이미 operator 다음에 변환할 타입이 지정되어 있기 때문에 일반함수처럼 앞에 리턴 타입이 필요 없다

'프로그래밍 > C++' 카테고리의 다른 글

유닉스 - math 함수 포함된 소스 컴파일 하기  (0) 2011.03.18
소수점 반올림  (0) 2011.03.18
C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
Posted by 마블(이환문)
프로그래밍/C++2011. 3. 18. 17:46

유닉스에서 math.h 헤더 파일을 포함시키고 컴파일 하게 되면

에러가 발생한다. 이를 해결하기 위해서는 컴파일 명령시 -lm을 붙여준다.

예)
gcc -o test test.c -lm
gcc test.c -lm

이와 같이 컴파일을 하면 된다.

'프로그래밍 > C++' 카테고리의 다른 글

operator LPCTSTR()  (0) 2011.03.22
소수점 반올림  (0) 2011.03.18
C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
Posted by 마블(이환문)
프로그래밍/C++2011. 3. 18. 17:02

실수 x 반올림한 = floor(x+0.5)

 

척 보면 이해가 갈 것이다. 0.5를 더한 후 바로 왼쪽의 정수를 찾으면 이 값이 바로 반올림값이 된다. 어째서 이게 반올림이 되는가 선뜻 이해가 가지 않는다면 x에 1.4와 1.5를 대입해 보도록 하자.

 

floor(1.4+0.5) = 1.0

floor(1.5+0.5) = 2.0

 

1.4에 0.5를 더하면 1.9가 되는데 이 값을 내림하면 1.0이 된다. 1.5에 0.5를 더하면 2.0이 되므로 내림이 발생하지 않으며 2.0의 값을 그대로 유지할 것이다. 그러므로 floor(x+0.5)라는 공식은 0.5이상의 값은 다음 오른쪽 정수로 올림하고 0.5미만의 값은 직전의 왼쪽 정수로 내림함으로써 반올림 계산을 멋지게 하고 있는 것이다.

그렇다면 floor(x+0.5) 대신 ceil(x-0.5) 공식을 사용하는 것은 어떤 효과가 있을까? 두 공식은 비슷하지만 0.5의 경계에 걸렸을 때의 처리가 조금 다르다. floor(x+0.5)는 0.5 이상의 값을 반올림하는데 비해 ceil(x-0.5)는 0.5초과 값을 반올림한다. x가 1.5일 때 floor는 2.0으로 반올림하지만 ceil은 1.0으로 내림해 버릴 것이다. 물론 x가 1.500001이라면 ceil도 2.0으로 반올림을 한다. 두 공식의 차이를 그림으로 그려 보면 반올림되는 범위가 조금 다르다.

일반적으로 반올림이라 하면 0.5 이상을 올림하는 것이므로 floor 함수를 사용하는 것이 논리적으로 합당하다. 이 반올림 공식이 음수의 경우에도 제대로 동작하는지 확인해 보기 위해 x에 -1.4, -1.5, -1.6을 대입해 보자.

 

floor(-1.4+0.5) = -1.0

floor(-1.5+0.5) = -1.0

floor(-1.6+0.5) = -2.0

 

-1.4나 -1.6의 경우는 쉽게 이해가 되지만 -1.5를 반올림했을 때 -2.0이 아닌 -1.0이 된다는 점이 순간적으로 조금 헷갈릴 것이다. 그러나 수직선을 그려 놓고 생각해 보면 제대로 반올림되었다는 것을 확인할 수 있다. int(x+0.5)도 floor(x+0.5)와는 다르다. 캐스트 연산자는 무조건 소수점 이하를 기계적으로 잘라 버리기 때문에 x가 음수일 경우 터무니없는 결과가 나온다.

C는 자주 사용하는 기능에 대해 표준 함수들을 제공하지만 응용이 가능한 기능에 대해서는 별도의 함수를 제공하지 않는다. floor라는 간단한 함수로 반올림을 하는 것처럼 표준 함수를 조금만 응용하면 얼마든지 좀 더 복잡한 연산을 만들어 쓸 수 있기 때문이다. 표준 함수들을 많이 아는 것도 중요하지만 이미 알고 있는 함수들을 응용하는 능력도 중요하다. 이번에는 floor 함수를 한단계 더 응용하여 소수점 둘 째자리에서 반올림되도록 해 보자. 이 공식은 다음과 같다.

 floor(x*10+0.5)/10

x를 10배한 후 반올림하고 다시 10으로 나누면 소수점 둘 째자리에서 반올림된다. x가 3.14라고 했을 때 31.4로 만든 후 0.5를 더해 31.9로 만들고 이 값에 대해 floor 함수를 호출하면 31이 된다. 결과값을 다시 10으로 나누면 3.1이 될 것이다. 원래 x값에 10을 곱해 소수점을 잠시 왼쪽으로 한칸 옮긴 후 반올림 처리하고 다시 오른쪽으로 한칸 옮기는 것이다.

같은 원리로 소수점 셋 째자리에서 반올림하려면 floor(x*100+0.5)/100 공식을 사용하면 되고 소수점 넷 째자리에서 반올림하려면 floor(x*1000+0.5)/1000 공식을 쓴다. 곱하고 나누는 수만 조정하면 반올림되는 자리수를 원하는대로 지정할 수 있고 더하는 0.5를 조정하면 반올림 경계도 입맞대로 설정할 수 있다. 이 공식을 좀 더 일반화하면 소수점 n번째 자리에서 반올림되는 함수를 만들 수 있는데 아주 간단하므로 매크로 함수로 정의해 보았다.

#define banollim(x,dig) (floor((x)*pow(10,dig)+0.5)/pow(10,dig))



츌처 : www.winapi.co.kr

'프로그래밍 > C++' 카테고리의 다른 글

operator LPCTSTR()  (0) 2011.03.22
유닉스 - math 함수 포함된 소스 컴파일 하기  (0) 2011.03.18
C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
Posted by 마블(이환문)
프로그래밍/C++2009. 12. 10. 16:42

윈도우 프로그래밍시 콘솔프로그래밍처럼 원하는값을 찍어서 볼 수가 없다

그래서 윈도우프로그매이에서는 TRACE()를 사용한다

TRACE()는  디버깅할때 변수값 확인 또는 프로그램이 어디까지 수행되고 있는지 확인하기 위해서 사용한다

사용방법은 Trace.h 이라는 헤더파일을 만든다

Trace.h는 아래와 같이 작성한다

#include <tchar.h>

void TRACE_WIN32(LPCTSTR lpszFormat, ...)
{
 TCHAR lpszBuffer[0x160]; //버퍼 크기.
 va_list fmtList;
 va_start(fmtList, lpszFormat);
 _vstprintf_s(lpszBuffer, lpszFormat, fmtList);
 va_end(fmtList);
 ::OutputDebugString(lpszBuffer);
}


Trace.h 헤더파일을 추가한 후 아래와 같이 define 설정 후 사용한다
#define TRACE TRACE_WIN32

사용 예)

 TRACE("%d, %d\n", w, h);

내용은 출력부분에서 확인하면 된다

'프로그래밍 > C++' 카테고리의 다른 글

유닉스 - math 함수 포함된 소스 컴파일 하기  (0) 2011.03.18
소수점 반올림  (0) 2011.03.18
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Posted by 마블(이환문)
프로그래밍/C++2009. 7. 16. 15:03

STL의 컨테이너로 List라는게 있다

List는 우리가 흔히 사용하는 연결리스트 같은것 이라고 생각하면 된다

우선 간단하게 리스트에서 출력하는 방법이다

for(itor=list1.begin(); itor != list1.end(); itor++)
  cout << *itor;

'프로그래밍 > C++' 카테고리의 다른 글

소수점 반올림  (0) 2011.03.18
C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 5. 1. 13:06

input.txt 파일을 읽어와서 output.csv 파일을 만드는 프로그램이다

여기에 추가적으로 input.txt 파일에 보면 각 날짜의 시간별로 온도, 습도, 강수량 등이 기록되어 있다

이를 날짜별으로 평균 온도, 최대 최소 온도, 표준편차 등을 구해서 Reprot.csv 파일로 만드는 프로그램이다

#include 
#include 
#include 
#include 

typedef struct Weather
{
	char day[11];
	char time[6];
	float temperature;
	float humidty;
	float rainfall;
	float insolation;
	float sunshine_duration;
	char windy[5];
	float wind_velocity;
	float max_wind_velocity;
	struct Weather *next;
}Weather;

typedef struct 
{
	Weather* head;
}Weather_h;

Weather_h* CreateNode(void);
void PrintList();
void CreateCSVfile(Weather_h *L, Weather *p);
void CreateReportFile(Weather *p);
float Standard_Deviation(Weather *p, Weather* nextNdoe, float avgTmp, bool end, int count);
void FreeNode(Weather_h *L, Weather *p);

void main()
{
	Weather_h* L;

	L = CreateNode();


	PrintList();

}

Weather_h* CreateNode(void)
{
	Weather_h* L;
	L = (Weather_h *)malloc(sizeof(Weather_h));
	L->head = NULL;
	return L;
}

void PrintList()
{
	Weather* p;
	Weather* newData;
	Weather_h* L;
	L = (Weather_h *)malloc(sizeof(Weather_h));
	L->head = NULL;

	FILE *fp;

	if((fp = fopen("input.txt", "r")) == NULL)
	{
		printf("File is not found!\n");
	}

	p = L->head;

	fseek(fp, 110,SEEK_SET);

	puts("=========================================================================");
	puts("계측시간 온도(℃) 습도(%) 강수량(mm) 일사량(w/m2) 일조시간(hour) 풍향 풍속(m/s) 최대풍속(m/s)");

	while(!feof(fp))
	{
		newData = (Weather *)malloc(sizeof(Weather));

		fscanf(fp, "%s %s %f %f %f %f %f %s %f %f ", newData->day, newData->time, &newData->temperature, &newData->humidty, &newData->rainfall, &newData->insolation, &newData->sunshine_duration, &newData->windy, &newData->wind_velocity, &newData->max_wind_velocity);

		if(L->head == NULL)
		{
			L->head = newData;
			p = newData;
			newData->next = NULL;

		}
		else
		{
			p->next = newData;
			newData->next = NULL;
		}

		while(p->next != NULL)
		{

			p = p->next;
		}
	}

	p = L->head;
	CreateCSVfile(L, p);
	CreateReportFile(p);
	FreeNode(L, p);
	
	puts("\ncvs 파일과 report.csv 파일을 성공적으로 만드셨습니다.");
	puts("=========================================================================");

	fclose(fp);


}



void CreateCSVfile(Weather_h* L, Weather* p)
{
	FILE *fp;
	Weather* newData;
	newData = p;

	if((fp = fopen("output.csv", "w")) == NULL)
	{
		printf("File is not found!\n");
	}

	fprintf(fp, "계측날짜, 시간,온도(℃), 습도(%), 강수량(mm), 일사량(w/m2), 일조시간(hour), 풍향, 풍속(m/s), 최대풍속(m/s) \n");
	while(newData->next != NULL)
	{
		fprintf(fp, "%s,", newData->day);
		fprintf(fp, " %s,", newData->time);
		fprintf(fp, " %.1f,", newData->temperature);
		fprintf(fp, " %.1f,", newData->humidty);
		fprintf(fp, " %.1f,", newData->rainfall);
		fprintf(fp, " %.1f,", newData->insolation);
		fprintf(fp, " %.1f,", newData->sunshine_duration);
		fprintf(fp, " %s,", newData->windy);
		fprintf(fp, " %.1f,", newData->wind_velocity);
		fprintf(fp, " %.1f\n", newData->max_wind_velocity);


		newData = newData->next;
	}

	fclose(fp);
}

void CreateReportFile(Weather* p)
{
	FILE *fp;
	Weather *nextNode, *temp, *startNode;
	float avgTem = 0;
	float sumTem = 0;
	int count=0;
	float Max = -1000;
	float Min = 1000;
	float sumHumidty = 0;
	float avgHumidty = 0;
	float sumRainfall = 0;
	float avgRainfall = 0;
	float sumSunshine_duration = 0;
	float avgSunshine_duration = 0;

	temp = p;
	startNode = p;
	nextNode = p->next;

	if((fp = fopen("Report.csv", "w")) == NULL)
	{
		printf("File is not found!\n");
	}

	fprintf(fp, "[일별기상통계표] 계측 지역:신촌 \n");
	fprintf(fp, "일자, ,기온(Temperature), , , 습도, 강수량, 일조시간\n");
	fprintf(fp, " , 평균, 표준편차, 최고, 최저, [%%], [mm], [hour]\n");


	while(p->next != NULL)
	{
		temp = startNode;

		if(strcmp(p->day, nextNode->day) == 0)
		{
			sumTem = sumTem + p->temperature;
			sumHumidty = sumHumidty + p->humidty;
			sumRainfall = sumRainfall + p->rainfall;
			sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;

			count++;

			if(p->temperature > Max)
				Max = p->temperature;

			if(p->temperature < Min)
				Min = p->temperature;

			p = nextNode;
			nextNode = nextNode->next;


			if(p->next == NULL)
			{
				sumTem = sumTem + p->temperature;
				sumHumidty = sumHumidty + p->humidty;
				sumRainfall = sumRainfall + p->rainfall;
				sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;
				count++;

				avgTem = sumTem / count;
				avgHumidty = sumHumidty / count;
				avgRainfall= sumRainfall / count;
				avgSunshine_duration = sumSunshine_duration / count;

				fprintf(fp, "%s, ", p->day);
				fprintf(fp, "%.1f, " , avgTem );
				fprintf(fp, "%.4f,", Standard_Deviation(temp, temp, avgTem, true, count));
				fprintf(fp, "%.1f, " , Max);
				fprintf(fp, "%.1f, " , Min);
				fprintf(fp, "%.1f, " , avgHumidty);
				fprintf(fp, "%.1f, " , avgRainfall);
				fprintf(fp, "%.1f\n " , avgSunshine_duration);

			}

		}

		else if(strcmp(p->day, nextNode->day) != 0)
		{
			sumTem = sumTem + p->temperature;
			sumHumidty = sumHumidty + p->humidty;
			sumRainfall = sumRainfall + p->rainfall;
			sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;
			count++;

			avgTem = sumTem / count;
			avgHumidty = sumHumidty / count;
			avgRainfall= sumRainfall / count;
			avgSunshine_duration = sumSunshine_duration / count;

			if(p->temperature > Max)
				Max = p->temperature;

			fprintf(fp, "%s, ", p->day);
			fprintf(fp, "%.1f, " , avgTem);
			fprintf(fp, "%.4f,", Standard_Deviation(temp, nextNode, avgTem, false, count));
			fprintf(fp, "%.1f, " , Max);
			fprintf(fp, "%.1f, " , Min);
			fprintf(fp, "%.1f, " , avgHumidty);
			fprintf(fp, "%.1f, " , avgRainfall);
			fprintf(fp, "%.1f\n " , avgSunshine_duration);

			p = nextNode;
			nextNode = nextNode->next;
			startNode = p;
			avgTem = 0;
			sumTem = 0;
			sumRainfall = 0;
			sumHumidty = 0;
			sumSunshine_duration = 0;
			count = 0;
			Max = -1000;
			Min = 1000;

		}


	}



}

float Standard_Deviation(Weather* p, Weather* nextNode, float avgTmp, bool end, int count)
{
	Weather *temp = p;
	Weather *tempNext = nextNode;
	float sd = 0;
	float squaresum = 0;


	if(end == false)
	{
		while(strcmp(temp->day, tempNext->day) != 0)
		{
			squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
			temp = temp->next;
		}
		squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
		temp = temp->next;
	}
	else
	{
		while(temp->next != NULL)
		{
			squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
			temp = temp->next;

		}
		squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
		temp = temp->next;
	}
	sd = sqrt(squaresum) / count;
	return sd;	

}

void FreeNode(Weather_h *L, Weather *p)
{
	Weather *temp = L->head;


	if(L->head == NULL)
		return ;
	else
	{
		free(L);
		while(temp->next != NULL)
		{
			temp = p->next;
			free(p);
			p = temp;
		}
	}

}

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
PVOID  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 25. 20:03

회사에서나 개인적으로 S/W를 전문적으로 개발해 본 경험이 있는 분이라면 로깅 라이브러리는 거의 모든 프로젝트에 공통적으로 필요한 모듈이라는 걸 인정하실 것입니다.

로깅 라이브러리는 개발하는 중간에는 S/W 가 잘못 수행되는 부분은 없는지 디버깅하는 데 유용할 뿐만 아니라 개발이 완료되고 나서 시스템 운영자나 관리자가 해당 S/W를 운영하는 데 필수적인 정보를 제공하는 데 유용합니다. 여러분이 웬만한 규모 이상의 S/W를 개발한다면 항상 맞닥뜨려야 하는 문제라는 것이죠. 이렇게 거의 모든 S/W에서 필요한 라이브러리임에도 불구하고, 잘 설계된 로깅 라이브러리를 만든다는 건 쉽지 않은 일입니다.

이런 로깅 라이브러리를 설계/구현하는 긴 여정을 이번 글을 시작으로 해서 떠나 보도록 하겠습니다.

아~! 근데 본격적으로 여정을 떠나기 전에 한 가지 준비를 하고 떠나야 되겠습니다. 로깅을 위해 C/C++ 언어에서 제공하는 기본 메커니즘들을 확인하고 가는 것이죠. 이걸 확인하는 방법은 다양하겠지만, 저는 assert()를 통해 설명드리도록 하겠습니다.

assert() 란 함수의 본래 로직을 수행하기 전/후에 해당 로직이 가정하고 있는 특정 조건이 만족하는지를 체크해 보고 만족하지 않으면 실행을 중단해 버리는 특수한 함수로 개발 도중 디버깅 용도로 많이 쓰이는 방법입니다.

예를 들어, 여러분이 C 라이브러리 구현자라고 상상해 보시겠습니다. 그리고, memcpy() 와 같은 함수를 구현하는 책임이 있다고 상상해 보겠습니다. memcpy()는 다음과 같이 구현할 수 있을 것입니다.

/*
 * memcpy()는 pvSource 로부터 pvSink 로 sz 만큼을 복사함.
 * memcpy()는 pvSource 와 pvSink 가 NULL 이 아니라는 걸 가정하고 있음
 */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  while (sz-- > 0)
    *pSnk++ = *pSrc++;

  return pvSink;
}

기본적인 아이디어는 위와 같이 구현할 수 있지만, memcpy 가 가정하고 있는 pvSource와 pvSink 가 NULL 이 아니라는 건 체크하지 않고 있기 때문에 어떤 개발자가 실수로 pvSink 나 pvSource에 NULL을 주게 되면 실행시에 심각한 문제가 발생할 수도 있습니다. 이런 문제를 피하기 위해 당연히 본래의 로직을 실행하기 전에 pvSource 와 pvSink의 NULL 여부를 체크하는 로직을 넣어야 할 것입니다.

/*
 * pvSource, pvSink가 NULL인지를 체크함
 */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  /* pvSource 나 pvSink가 NULL 프로그램 수행을 중단 */
  if (pvSource == NULL || pvSink == NULL) {
    fprintf(stderr, "memcpy(): Bad args: pvSource or pvSink is NULL\n");
    abort();
  }

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

어떤 분은 NULL 인지 체크한 후 에러 메시지만 출력하고 바로 리턴하는 게 아니라 abort()를 호출하는 걸 이상하게 생각하실 분도 있을 것 같습니다. 그건 pvSource 나 pvSink의 값으로 NULL이 넘어 왔다면 프로그램에 뭔가 심각한 버그가 있다는 걸 뜻하기 때문입니다. 뭔가 심각한 버그가 있는 채로 실행되는 것 보다는 그 상황이 알려지면 빨리 종료해 버리는 것이 문제를 해결하는 데 훨씬 유리하기 때문입니다. 그렇다 하더라도 이미 S/W가 출시된 상황에서 그런 일이 벌어진다면 고객으로부터 Claim이 올 것은 자명한 일입니다. 따라서 pvSource 나 pvSink 가 NULL 인지 체크하는 코드는 Production 시스템에 사용되는 것이 아니라 개발시에 사용되어야 그 의미가 살아날 수 있습니다. 즉, 다음과 같이 작성해야 할 것입니다.

/*
  * pvSource, pvSink가 NULL인지를 체크함
  */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

#ifdef DEBUG
  /* pvSource 나 pvSink가 NULL 프로그램 수행을 중단 */

  if (pvSource == NULL || pvSink == NULL) {
    fprintf(stderr, "memcpy(): Bad args: pvSource or pvSink is NULL\n");
    abort();
  }
#endif

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

위와 같이 작성하고 개발시에는 DEBUG 를 정의해서 컴파일하고, 정식 릴리즈시에는 DEBUG를 정의하지 않고 컴파일하면 개발시에만 abort()가 적용되어 디버깅하는 데 도움을 얻을 수 있게 됩니다.

이렇게 뭔가 만족되어야 하는 조건을 검사하고, 그 조건이 만족하지 않을 경우 실행을 중단해버리는 함수가 바로 assert() 입니다. 다음은 assert() 를 적용해서 위 코드를 수정해 본 것입니다.

/*
  * pvSource, pvSink가 NULL인지를 체크함
  */
void*
my_memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  assert(pvSource != NULL && pvSink != NULL);

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

char* pSource = "source memory content";
char pSink[32];

/* main() 함수 */
int
main(void)
{
  printf("Calling my_memcpy(%08x,%08x, %d)\n",
    pSink, pSource, strlen(pSource));
  my_memcpy(pSink, pSource, strlen(pSource));
  /* assert()가 실패하는 케이스 */
  printf("Calling my_memcpy(NULL,%08x, %d)\n", pSource,
    strlen(pSource));

  my_memcpy(NULL, pSource, strlen(pSource));
}

다음은 GCC(3.4.4)로 컴파일하고 실행해 본 결과입니다.

$ ./debug
Calling my_memcpy(00404020,0040302b, 21)
Calling my_memcpy(NULL,0040302b, 21)
debug: debug.c:15: main: Assertion `pvSource != NULL && pvSink != NULL' failed.
Aborted


위 결과에서 보시다시피 두 번째 my_memcpy() 실행 중에 assert()가 실패해서 실행이 취소되는 걸 확인할 수 있습니다.

assert()가 실패했을 때, 출력해주는 메시지를 살펴 보시기 바랍니다. 프로그래머가 특별히 입력인자로 주지도 않았는데 다음과 같은 부가적인 정보를 출력해서 디버깅이 용이하도록 해줍니다.

1. debug.c 라는 파일명을 출력해 주네요
2. 정확한 라인번호도 출력해 줍니다.
3. pvSource != NULL && pvSink != NULL 이라는 수식을 문자열로 출력해 줍니다.

너무 신기하지 않나요 ? 도대체 assert() 란 녀석이 어떤 마술을 부리길래 프로그래머가 주지도 않은 정보를 알아내서 저런 정보를 출력해 줄 수 있을까요 ? 전 처음에 이 assert() 란 녀석을 써보고서는 어찌나 신기했던지 어떻게 구현된지를 알아낼려고 한참을 헤멨습니다. 여러분도 궁금하시죠 ? 그럼 지금부터 하나 하나 그 비밀을 파헤쳐 보도록 하겠습니다.

assert() 란 녀석이 어떻게 구현되어 있는지를 알아내면 그 비밀을 알아낼 수 있을 것 같습니다. 우선 assert() 가 선언되어 있는 헤더 파일부터 뒤져보기로 하죠.

제가 지금 이 글을 쓰면서 쓰고 있는 컴파일러는 GCC 인데요, 그곳에서는 다음과 같이 assert()가 정의되어 있습니다.

/* 이해를 돕기 위해 간략하게 표현했습니다 */
extern void __assert_fail (__const char *__assertion,
                           __const char *__file,
                          
unsigned int __line,
                           __const char *__function)


#define assert(expr) \
 
((void) ((expr) ? 0 : (__assert_fail (#expr, __FILE__,  \
                                       __LINE__, __func__), 0)))


일단 이걸 보면 assert() 가 그냥 함수가 아니라 매크로 함수인 것을 알 수 있습니다. 그리고 내부적으로는 __assert_fail() 이라는 또 다른 함수를 #expr, __FILE__, __LINE__, __func__ 라는 인자를 가지고 호출하는 걸 확인할 수 있습니다. 도대체 이런 듣도 보도 못한 애들은 어느 별에서 온 걸까요 ? 참 신기하긴 합니다만 신기해 하는 것으로 그쳐서는 안되고 진정한 골수 엔지니어라면 얘네들이 어떤 별에서 왔는지 무슨 일이 있어도 알아내야 되겠지요.

우선 #expr 부터 살펴 보겠습니다. 다음 코드를 한 번 컴파일하고 실행시켜 보시겠어요 ?

#include <stdio.h>

#define STRINGIFY(x) #x

int
return10(void)
{
  return 10;
}

int
main(void)
{
  printf("%s = %d\n", STRINGIFY(10 + 100), 10 + 100);
  printf("%s = %d\n", STRINGIFY(strlen("printf")), strlen("printf"));
  printf("%s returned %d\n", STRINGIFY(return10()), return10());
}

컴파일한 후 실행시켜 보았더니 이런 결과가 나오네요.

$ gcc -o stringify stringify.c
$ ./stringify
10 + 100 = 110
strlen("printf") = 6
return10() returned 10

어~! STRINGIFY()를 호출하면 STRINGIFY로 넘겨준 인자가 그냥 그대로 문자열 리터럴이 되어 버리네요. 아~! 그렇구나. 매크로 함수로 넘겨준 인자에 '#'를 붙이면 전처리기가 그걸 문자열 리터럴로 바꿔주나 봅니다. 그것 참 쓸모 있을 것 같습니다.

다음은 __FILE__, __LINE__, __func__ 를 알아보도록 하겠습니다.

__FILE__ 매크로는 컴파일시에 파일명을 나타내는 문자열 리터럴로 치환되고 __LINE__ 은 소스 코드 라인번호로 치환됩니다. 그리고, __func_ 는 매크로가 아니라 컴파일러에 의해 자동으로 선언되는 변수로서 함수명을 나타내는 문자열을 가리키게 됩니다. __FILE__ 은 문자열 리터럴이고, __LINE__ 은 정수 리터럴이고, __func__ 는 자동으로 정의되는 변수라는 점에 주의해야 합니다. __func__ 와 비슷한 역할을 하는 녀석이 __FUNCTION__ 이라는 녀석입니다. 다음 코드를 컴파일해서 실행해 볼까요 ?

/* debug2.c */
#include <stdio.h>
#include <assert.h>

int
main(void)
{
  printf("%s() at %s::%05d\n", __FUNCTION__, __FILE__, __LINE__);
  printf("%s() at %s::%05d\n", __func__, __FILE__, __LINE__);
  assert(1 == 0);

  return 0;
}

Windows XP 에서 cygwin GCC(3.4.4)로 컴파일해서 실행해 봤더니 다음과 같은 결과가 나오는 군요.

$ ./debug2
main() at debug2.c::00009
main() at debug2.c::00010
assertion "1 == 0" failed: file "debug2.c", line 11
     38 [sig] debug2 5940 C:\cygwin\home\김윤수\work\debug2.exe: *** fatal error - called with threadlist_ix -1
Hangup

주의할 점은 __FUNCTION__ 은 비표준으로 컴파일러 벤더들이 자체적으로 제공하는 확장 매크로로 preprocessing 중에 문자열 리터럴로 치환되는 반면 __func__ 는 C99 표준에 포함된 것으로 컴파일러에 의해 자동적으로 정의되는 const char * 형의 변수라는 점입니다.

일단 GCC는 __FUNCTION__ 과 __func__ 둘 다를 지원하는 반면 Microsoft Visual C++ 2005의 cl은 __FUNCTION__ 만 지원하는 군요. 또 신기한 점은 GCC, G++은 __FUNCTION__ 을 __func__ 와 동일하게 처리한다는 점입니다. 위에서 컴파일해 본 debug2.c 에 대해 preprocessing 한 결과를 살펴 보면 매크로가 아니라 변수로 취급하는 걸 알 수 있습니다.

$ gcc -E debug2.c > debug2.pre.c
$ cat debug2.pre.c
......

int
main(void)
{
  printf("%s() at %s::%05d\n", __FUNCTION__, "debug2.c", 10);
  printf("%s() at %s::%05d\n", __func__, "debug2.c", 11);
  ((1 == 0) ? (void)0 : __assert("debug2.c", 12, "1 == 0"));

  return 0;
}

위에서 첫번째 명령에서 gcc 의 -E option은 preprocessing 한 결과를 표준출력으로 내보내라라는 옵션입니다. 위 명령들은 그걸 리다이렉션해서 debug2.pre.c 라는 파일로 저장해서 그 내용을 살펴본 것입니다. (각 컴파일러에서 preprocessing 만 하게 하는 옵션을 알아두면, 매크로 치환하면서 문제가 발생하는 경우 쉽게 디버깅할 수 있습니다. 꼭 알아 두시기를 권유합니다.)

__FUNCTION__, __func__ 이 매크로였다면 preprocessing 후에는 둘 다 __FILE__ 처럼 문자열 리터럴로 치환되어야 함에도 원래 이름대로 그대로 남아 있는 것은 전처리기가 처리하지 않는다는 뜻이 될 것입니다.

위에서 assert() 가 어떻게 정의되어 있는지 보여드렸을 때는 간략하게 하기 위해 복잡한 조건 컴파일을 보여드리지 않았는데요, 실제로는 다음과 같이 NDEBUG(No debugging 의 약자라고 보시면 됩니다)가 정의되어 있느냐에 따라 조건 컴파일이 되도록 정의되어 있습니다.

#ifdef NDEBUG /* required by ANSI standard */

#define assert(expr)      ((void)0)

#else

/* 아까 나온 부분. 이해를 돕기 위해 간략하게 표현했습니다 */

......
#define assert(expr) \
 
((void) ((expr) ? 0 : (__assert_fail (#expr, __FILE__,  \
                                       __LINE__, __func__), 0)))

#endif

즉, NDEBUG 가 정의되어 있으면(다르게 표현하면 디버깅 버전이 아니라는 뜻이겠지요), assert() 가 아무런 영향을 미치지 않고, 아무것도 정의되어 있지 않으면 assert()가 영향을 미치는 것이겠지요. NDEBUG는 ANSI 표준에 의해 정의된 매크로이기 때문에 대부분의 컴파일러가 지원할 것으로 보입니다. 앞에서 작성했던 debug2.c 를 다음과 같이 NDEBUG를 정의한 채로 컴파일한 후 실행하면 assert()가 아무런 영향을 미치지 않는 것을 확인할 수 있습니다.

$ gcc -o debug2 -DNDEBUG debug2.c
$ ./debug2
main() at debug2.c::00010
main() at debug2.c::00011


이렇게 디버깅 버전과 Production 버전에 따라 여러 가지 코드가 포함되고 포함되지 않게 하는 것은 실제 프로젝트에서 일상적으로 활용되는 방법입니다. 개발하는 동안에는 디버깅 버전으로 원래의 실행 로직외에 다양한 코드 또는 정보들이 실행 파일에 포함되게 함으로써 문제가 발생했을 때 신속하게 찾을 수 있게 도와주고, Production 버전에서는 성능을 개선하고 실행파일의 크기를 줄이기 위해 그런 부가적인 정보 또는 코드를 없애버리는 것이지요

마지막으로 한 가지 더 주의할 점을 알려 드리겠습니다. __FUNCTION__ 은 컴파일러 벤더들이 나름대로 정의한 확장 매크로이기 때문에 portability 가 떨어지고, __func__ 는 최신 C99 규격에 포함된 것이라 portability 가 약간 떨어진다는 것입니다. 물론 장기적으로는 __func__ 를 쓰는 것이 유리하겠지만(표준이니까 __func__ 를 지원하는 컴파일러가 많이 생길 것으로 기대할 수 있으므로), Microsoft Visual C++ 2005 버전도 지원하지 않는 걸 보니 __func__도 본격적으로 쓰일 수 있으려면 아직 갈 길이 먼 것 같습니다. 정리하자면 portability 가 중요하다면 __FUNCTION__ 이나 __func__ 를 전혀 쓰지 않거나 아니면 쓰더라도 컴파일러에서 __FUNCTION__ 이나 __func__이 지원되는 여부에 따라서 다르게 처리되도록 해야할 것입니다. 더 상세한 정보를 얻기 원하시면 다음 링크를 참고하시기 바랍니다.

__LINE__, __FUNCTION__, and __FILE__ macros
C, C++ 언어에서 디버깅시에 유용하게 쓸 수 있는 predefined macro 에 대한 설명
Pre-defined C/C++ Compiler Macros
다양한 C/C++ 컴파일러에서 정의된 표준 및 비표준 predefined macro 목록을 잘 정의해 둔 곳

이번 글에서는 다음과 같은 사항을 기억하시면 될 것입니다.

  1. 매크로 함수 정의에서 인자 x 에 대해 #x 라고 쓰면 x 가 문자열 리터럴로 바뀝니다.
  2. __FUNCTION__ 또는 __func__ 는 현재 함수명을 출력하는 데 사용할 수 있습니다. 단, __FUNCTION__, __func__ 모두 portability 측면에서 약간 문제가 있으므로 주의해서 사용해야 합니다.
  3. __FILE__ 은 현재 파일명을 출력하는 데 사용할 수 있습니다.
  4. __LINE__ 은 현재 라인번호를 출력하는 데 사용할 수 있습니다.
  5. 조건부 컴파일을 이용해서 디비깅용 빌드와 Production 빌드를 구분하는 방법을 사용할 수 있어야 합니다.
  6. 컴파일러 옵션 중 preprocessing 만 하는 옵션을 알아두면 매크로 함수를 디비겅하는데 유용하게 사용될 수 있습니다.

자~ 그럼 이상으로 해서 로깅 라이브러리를 설계/구현하기 위한 여정에 필수적인 도구들을 다 챙긴 것 같습니다

출처: 김윤수의 이상계를 꿈꾸며
url : http://yesarang.tistory.com/74

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
Abstract Factory Pattern  (0) 2009.04.22
PVOID  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 22. 16:01


구체적 클래스를 정의하지 않고도 서로 관련성이 있거나
독립적인 여러 객체의 군을 생성하기 위한 인터페이스 제공

 정의: 추상적인 부품을 조립해서 추상적인 제품을 만드는 추상적인 공장 같은 패턴
       : 클라이언트에서 구상 클래스를 지정하지 않으면서도 일군의 객체를 생성할 수 있도록 하는 패턴

활용:
         1. 생성되고 구성되고 표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때
         2. 하나 이상의 제품군들 중 하나를 선택해서 시스템을 설정해야 하고 한번 구성한 제품을
            다른 것으로 대체 가능할 때
         3. 관련된 객체군을 함께 사용해서 시스템을 설계하고,
            이 제품이 갖는 제약사항을 따라야 할 때
         4. 제품에 대한 클래스 라이브러리를 제공하고,
            그들의 구현이 아닌 인터페이스를 표현하고 싶을 때
결과:
       1.구체적인 클래스를 분리한다  
         추성클래스가 정의한 인터페이스를 통해서만 클라이언트 프로그램을 작성한다
       2. 제품군을 쉽게 대체할 수 있도록 한다
       3. 제품간의 일관성을 증진한다
       4. 새로운 종류의 제품을 제공하기 어렵다 –
          새로운 제품에 대해 추상 팩토리를 확장하는 것이 어렵다

단점: 클래스 계층이 커질 수 있다

예제코드

#include 
#include 

#include 
#include 

class AbstractProdcutA
{
public:
 AbstractProdcutA ()         {}
 virtual ~AbstractProdcutA() {}
};

class AbstractProdcutB
{
public:
 AbstractProdcutB ()         {}
 virtual ~AbstractProdcutB() {}
};

class AbstractFactory
{
public:
 virtual AbstractProdcutA* CreateProductA (void) = 0;
 virtual AbstractProdcutB* CreateProductB (void) = 0;
};

class ProdcutA1 : public AbstractProdcutA
{
public:
 ProdcutA1()
 {
  std::cout << "ProdcutA1()" << std::endl;
 }
};
class ProdcutA2 : public AbstractProdcutA
{
public:
 ProdcutA2()
 {
  std::cout << "ProdcutA2()" << std::endl;
 }
};
class ProdcutB1 : public AbstractProdcutB
{
public:
 ProdcutB1()
 {
  std::cout << "ProdcutB1()" << std::endl;
 }
};
class ProdcutB2 : public AbstractProdcutB
{
public:
 ProdcutB2()
 {
  std::cout << "ProdcutB2()" << std::endl;
 }
};

class ConcreteFactory1 : public AbstractFactory
{
public:
 virtual AbstractProdcutA* CreateProductA (void)
 {
  return new ProdcutA1();
 }
 virtual AbstractProdcutB* CreateProductB (void)
 {
  return new ProdcutB1();
 }
};

class ConcreteFactory2 : public AbstractFactory
{
 virtual AbstractProdcutA* CreateProductA (void)
 {
  return new ProdcutA2();
 }
 virtual AbstractProdcutB* CreateProductB (void)
 {
  return new ProdcutB2();
 }
};


class Client
{
public:
 void Create (AbstractFactory *f)
 {
  ProdcutA_.reset( f->CreateProductA() );
  ProdcutB_.reset( f->CreateProductB() );
 }

private:
 std::auto_ptr ProdcutA_;
 std::auto_ptr ProdcutB_;
};


int _tmain(int argc, _TCHAR* argv[])
{
 Client client;


 std::cout << "by ConcreteFactory1" << std::endl;
 client.Create ( &ConcreteFactory1() );

 std::cout << std::endl;

 std::cout << "by ConcreteFactory2" << std::endl;
 client.Create ( &ConcreteFactory2() );

 return 0;
}

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
PVOID  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 22. 15:18

1. PVOID

PVOID란 void 포인터, 즉 void *을 말합니다. 이 포인터는 C언어에서는 굉장히 막강한 능력을 가지고 있죠. 어떠한 능력인고 하니 모든 다른 포인터를 가리킬 수 있으며, 다른 모든 포인터로 자동적으로 캐스팅이 이루어진는 점 입니다. 따라서 C언어에서 일반화 코드 내지는 자료구조의 코드등에 약방감초처럼 등장하는 놈이 PVOID라 할 수 있겠습니다.

그럼 간단히 PVOID의 능력을 살펴보고 넘어가도록 하겠습니다.

int a = 3; 
PVOID pv = &a; 

C언어에서는 매우 합법적인 코드입니다. pv는 모든 포인터를 가리킬 수 있으므로, 당연히 int의 포인터도 가리킬 수 있는 것입니다.

int *pi = pv; 

이또한 아주 합법적인 코드입니다. C언어에서 PVOID는 다른 포인터로 자동적으로 승격되기 때문이죠.

여기서 중요한 코드를 한번 살펴 보도록 합시다.

int a = 3; 
int *pi = &a; 
PVOID pv = &a; 

자 위와 같은 코드가 있다고 했을때 우리가 pi의 값을 읽기 위해서는 역참조 연산자 *를 사용해서 *pi로 읽습니다. 그러면 a에 저장되어 있는 값인 3이 나오게 되는 것이죠. 그렇다면 PVOID를 역참조하기 위해서 *pv라고 하면 어떻게 될까요? 그러면 컴파일에러가 발생하게 됩니다. 왜일까? 왜 역참조를 하지 못하는 것일까요?

+---+---+---+---+ 
|   |   |   |   | 
+---+---+---+---+ 

일반적인 32비트 컴퓨터에서 int는 위와 같은 4바이트로 이루어집니다. 여기서 int의 포인터라는 것이 가지고 있는 정보는 두가지입니다. 위의 메모리가 시작되는 주소와 함께, 한 덩어리 크기가 4바이트라는 것입니다.

A                 B                 C 
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+ 
|FA |0C |2B |34 | |FA |0C |2B |34 | |FA |0C |2B |34 | 
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+ 

편의상 메모리에 데이터가 위와 같이 저장되어 있다고 가정해 봅시다. A,B,C는 각각 주소를 나타냅니다. int의 포인터가 A라는 위치를 가리킬때, 해당 데이터를 읽게되면 그 위치부터의 4바이트를 읽어서 FA 0C 2B 34라는 값이 나오게 됩니다. 그렇다면 해당 포인터에 1을 더하면 어떻게 될까요? 그러면 해당 포인터는 B지점으로 이동합니다. 왜냐하면 int가 가리키는 놈은 4바이트라는 것을 알고 있으므로 다음 데이터를 읽기 위해서 4바이트 뒤로 이동하는 것이죠.

여기서 우리는 PVOID가 역참조를 하지 못하는 이유를 알 수 있습니다. PVOID는 모든 것을 가리킬 수 있다는 사실에서... 그 놈이 가리키는 놈의 크기를 알지 못하는 것입니다. 즉, 다른 포인터는 모두 번지와 함께 대상체의 크기 정보를 가지고 있는 반면에 PVOID는 대상체의 크기 정보는 없이 단순히 번지만 가지고 있는 것이죠. 따라서 PVOID는 포인터의 역참조 연산을 비롯해 +,-연산도 이루어질 수 없습니다.

그러므로 또한 우리가 코드상에서 PVOID를 통해서 일반화를 시킬 경우는 항상 크기를 같이 저장하거나, 아니면 포인터를 읽고/쓰는 부분을 콜백으로 처리하는 등의 형태가 됩니다. 즉, 다음과 같은 swap함수의 원형이 나올 수 없다는 이야기가되죠.

void swap(PVOID pa, PVOID pb); 

PVOID만으로는 절대로 swap을 할 수가 없습니다. 왜냐하면 크기 정보가 없기 때문이죠. 물론 프로그래머가 PVOID가 가지고 오는 포인터가 무조건 int의 포인터라고 가정하면 저런 원형도 나올 수 있습니다.

void swap(PVOID pa, PVOID pb) 
{ 
    int *pc = pa; 
    int *pd = pb; 
    
    ... 
} 

하지만 위의 코드의 swap함수는 일반화가 안된 그저 int값만 교환하는 swap함수와 다를 바가 없습니다. 따라서 C에서 일반화된 코드를 작성하기 위해서 PVOID를 선택한 경우는 그 사이즈또한 같이 인수로 받아야 합니다.

void swap(PVOID pa, PVOID pb, size_t size); 

이제야 비로서 사이즈에 맞는 swap을 할 수 있는 함수가 되었습니다.

이번 장에서 가장 중요하게 기억해야 할 점은 PVOID는 모든 포인터를 가리킬 수 있다는 점, 다른 포인터로 자동 승격된다는 점. 그리고 PVOID내에는 크기 정보가 없다는 점입니다.

2. 템플릿

그럼 이번에는 템플릿을 한번 짚고 넘어가 보도록 하겠습니다. 템플릿 또한 일반화 프로그램의 대명사와 같은 놈이므로 앞장에 나온 PVOID와 비슷하다고 생각할 수 있습니다. 하지만 일반화 코드를 만들때 사용한다는 점 왜에는 전혀 똑같이 않습니다. 실제로 비교 대상도 아니고, 템플릿의 내부 구현이 PVOID와 상관이 있지도 않습니다. 그럼 이제 템플릿을 한번 살펴보겠습니다.

템플릿의 기본 아이디어는 코드를 상세화 하지 않고 그 형틀만 만든다는 것입니다. 즉, 형틀만 만들어 두고 컴파일하는 시점에 작은것이 필요하면 형틀에서 작은 놈을 찍어내고, 큰것이 필요하면 형틀에서 큰것을 찍어내는 구조와 같습니다.

그럼 앞장에서 보였던 swap함수를 이번에는 템플릿으로 한번 작성해 보겠습니다.

template 
void swap(T &a, T &b) 
{ 
    T c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

위가 템플릿의 swap함수입니다. 여기서 변화 가능한 부분은 T입니다. 형틀에서 나머지는 모두 고정되어 있되, T만 바뀔 수 있습니다.

int a, b; 
swap(a,b); 

위와 같이 호출했다고 가정해봅시다. 그럼 컴파일러는 생각을 합니다. "음 요놈이 int형으로 호출했네. 그럼 내가 int형 함수를 하나 만들어야 겠군. " 같은 것을 떠올리겠죠.

void swap(int &a, int &b) 
{ 
    int c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

그러면서 위 함수를 형틀에서 찍어냅니다.

이번에는 아래와 같이 호출했다고 해봅시다.

double a,b; 
swap(a,b); 

그럼 컴파일러는, "어? double 버전도 필요한가 보네"... 하고는 함수를 하나 더 만듭니다.

void swap(double &a, double &b) 
{ 
    double c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

자, 그럼 결국 함수는 두개가 된 셈입니다. 프로그래머는 함수를 하나 만들었지만 컴파일타임에 컴파일러가 보고 함수를 필요한 만큼 찍어내는 것이 템플릿의 기본 전략인 셈이죠.

이번 장에서 기억해야 할 점은 세가지입니다.

  • 1. C++에서 일반화 프로그래밍에 템플릿을 사용할 수 있다.
  • 2. 템플릿은 PVOID와 관련이 없다.
  • 3. 템플릿의 구현의 핵심은 컴파일 타임에 필요한 만큼 함수를 생성하는데 있다.

3. PVOID와 템플릿...

이번 장에서는 실제 어셈블된 코드를 통해서 어떠한 방식의 접근 법이 어떠한 장점과 단점을 가지는지 알아 보도록 합시다.

먼저 아래코드는 C언어 버전입니다. 편리한대로 PVOID를 void*로 작성했습니다. 또한 어셈블해서 코드를 살펴 볼 것이므로 최대한 간단하게 작성했습니다. 모든 에러와 예외는 없다는 가정하에서 작성되었다는 점을 기억해 주십시요.

#include  
#include  

void swap(void *pa, void *pb, size_t size) 
{ 
       void *pm = malloc(size); 
        
       memcpy(pm, pa, size); 
       memcpy(pa, pb, size); 
       memcpy(pb, pm, size); 
        
       free(pm); 
} 

int main() 
{ 
       int       a=1, b=2; 
       double       c=3, d=4; 
        
       swap(&a, &b, sizeof a); 
       swap(&c, &d, sizeof c); 
       return 0; 
} 

위 코드를 어셈블한 리스팅은 아래와 같습니다.

    TITLE   D:\backup\test\pvs\pvs.c 
    .386P 
include listing.inc 
if @Version gt 510 
.model FLAT 
else 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
_DATA   SEGMENT DWORD USE32 PUBLIC 'DATA' 
_DATA   ENDS 
CONST   SEGMENT DWORD USE32 PUBLIC 'CONST' 
CONST   ENDS 
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS' 
_BSS    ENDS 
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS' 
_TLS    ENDS 
FLAT    GROUP _DATA, CONST, _BSS 
    ASSUME  CS: FLAT, DS: FLAT, SS: FLAT 
endif 
PUBLIC  _swap 
EXTRN   _free:NEAR 
EXTRN   _malloc:NEAR 
EXTRN   _memcpy:NEAR 
_TEXT   SEGMENT 
_pa$ = 8 
_pb$ = 12 
_size$ = 16 
_pm$ = -4 
_swap   PROC NEAR 

; 5    : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 51       push    ecx 

; 6    :    void *pm = malloc(size); 

  00004 8b 45 10     mov     eax, DWORD PTR _size$[ebp] 
  00007 50       push    eax 
  00008 e8 00 00 00 00   call    _malloc 
  0000d 83 c4 04     add     esp, 4 
  00010 89 45 fc     mov     DWORD PTR _pm$[ebp], eax 

; 7    :    
; 8    :    memcpy(pm, pa, size); 

  00013 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  00016 51       push    ecx 
  00017 8b 55 08     mov     edx, DWORD PTR _pa$[ebp] 
  0001a 52       push    edx 
  0001b 8b 45 fc     mov     eax, DWORD PTR _pm$[ebp] 
  0001e 50       push    eax 
  0001f e8 00 00 00 00   call    _memcpy 
  00024 83 c4 0c     add     esp, 12            ; 0000000cH 

; 9    :    memcpy(pa, pb, size); 

  00027 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  0002a 51       push    ecx 
  0002b 8b 55 0c     mov     edx, DWORD PTR _pb$[ebp] 
  0002e 52       push    edx 
  0002f 8b 45 08     mov     eax, DWORD PTR _pa$[ebp] 
  00032 50       push    eax 
  00033 e8 00 00 00 00   call    _memcpy 
  00038 83 c4 0c     add     esp, 12            ; 0000000cH 

; 10   :    memcpy(pb, pm, size); 

  0003b 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  0003e 51       push    ecx 
  0003f 8b 55 fc     mov     edx, DWORD PTR _pm$[ebp] 
  00042 52       push    edx 
  00043 8b 45 0c     mov     eax, DWORD PTR _pb$[ebp] 
  00046 50       push    eax 
  00047 e8 00 00 00 00   call    _memcpy 
  0004c 83 c4 0c     add     esp, 12            ; 0000000cH 

; 11   :    
; 12   :    free(pm); 

  0004f 8b 4d fc     mov     ecx, DWORD PTR _pm$[ebp] 
  00052 51       push    ecx 
  00053 e8 00 00 00 00   call    _free 
  00058 83 c4 04     add     esp, 4 

; 13   : } 

  0005b 8b e5        mov     esp, ebp 
  0005d 5d       pop     ebp 
  0005e c3       ret     0 
_swap   ENDP 
_TEXT   ENDS 
PUBLIC  _main 
EXTRN   __fltused:NEAR 
_TEXT   SEGMENT 
_a$ = -4 
_b$ = -8 
_c$ = -16 
_d$ = -24 
_main   PROC NEAR 

; 16   : { 

  0005f 55       push    ebp 
  00060 8b ec        mov     ebp, esp 
  00062 83 ec 18     sub     esp, 24            ; 00000018H 

; 17   :    int a=1, b=2; 

  00065 c7 45 fc 01 00 
    00 00        mov     DWORD PTR _a$[ebp], 1 
  0006c c7 45 f8 02 00 
    00 00        mov     DWORD PTR _b$[ebp], 2 

; 18   :    double  c=3, d=4; 

  00073 c7 45 f0 00 00 
    00 00        mov     DWORD PTR _c$[ebp], 0 
  0007a c7 45 f4 00 00 
    08 40        mov     DWORD PTR _c$[ebp+4], 1074266112 ; 40080000H 
  00081 c7 45 e8 00 00 
    00 00        mov     DWORD PTR _d$[ebp], 0 
  00088 c7 45 ec 00 00 
    10 40        mov     DWORD PTR _d$[ebp+4], 1074790400 ; 40100000H 

; 19   :    
; 20   :    swap(&a, &b, sizeof a); 

  0008f 6a 04        push    4 
  00091 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00094 50       push    eax 
  00095 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00098 51       push    ecx 
  00099 e8 00 00 00 00   call    _swap 
  0009e 83 c4 0c     add     esp, 12            ; 0000000cH 

; 21   :    swap(&c, &d, sizeof c); 

  000a1 6a 08        push    8 
  000a3 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  000a6 52       push    edx 
  000a7 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  000aa 50       push    eax 
  000ab e8 00 00 00 00   call    _swap 
  000b0 83 c4 0c     add     esp, 12            ; 0000000cH 

; 22   :    return 0; 

  000b3 33 c0        xor     eax, eax 

; 23   : } 

  000b5 8b e5        mov     esp, ebp 
  000b7 5d       pop     ebp 
  000b8 c3       ret     0 
_main   ENDP 
_TEXT   ENDS 
END 

너무나 사실 그대로의 코드이기에 별로 살펴 볼 필요가 없습니다.

이번에는 템플릿 버전입니다.

template 
void swap(T &a, T &b) 
{ 
       T c; 
        
       c = a; 
       a = b; 
       b = c; 
} 

int main() 
{ 
       int       a=1, b=2; 
       double       c=3, d=4; 
        
       swap(a, b); 
       swap(c, d); 

       return 0; 
} 

마찬가지의 어셈블 리스팅입니다.

    TITLE   D:\backup\test\templ\templ.cpp 
    .386P 
include listing.inc 
if @Version gt 510 
.model FLAT 
else 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
_DATA   SEGMENT DWORD USE32 PUBLIC 'DATA' 
_DATA   ENDS 
CONST   SEGMENT DWORD USE32 PUBLIC 'CONST' 
CONST   ENDS 
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS' 
_BSS    ENDS 
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS' 
_TLS    ENDS 
;   COMDAT _IsEqualGUID 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT _== 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAH0@Z 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAN0@Z 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
FLAT    GROUP _DATA, CONST, _BSS 
    ASSUME  CS: FLAT, DS: FLAT, SS: FLAT 
endif 
PUBLIC  _main 
PUBLIC  ?swap@@YAXAAH0@Z                ; swap 
PUBLIC  ?swap@@YAXAAN0@Z                ; swap 
EXTRN   __fltused:NEAR 
_TEXT   SEGMENT 
_a$ = -4 
_b$ = -8 
_c$ = -16 
_d$ = -24 
_main   PROC NEAR 

; 19   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 83 ec 18     sub     esp, 24            ; 00000018H 

; 20   :    int a=1, b=2; 

  00006 c7 45 fc 01 00 
    00 00        mov     DWORD PTR _a$[ebp], 1 
  0000d c7 45 f8 02 00 
    00 00        mov     DWORD PTR _b$[ebp], 2 

; 21   :    double  c=3, d=4; 

  00014 c7 45 f0 00 00 
    00 00        mov     DWORD PTR _c$[ebp], 0 
  0001b c7 45 f4 00 00 
    08 40        mov     DWORD PTR _c$[ebp+4], 1074266112 ; 40080000H 
  00022 c7 45 e8 00 00 
    00 00        mov     DWORD PTR _d$[ebp], 0 
  00029 c7 45 ec 00 00 
    10 40        mov     DWORD PTR _d$[ebp+4], 1074790400 ; 40100000H 

; 22   :    
; 23   :    swap(a, b); 

  00030 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00033 50       push    eax 
  00034 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00037 51       push    ecx 
  00038 e8 00 00 00 00   call    ?swap@@YAXAAH0@Z   ; swap 
  0003d 83 c4 08     add     esp, 8 

; 24   :    swap(c, d); 

  00040 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  00043 52       push    edx 
  00044 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  00047 50       push    eax 
  00048 e8 00 00 00 00   call    ?swap@@YAXAAN0@Z   ; swap 
  0004d 83 c4 08     add     esp, 8 

; 25   : 
; 26   :    return 0; 

  00050 33 c0        xor     eax, eax 

; 27   : } 

  00052 8b e5        mov     esp, ebp 
  00054 5d       pop     ebp 
  00055 c3       ret     0 
_main   ENDP 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAH0@Z 
_TEXT   SEGMENT 
_a$ = 8 
_b$ = 12 
_c$ = -4 
?swap@@YAXAAH0@Z PROC NEAR              ; swap, COMDAT 

; 10   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 51       push    ecx 

; 11   :    T c; 
; 12   :    
; 13   :    c = a; 

  00004 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00007 8b 08        mov     ecx, DWORD PTR [eax] 
  00009 89 4d fc     mov     DWORD PTR _c$[ebp], ecx 

; 14   :    a = b; 

  0000c 8b 55 08     mov     edx, DWORD PTR _a$[ebp] 
  0000f 8b 45 0c     mov     eax, DWORD PTR _b$[ebp] 
  00012 8b 08        mov     ecx, DWORD PTR [eax] 
  00014 89 0a        mov     DWORD PTR [edx], ecx 

; 15   :    b = c; 

  00016 8b 55 0c     mov     edx, DWORD PTR _b$[ebp] 
  00019 8b 45 fc     mov     eax, DWORD PTR _c$[ebp] 
  0001c 89 02        mov     DWORD PTR [edx], eax 

; 16   : } 

  0001e 8b e5        mov     esp, ebp 
  00020 5d       pop     ebp 
  00021 c3       ret     0 
?swap@@YAXAAH0@Z ENDP                   ; swap 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAN0@Z 
_TEXT   SEGMENT 
_a$ = 8 
_b$ = 12 
_c$ = -8 
?swap@@YAXAAN0@Z PROC NEAR              ; swap, COMDAT 

; 10   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 83 ec 08     sub     esp, 8 

; 11   :    T c; 
; 12   :    
; 13   :    c = a; 

  00006 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00009 8b 08        mov     ecx, DWORD PTR [eax] 
  0000b 89 4d f8     mov     DWORD PTR _c$[ebp], ecx 
  0000e 8b 50 04     mov     edx, DWORD PTR [eax+4] 
  00011 89 55 fc     mov     DWORD PTR _c$[ebp+4], edx 

; 14   :    a = b; 

  00014 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00017 8b 4d 0c     mov     ecx, DWORD PTR _b$[ebp] 
  0001a 8b 11        mov     edx, DWORD PTR [ecx] 
  0001c 89 10        mov     DWORD PTR [eax], edx 
  0001e 8b 49 04     mov     ecx, DWORD PTR [ecx+4] 
  00021 89 48 04     mov     DWORD PTR [eax+4], ecx 

; 15   :    b = c; 

  00024 8b 55 0c     mov     edx, DWORD PTR _b$[ebp] 
  00027 8b 45 f8     mov     eax, DWORD PTR _c$[ebp] 
  0002a 89 02        mov     DWORD PTR [edx], eax 
  0002c 8b 4d fc     mov     ecx, DWORD PTR _c$[ebp+4] 
  0002f 89 4a 04     mov     DWORD PTR [edx+4], ecx 

; 16   : } 

  00032 8b e5        mov     esp, ebp 
  00034 5d       pop     ebp 
  00035 c3       ret     0 
?swap@@YAXAAN0@Z ENDP                   ; swap 
_TEXT   ENDS 
END 

이 리스트는 조금 살펴볼 부분이 있는데 아래 부분입니다.

; 23   :    swap(a, b); 

  00030 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00033 50       push    eax 
  00034 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00037 51       push    ecx 
  00038 e8 00 00 00 00   call    ?swap@@YAXAAH0@Z   ; swap 
  0003d 83 c4 08     add     esp, 8 

; 24   :    swap(c, d); 

  00040 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  00043 52       push    edx 
  00044 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  00047 50       push    eax 
  00048 e8 00 00 00 00   call    ?swap@@YAXAAN0@Z   ; swap 
  0004d 83 c4 08     add     esp, 8 

보면 컴파일러는 ?swap@@YAXAAHO@Z라는 함수와 함께 ?swap@@YAXAANO@Z라는 함수를 호출 하는 것을 볼 수 있습니다. 실제로 컴파일 타임에 필요에 의해서 두가지 버전의 함수를 만든 것이죠. 전체 리스팅에서 아래로 가면 두가지 함수의 실제 코드가 따라 나오게 되는데, 스택 프레임 생성과 복사하는 크기 외에는 그렇게 큰 차이가 나지 않습니다.

여기서 우리는 결론을 내릴 수 있습니다. 템플릿의 구현은 PVOID와 전혀 상관이 없다. 단지 컴파일러가 컴파일 타임에 필요한 코드를 생성해 주는 것 뿐이다.

그럼 템플릿과 PVOID의 각기 장/단점을 알아봅시다.

PVOID로 만든 swap함수는 불행하게도 size가 실행시간에 결정되기 때문에, 힙을 사용할 수 밖에 없습니다. 물론 스택 할당 함수를 사용할 수도 있습니다. 하지만 함수 하나가 모든 자료형에 대해서 적용되므로 코드 크기는 작다고 할 수 있습니다.

템플릿 버전은 각각에 다른 버전의 함수가 만들어지기 때문에 크기가 커진다는 단점이 있습니다. 반면에 힙같은 외부 의존성이 낮기 때문에 빠르다고 할 수 있습니다.

결론을 정리하면 PVOID는 작고 느리다. 반면 템플릿은 크고 빠르다가 되겠습니다.

4. 무엇이 비교 대상인가?

그러나 사실 위의 3장은 전혀 관계없는 두가지의 것을 비교하고 있다. PVOID와 템플릿. 이건 템플릿의 구현 과정에 PVOID가 전혀 관여하지 않는다는 사실을 보여주기 위해서 설명한 장들이었습니다. 3번 장을 읽고는 의문이 들었을 것입니다. 템플릿이 크다고? 그렇다면 ATL/STL등에서 주장하는 작다는 말은 무엇인가?... 어떨때는 크다... 어떨때는 작다... 뭐야?????????????....

맞는 말입니다. 비교 대상에 따라 틀린 것이죠. 주어와 비교 대상이 빠졌기 때문에 오해가 생긴 것입니다.

템플릿은 PVOID에 비해서는 큰 코드가 만들어진다.
템플릿은 정적 라이브러리에 비해서는 작은 코드가 만들어진다.

이것이 결론입니다. 실제로도 템플릿과 PVOID를 통상적으로 비교하는 일은 없습니다. 정적 라이브러리가 가지는 단점과 비교할때 많이 사용됩니다. 정적 라이브러리는 불필요한 필요없는 코드들도 모두 링크되기 때문에 그만큼 오버헤드를 가지게 됩니다. 하지만 템플릿은 컴파일타임에 사용자가 호출한 함수들만 연결하기 때문에 필요없는 것들은 링크단계까지 아예 갈 일이 없는 것이죠. 실제로 그것이 ATL이 MFC에 비해서 가지는 장점이기도 합니다.

끝으로 C언어의 창시자인 커닝헌의 인터뷰 중 일부를 발췌해서 올립니다. 그는 C와 C++의 장,단점을 날카롭게 한가지로 일축해서 표현했습니다. 결국 도구는 다 그 용도에 맞게 써야된다는 것이죠...

Sometimes I do write C++ instead of C. C++ I think is basically too big a language, although there's a reason for almost everything that's in it. When I write a C program of any size, I probably will wind-up using 75, 80, 90% of the language features. In other words, most of the language is useful in almost any kind of program. By contrast, if I write in C++ I probably don't use even 10% of the language, and in fact the other 90% I don't think I understand. In that sense I would argue that C++ is too big, but C++ does give you may of the things that you need to write big programs: it does really make it possible for you to create objects, to protect the internal representation of information so that it presents a nice facade that you can't look behind. C++ has an enormous amount of mechanism that I think is very useful, and that C doesn't give you.

때때로 나는 C대신 C++을 사용해서 프로그램을 작성합니다. 제가 생각하기에 C++은 안에 모든 기능을 내장하기 때문이라고 하더라도 매우 큰 언어입니다. 제가 어떤 크기의 C프로그램이든 작성할때, 저는 75, 80, 90% 정도의 언어 기능을 사용합니다. 다시 말하면 어떤 종류의 프로그램이든 간에 대부분의 C언어 기능이 유용하다는 점 입니다. 이와는 대조적으로 제가 C++을 사용한다면 언어의 10% 정도도 사용하지 못합니다. 그리고 나머지 90%는 제가 이해하고 있다고 생각하지 않습니다. 이러한 점들에서 전 C++은 너무 크다고 말합니다. 그러나 C++은 큰 프로그램을 작성하는데 필요한 많은 기능들을 제공합니다. 객체를 만드는 것이 가능하며, 내부적인 정보의 표현을 보호할 수 있습니다. 그래서 결국 내부를 보지 못하게 만드는 훌륭한 외관을 표현할 수 있습니다. C++은 제가 생각하기에 아주 방대한 양의 유용한 메카니즘을 가지고 있습니다. 그리고 그것은 C언어가 당신에게 주지 못하는 점 입니다.

출처: MSDN Library
url: http://www.gosu.net/GosuWeb/ArticleMSView.aspx?ArticleCode=341

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
Posted by 마블(이환문)