[C++ 정리] 1. 문자열 다루기 (C스타일) 2015-06-16

C++로 개발하기 위해서는 C에서 사용하던 문자열 다루는 방식 또한 이해해야 한다. 이유는 많은 코드들이 C스타일로 개발되어 왔기 때문에 알고 있어야 하는 부분이다.

#include "stdafx.h"
#include <iostream>
#include <cstring>

using namespace std;

void TestChar(char* pointer);

int _tmain(int argc, _TCHAR* argv[])
{
	// 원본 문자열
	char orgString[] = "TEST STRING";
	// ※ 배열이름은 첫번째 문자열의 주소와 같다.	
	int orgStringLen = strlen(orgString);
	int orgStringAddrLen = strlen(&orgString[0]);
	cout << "STRING_LENGTH : " << orgStringLen << " :: " << orgStringAddrLen << "\n";

	// 대상 문자열
	// - strlen함수는 마지막 NULL문자는 포함하지 않고 카운트하므로 +1을 해준다.
	char* tarString = new char[orgStringLen+1];

	// 문자열 복사
	// Error - This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
	// strcpy(tarString, orgString);
	// ※ 안전한 스트링 함수의 사용 : tarString크기를 넘어서는 메모리 복사를 방지
	strcpy_s(tarString, orgStringLen + 1, orgString);

	// 문자열 출력
	// ※ 배열이름은 첫번째 문자열의 주소라고 하였으나 문자열이 출력되는 이유는?
	//    ==> 문자열의 첫번째 바이트의 주소가 전체 문자열을 지칭하기 때문
	cout << "ORG_STRING : " << orgString << "\n";
	cout << "TAR_STRING : " << tarString << "\n";

	// 배열이름이 첫번째 문자열의 주소가 되어 문제가 되는점
	// - TestChar함수에서는 문자열인지 문자인지 알수가 없다.
	char singleChar = 'A';
	char multiChar[] = "ABCDEFG";
	TestChar(&singleChar);
	TestChar(multiChar);

	// 문자열 비교
	if (strcmp(orgString, tarString) == 0){
		cout << "compare \"" << orgString << "\" :::: \"" << tarString << "\" indentical.\n";
	}
	else
	{
		cout << "compare \"" << orgString << "\" :::: \"" << tarString << "\" not indentical.\n";
	}

	// 문자열 결합
	char addString[] = "+ADD STRING";
	int addStringLen = strlen(addString);
	// int addStringLen = strlen(&addString[0]);
	char* tempString = new char[orgStringLen + addStringLen + 1];

	// 문자열 복사 + 결합
	// Error - This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
	// strcat(orgString, addString);
	// ※ 안전한 스트링 함수의 사용 : tarString크기를 넘어서는 메모리 복사를 방지
	strcpy_s(tempString, orgStringLen + addStringLen + 1, orgString);
	strcat_s(tempString, orgStringLen + addStringLen + 1, addString);
	
	if (strcmp(tarString, tempString) == 0){
		cout << "compare \"" << tarString << "\" :::: \"" << tempString <<"\" indentical.\n";
	}
	else
	{
		cout << "compare \"" << tarString << "\" :::: \"" << tempString << "\" not indentical.\n";
	}

	// 문자열 선언
	char declareArray[] = "ABCD";
	char* declarePointer = "ABCD";
	// 시작주소
	void* startAddrArray = declareArray;
	void* startAddrPointer = declarePointer;
	// 끝주소
	void* endAddrArray = declareArray+3;
	void* endAddrPointer = declarePointer+3;
	
	// 값의 출력 결과는 동일하지만 메모리에 저장되는 구조는 다르다.
	// ==> pointer로 선언할 경우 문자열 덩어리를 가리키는 값이 메모리에 따로 저장됨
	// ==> (&declarePointer != startAddrPointer)
	cout << "Value : " << declareArray << "\n";
	cout << "Array Addr : " << &declareArray << "(" << startAddrArray << "~" << endAddrArray << ")" << "\n";
	cout << "Value : " << declarePointer << "\n";
	cout << "Pointer Addr : " << &declarePointer << "(" << startAddrPointer << "~" << endAddrPointer << ")" << "\n";

	return 0;
}

void TestChar(char* pointer)
{
	// pointer는 단일문자인지 문자열인지 알수가 없다.
	// ==> 해결책은 주석을 통해서 약속한다.
	// pointer가 가리키는 변수가 단일문자라면 
	// NULL문자를 포함하고 있지 않기 때문에 쓰레기값이 뒤에 붙어서 출력된다.	
	cout << "TestChar : " << pointer << "\n";
	// 단일 문자를 출력하기 위해서는 앞에 *를 붙인다.
	cout << "TestChar : " << *pointer << "\n";
}
* 배열의 이름은 첫번째 문자열의 주소와 같다. ( orgString == &orgString[0] )
* strlen 함수는 마지막 NULL문자를 제외하고 카운트 하는 점을 유념한다.
* 문자열 함수를 다루는 법을 익힌다. (strlen, strcpy, strcmp, strcat)
* char declareArray[] = "ABCD";	char* declarePointer = "ABCD"; 두가지 선언법의 메모리 구조는 다르다. (아래그림 참조)
* 배열이름이 첫번째 문자열의 주소가 되기 때문에 유의해야 한다. (TestChar 함수의 주석 참조)

character

[C++ 정리] 정적 & 동적 메모리 할당 2015-06-15

메모리가 운영되는 방식에 대한 이해를 위해 다음 그림을 보도록 하자.

memory

프로그램이 실행되고 동작하는 원리

① 사용자가 운영체제를 통해 프로그램 실행을 요청한다.
② 하드디스크에 저장되어있던 프로그램을 작동시키기 위해 메모리의 프로그램 코드 영역에 올린다.
③ CPU는 프로그램 코드를 읽어 호출규약(Calling Convention)에 의해 메모리를 관리하고 명령문들을 실행한다.
④ 프로그램 실행을 위해 동적메모리가 할당되면 FreeStore 영역을 사용하게 된다.(아래로 넓힌다.)
⑤ 시스템이 작동하기 위해 CPU가 임시적인 정보를 스택에 저장하게 되면 FreeStore 영역을 사용하게 된다.(위로 넓힌다.)
※ Heap메모리와 Stack메모리의 많은 사용으로 FreeStore영역이 없어져 만나게 된다면 메모리가 부족한 상태가 된다.

메모리 영역별 설명

* Program Code : 실행한 프로그램의 코드가 저장되어있다.
* Data : 전역변수와 static변수가 할당되고 프로그램 종료시까지 남아있다.
* Heap : 동적으로 할당된 메모리영역이며 프로그래머에 의해 할당(new(CPP) or malloc(C)) / 해제(delete(CPP) or free(C)) 된다.
* Stack : 지역변수와 매개변수가 할당되고 함수를 빠져나가면 자동 소멸된다.

프로그램 개발시에는 3가지 영역으로 이해하고 있으면 된다.

* Static 메모리 : 데이터 영역에 저장되고 지속성이 보장됨.
* Heap 메모리 : 힙 영역에 저장되고 지속성이 보장되나 프로그래머에의해 꼼꼼한 관리(메모리 해제)가 필요함.
* Stack 메모리 : 스택 영역에 저장되고 함수를 빠져나가면 자동 소멸됨.
※ 정적메모리 = (전역변수, static변수, 지역변수, 매개변수 저장) // 동적메모리 = (프로그램 실행중 생성되고 소멸되는 변수 저장)

동적메모리 할당

memory2

#include "stdafx.h"
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	int* pointer = new int[100];

	pointer[10] = 100;	

	cout << "pointer Address : " << &pointer << "\n";
	cout << "new Address : " << pointer << "\n";
	cout << "value Address : " << pointer[10] << "\n";	

	//delete[] pointer;
	delete[] pointer;
	// 동적으로 할당한 int[100]은 해제하였더라도
	// 정적으로 할당된 pointer는 쓰레기 값을 가지게 되므로 NULL로 초기화해 안전하게 하자.
	// ※ 해제한 메모리를 또 해제할 경우 에러가 발생해 프로그램동작이 멈추게 된다.
	pointer = NULL;
	cout << "init Address : " << pointer << "\n";

	// pointer를 NULL로 초기화(메모리 주소값이 NULL일경우 아무일도 하지 않는다)
	// 했기 때문에 실수로 다시 해제를 하였더라도 문제가 없다.
	delete[] pointer;

	// new => delete, new[] => delete[] 로 쌍을 맞춰서 사용한다.
	// - 쌍을 맞추기 않을 경우 일부만 해제되는 등의 문제가 발생할 수 있다.
	int* onePointer = new int;
	*onePointer = 100;
	delete onePointer;

	return 0;
}
* 메모리를 동적으로 할당하고 해제한다.
* 메모리 해제 후 포인터는 NULL로 꼭 초기화해 에러를 방지한다.
* new => delete, new[] => delete[] 로 쌍을 맞춰서 사용한다.

[C++ 정리] 포인터형 함수 2015-06-15

함수 또한 변수와 마찬가지로 메모리의 일부영역을 차지하고 있어 참조가 가능하고 이를 파라미터로 넘길 수도 있다. 다음예제는 OS를 구분하여 함수를 구분하여 실행할 수 있도록 함으로써 함수 내부에서는 OS버전에 관계없이 함수를 호출할 수 있게 해준다.

#include "stdafx.h"
#include <iostream>
#include <windows.h>

using namespace std;

typedef void(*FUNCTION_STORE) ();

void FunctionA();
void FunctionB();
void TestFunc(FUNCTION_STORE pFunctionStore);
BOOL IsCurrentProcess64bit();
BOOL IsCurrentProcessWow64();
BOOL Is64BitWindows();

int _tmain(int argc, _TCHAR* argv[])
{	
	FUNCTION_STORE pFunction;	

	if (Is64BitWindows())
	{
		// 64비트의 경우 FunctionA를 참조
		pFunction = &FunctionA;
	}
	else
	{
		// 64비트가 아닐경우 FunctionB를 참조
		pFunction = &FunctionB;
	}	

	// 구분된 함수를 인자로 전달
	TestFunc(pFunction);

	return 0;
}

void FunctionA()
{
	cout << "Call Function A - 64bit OS.\n\n";
}

void FunctionB()
{
	cout << "Call Function B - 32bit OS.\n\n";
}

void TestFunc(FUNCTION_STORE pFunctionStore)
{
	// OS 버전과 관계없이 동일 코드를 실행한다.
	(*pFunctionStore)();
}

BOOL IsCurrentProcess64bit()
{
#if defined(_WIN64)
	return TRUE;
#else
	return FALSE;
#endif
}

BOOL IsCurrentProcessWow64()
{
	BOOL bIsWow64 = FALSE;
	typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
	LPFN_ISWOW64PROCESS fnIsWow64Process;

	fnIsWow64Process = (LPFN_ISWOW64PROCESS)
		GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
	if (!fnIsWow64Process)
		return FALSE;

	return fnIsWow64Process(GetCurrentProcess(), &bIsWow64) && bIsWow64;
}

BOOL Is64BitWindows()
{
	if (IsCurrentProcess64bit())
		return TRUE;

	return IsCurrentProcessWow64();
}

function3