2016년 4월 30일 토요일

Shell Sort - Gap Comparison

Hibbard Gap (2^n-1) 과 가장 좋은 성능을 보인다는 Gap (1, 4, 10, 23, 57, 132, 301, 701) 두 Gap을 비교하였다.

랜덤으로 입력받은 100,000개의 데이터를 사용하였다.
  • Source


  • Result



2016년 4월 9일 토요일

Debug Mode vs Release Mode (디버그 모드 vs 릴리즈 모드)

Debug Mode와 Release Mode는 실행파일에서 차이가 있습니다.

Debug Mode는 컴파일시에 Debug정보를 실행파일에 같이 삽입합니다.
Release Mode에서는 Debug 관련 정보를 삽입하지 않습니다.

따라서 Release Mode로 만든 파일이 속도도 빠르며 실행파일 크기도 작습니다.

또한 Debug Mode의 실행파일은 컴파일러가 최적화를 해주지 않지만, Realase Mode의 실행 파일은 최적화를 해줍니다.




다음과 같이 소스 파일이 있을 때, 반복문을 1000번 돌 때마다 a에 10을 대입해줍니다.
우리가 눈으로만 봐도 알 수 있듯이, a에 10만 대입하고 끝내면 될 걸, 의미 없는 반복문이 돌고 있습니다. 컴파일러 또한 이것을 인지합니다.

  • Debug Mode DisAssembly Screen

  • Release Mode DisAssembly Screen


변수 a와 i, 반복문 과정을 모두 생략했으며 printf함수에 10(0Ah)을 출력한 명령어만 들어간 것을 확인할 수 있습니다.


  • Debug Mode File Size


  •  Release Mode File Size

실행 파일 크기가 Debug 모드에 비해 작은 것을 확인할 수 있습니다.



 ※ 우리는 실제 프로그램을 만들 때, 개발과정에서는 Debug Mode로 컴파일 하지만, 실제 제품으로 출시하기 위해서는 최종적으로 Release Mode로 컴파일이 되야 합니다. 또한 두 Mode 결과가 일치해야 합니다.

Build and Compile Difference (빌드와 컴파일 차이점)

비쥬얼 스튜디오에서 우리는 보통 F7을 눌러 에러 상황을 확인한다. 바로 F7이 Build이며, Ctrl+F7이 Compile이다. 이 둘의 차이점은 다음과 같습니다.

Compile ⊂ Build 이며,
간단하게 말하자면
Build = Compile + Link 인 것입니다.

Compile만 하게 되면 .obj 파일인 목적파일만 생성되며 링킹 과정을 하지 않습니다.
Build를 하게 되면 목적파일이 생성되고 링킹과정 뿐만 아니라 실행파일도 생성됩니다.


다음과 같이 두 개의 파일이 있을 때,








Test.c 파일의 변수 a를 주석처리하고 빌드(F7)을 하게 되면, 다음과 같은 에러가 납니다.





외부 변수 a를 찾을 수 없다는 에러입니다. 즉 링킹 과정에서 Test.c와 TestMain.c를 링킹 하였지만, 선언 된 변수 a가 주석처리되어 찾을 수 없다는 의미입니다.



하지만 컴파일(Ctrl+F7)을 하게 되면 에러가 나타나지 않습니다.



왜냐하면, 각각의 두 소스파일에 대해 .obj 목적파일만 생성되었기 때문입니다.




2016년 4월 7일 목요일

Effective C++ (ECPP) 'Chapter 1 - C++에 왔으면 C++의 법을 따릅시다'

  • 항목 1 : C++를 언어들의 연합체로 바라보는 안목은 필수
 C++는 단일 언어로 보지 않고 여러개의 하위 언어를 제공한다고 봐야합니다. C++는 한 가지 프로그래밍 규칙인 통합 언어가 아니라 하위 언어들의 연합체입니다. C++를 사용한 효과적인 프로그래밍 규칙은 C++의 어떤 부분을 사용하느냐에 따라 달라집니다. C++의 하위 언어는 다음과 같이 4가지 입니다.
- C : template, exception, overloading 등등이 없는 C
- 객체 지향 개념의 C++ : class, capsulation, inheritance, polymorphism, virtual function 등의 class를 쓰는 c
- template c++ : template metaprogramming(TMP)
- STL : container, iterator, algorithm, function object가 서로 얽혀 돌아가는 규약


  • 항목 2 : #define을 쓰려거든 const, enum, inline을 떠올리자
1. #define은 컴파일러에게 소스코드가 넘어가기 전에 선행 처리자가 숫자 상수로 바꾸어 버리기 때문에, #define에 사용한 이름이  컴파일러가 쓰는 기호 테이블에 들어가지 않는다. 숫자 상수로 바뀐 부분에서 에러가 발생한다면 에러의 원인을 찾기 힘들 것이다. 따라서 매크로 대신 const 상수를 사용한다.
 #define KYS  1   =>  const int KYS = 1;


2. 정수 타입의 정적 클래스 상수에 대한 클래스 내 초기화를 금지하는 구식 컴파일러일 때, class를 컴파일 하는 도중에 클래스 상수의 값이 필요한 경우는 enum을 사용해야한다. 
(배열 멤버를 선언하는 경우가 대표적이다.)
class A{
private:
 enum { num = 5};
 int tmpArr[num];
}
이를 enum hack이라고 말한다.

3. 함수처럼 쓰이는 매크로를 만들려면 #define대신 inline을 사용한다. #define으로 매크로 함수를 정의할 경우, 인자마다 괄호를 씌어주어야 하며, 골치 아픈 일이 많이 발생할 여지가 있다. inline을 사용할 경우 인자마다 괄호를 사용할 필요가 없고, #define에서 발생할 문제들이 해결되며 함수의 유효범위 및 접근규칙도 그대로 따라 갑니다. 


  • 항목 3 : 낌새만 보이면 const를 들이대 보자!
1. const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 줍니다.  const는 어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환 타입, 멤버 함수에도 붙을 수 있습니다.

2. 컴파일러 쪽에서 보면 bitwise constness를 지켜야 하지만, 사용자는 logical constness를 사용해서 프로그래밍 해야 합니다.

3. 상수 멤버 및 비 상수 멤버 함수가 기능적으로 똑같게 구현되어 있을 경우에는 코드 중복을 피하는 것이 좋은데, 이때 비상수 버전이 상수 버전을 호출하도록 만들어야 합니다. (상수버전이 비상수 버전을 호출하는 경우는 안정성에 문제가 생깁니다.)

ex)
const char& operator[](std::size_t position)const {
return text[position];
}
char& operator[](std::size_t position) {
return
const_cast<char&>(
static_cast<const TextBlock&>(*this)[position]
);
}



  • 항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자

1. primitive type 객체는 직접 초기화합니다. primitivie type 객체는 경우에 따라 저절로 초기화 되기도 하기 안 될 수도 있기 떄문입니다.

2. 생성자에는 데이터 멤버에 대하여 생성자 함수 내부에 대입문을 사용하여 멤버를 초기화 하지 말고 멤버 초기화 리스트를 사용하는게 좋다.
 대입문을 사용할 경우, 데이터 멤버가 사용자 객체 일 때, 대입 하기 전에 기본 생성자를 호출하여 기본 값들로 초기화한다. 그 후에 대입문을 통해 사용자 객체 멤버에 대입을 통한 값을 저장한다. 즉, 기본 생성자를 호출하는 쓸데 없는 작업이 발생한다.
 멤버 초기화 리스트는 해당 멤버 값에 대한 생성자 함수를 호출 하기 때문에 기본생성자를 호출하는 일은 발생하지 않는다. 따라서 멤버 초기화 리스트를 사용하는 것이 더 효율적이다.

3. 여러 translate unit에 있는 non-local static object들의 초기화 순서 문제는 피해서 설계해야 한다. 즉, non-local static object를 local static object로 바꾸면 된다.
 => 여러 소스코드에 각각 객체들이 생성한다고 가정한다. 다른 소스코드의 객체를 사용하려 할 때, 해당 소스코드의 객체가 초기화 되지도 않은 상태에서 해당 객체를 사용할 경우 문제가 발생한다. 이러한 경우를 방지하기 위해 local static object(함수 안에 static으로 객체를 생성한 후 반환) 방식을 사용하여 해당 객체를 사용하려 할 때, 함수를 호출하여 그 함수가 객체를 생성 및 초기화한다. 그 후에 함수는 해당 객체를 반환하여 그 객체를 사용하는 방식이다.


◈ local static object
- 함수 안에 static으로 선언된 객체

◈ non-local static object
- 전역 객체
- 네임스페이스 유효범위에서 정의된 객체
- 클래스 안에서 static으로 선언된 객체
- 파일 유효범위에서 static으로 정의된 객체

2016년 4월 3일 일요일

Proper Subset


  • Problem
집합을 부분 집합 크기 순으로 정렬하는 알고리즘

  • Source
  • Result Screen


Bit Calculation Marco


  • Source

#pragma once

// 한 비트 클리어
#define clear_bit(data, loc) ( (data) &= ~( 0x1<<(loc) ) )
// 연속된 여러 비트 클리어
#define clear_bits(data, area, loc) ( (data) &= ~( (area)<<(loc) ) )

// 한 비트 설정
#define set_bit(data, loc) ( (data) |= 0x1<<(loc) )

// 연속된 여러 비트 설정
#define set_bits(data, area, loc) ( (data) |= (area)<<(loc) )

// 한 비트 반전
#define invert_bit(data, loc) ( (data) ^= (0x1<<(loc)) )

// 연속된 여러 비트 반전
#define invert_bits(data, area, loc) ( (data) ^= ( (area)<<(loc) ) )

// 비트 검사
#define check_bit(data, loc) ( (data) & 0x1<<(loc) )

// 비트 추출
#define extract_bits(data, area, loc) ( ( (data)>>(loc) ) & (area) )




◈ reference
 Book : 프로그래머가 몰랐던 멀티코어 CPU 이야기

Selection, Quick Sort, Merge Sort

  • Source


Error : iterator not incrementable, decrementable, dereferencable, incompatible



 그래픽스 관련 코딩을 하면서 STL을 사용하다가 위와 같은 에러가 발생 하였는데, 이유를 찾는데 꽤나 시간이 소요 되었다. STL을 다루는데 서투른 사람은 나와 같은 실수를 저지를거 같아서 글로 남기게 되었다.

 STL의 Sequence Container 사용하면서 다음과 같은 runtime error 문구를 발견 한 적이 있을 것이다. 이 같은 에러는 컴파일 에러가 아니기 때문에 확인할 수가 없어서, 직접 디버깅을 하여 찾아봐야 한다.

 보통 해당 컨테이너의 범위를 넘어가면 런타임 에러로 out of range를 표시해준다. 이런 에러는 쉽게 원인을 찾을 수 있다. 저 에러도 어찌보면 비슷한 경우에 발생하지만, 처음 저 에러를 겪는 사람들은 혼란스러울 것이다. 디버깅을 하지 않고, 출력으로 찾는다면 많은 시간이 소요 될 것이다. 이 글을 쓰는 나도 디버깅을 통해 저 에러의 원인을 발견하였다.

 이 에러가 발생하는 대부분의 이유가 다음과 같다고 생각한다.

for (iter = AEL.begin(); iter != AEL.end(); iter++;) {
}

 STL을 사용하는 사람이라면, 이와 같은 반복문 형식을 자주 사용할 것이다.

 컨테이너의 원소를 삭제하려고 한다면, 어떻게 할 것인가? 만약 삭제후에도 반복문을 계속 돌아야 한다면?

for (iter = AEL.begin(); iter != AEL.end(); iter++;) {
if (Condition) {
AEL.erase(iter);
}
}

 이렇게 구현하는 사람이 많을 것이다. 하지만 빌드해보면 에러가 발생할 것이다. 그렇다면 이유는 무엇일까? 단순하게 코드로만 보면 맞는거 같지만, STL iterator의 원리와 컨테이너의 멤버함수 원리만 알면 쉽게 알 수 있다.

 erase(iter) 멤버함수는 iter라는 반복자가 가리키는 원소를 제거한다. 원소를 제거 후 바로 다음 원소를 가리키는 반복자를 반환한다.

 이렇게 말해도 에러의 원인을 눈치채지 못하는 사람이 많을 것이다.


 다음 그림과 같이 해당 반복자 부문을 erase 멤버함수를 통해 제거하면 iter 반복자가 무엇을 가리키고 있는지 알 수 없다. 해당 원소가 제거 됬기 때문이다.
"iter++가 있잔아요? 그럼 iter이 다음 원소를 가리키는게 아닌가요?" 라고 말할 사람들이 있을 것이다.

 예를 들면 iter 반복자가 컨테이너 원소 값 주소 0x0001 번지를 저장하고 있다고 하자.
해당 반복자가 erase되면 컨테이너 원소 0x0001번지는 소멸된다. 그럼 반복자는 쓰레기 값을 가리키고 있는것이다. 반복자가 가리키는 값을 기준으로 증가하거나 감소할 수 있는데 해당 반복자는 무엇을 가리키는지 알 수 없기 때문에, 다음과 같은 에러가 발생할 것이다.
(Error : iterator not incrementable, decrementable, dereferencable, incompatible)

 "포인터처럼 다음 주소를 가리키면 되는게 아닌가" 라고 생각할 수도 있다. 이건 지극히 나의 주관적인 생각이지만,  STL 자체적으로 안전성 때문이라고 생각한다. 반복자는 포인터를 흉내낸 형태이지만, STL의 여러 컨테이너와 호환을 위해서 iterator 내부적으로 막아논 것으로 예상된다. 이 부분에 대해서는 더 공부 후 알게 되면 글을 수정하도록 하겠다.

 그렇다면 해결책은 무엇일까? erase가 반환하는 다음 원소를 iter 반복자가 다시 저장하는 방법으로 이용해야한다. 또한 반복문안에 iter++을 제거후 논리적으로 표현해야한다. 왜냐하면 erase가 반환후 다음 원소를 가리키는데 거기에 또 iter++를 하게되면 반복자가 두 번 이동하기 때문이다.

for (iter = AEL.begin(); iter != AEL.end();) {
if (Condition) {
iter = AEL.erase(iter);
}
else {
iter++;
}
}

 이와 같이 반복문의 증감부분을 없애고, 반복문 안에 조건문을 통해 erase가 반환한 반복자를 사용하면 된다.

2016년 4월 1일 금요일

Multidimensional Array Fuction Parameter Delivery & Return

포인터 배열 : 배열에 메모리 주소 값을 저장하는 배열
int* a[3] = {0xf02f1022, 0xaddf1022, 0xf4221124 }

배열 포인터 : 배열을 가리키는 포인터
int (*a)[4] : 한 행마다 열을 4만큼씩 표현하는 2차원 배열

1차원 배열 변수의 값은 해당 배열공간을 가리키는 주소 이므로
int a[10];
int *b = a;
가 가능하다.

파란색 = 옳은 것
빨간색 = 틀린 것


  • 매개변수 (Parameter)
다차원 배열을 함수의 인자로 전달할 때 다음과 같이 전달할 수 있다.
void Ex(int a[2][2]) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++)
cout << a[i][j] << " ";
cout << endl;

}
}
void main() {
int ary[2][2]{ 1,2,3,4 };
Ex(ary);
}
하지만 이와 같은 방법은 범용적이지 못하고 한정적이다. 2행 2열 크기의 배열만 전달할 수 있기 때문이다.
---------------------------------------------------------------------------------------------------

void Ex(int a[][]) {
 // ditto
}
그렇다면 이렇게 함수를 만드는 경우를 생각할 수 있을 것이다. 하지만 이 경우도 옳지 않다. 왜냐하면 힘수로 배열을 전달할 때, 배열의 첫번째 주소를 전달한다. 매개변수로 받을 변수는 이 주소가 몇 행 몇 열인지 모르기 때문에 컴파일 오류가 발생할 것이다. 따라서 배열에 명시적으로 명시해줘야한다.
---------------------------------------------------------------------------------------------------

void Ex(int a[2][]) {
 // ditto
}
다음과 같이 행만 명시한다면, 이 부분도 잘못 됬다. 만약에 2행 2열의 배열이 함수의 인자로 전달되면 매개변수 a는 1행부터 데이터를 몇 번째 열까지 데이터를 받아야 할지 모르기 때문이다.
---------------------------------------------------------------------------------------------------

void Ex(int a[][2]) {
 // ditto
}
이와 같은 방법으로 사용하면 된다.
1 2 3 4 5 6 7 8 9 10 인 배열이 Ex함수의 매개변수로 전달된다면
a배열에 저장된 데이터는 {{1,2}, {3,4}, {5,6}, {7,8}, {9,10}}과 같이 표현 할 수 있다.
열에 명시적으로 숫자를 주었기 때문에, 컴파일러가 위와 같이 해석할 수 있다.

void Ex(int (*a)[2]) {
 // ditto
}
또는 배열 포인터를 이용한다.
int형 포인터를 저장할수 있는 배열이라는 의미이다.
쉽게 풀어서 말하자면, 2개의 데이터를 가진 배열의 주소를 가리키는 포인터이다.
---------------------------------------------------------------------------------------------------


  • 반환 (Return)
다차원 배열을 반환한다고 할 때, C에 익숙치 못한 사람은 다음과 같이 생각할 것이다.

int** Ex2() {
static int a[3][3] = { 1,0,0,0,1,0,0,0,1 };
return a;
}
void main() {
int** dm;
dm = Ex2();
}
 위 방법은 잘못 됬다. Ex2함수에서 int a[3][3] 자체는 3행 3열의 배열을 가리키는 주소이기 때문에 더블 포인터로 반환 할 수 없다. 다차원 배열들을 표현하기 위해 int a[3][3]와 같이 사용한 것이지, 실제로는 한 메모리 공간에 있는 것이다. (동적으로 다차원 할당하는 경우는 힙안에서 여러 공간에 있을 수 있다.)
---------------------------------------------------------------------------------------------------

int* Ex2() {
static int a[3][3] = { 1,0,0,0,1,0,0,0,1 };
return (int*)a;
}
void main() {
int (*dm)[3];

dm = (int(*)[3])Ex2();

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
cout << dm[i][j] << " ";
cout << endl;
}
}
 네트워크 프로그래밍을 해본 사람은 다음과 같이 형변환을 이용하여 반환할 수도 있다.
 2차원 배열도 실제로는 1차원 배열이 쭉 나열 되있는 것이므로, int형 포인터로 캐스팅후 반환하면 된다. main문에서도 int*형으로 받아야 한다. 2차원 배열로 받을려면 명시적으로 (int(*)[3])와 같이 캐스팅 해줘야한다.
 함수 안에 static으로 선언한 이유는 지역 변수는 함수가 끝나면 소멸되기 때문이다. 만약에 static을 사용하지 않고 반환하게 되면 배열의 주소 값이 반환되게 되는데, main문으로 돌아와 이 주소값을 다른 변수에 저장하여도, 이미 이 배열 공간은 함수가 반환되면서 스택에서 소멸되었기 때문이다.
 위 방법은 어찌보면 편법에 가깝다. 캐스팅 변환 없이 반환하려면 반환형을 명시적으로 표현 해줘야한다.
---------------------------------------------------------------------------------------------------

int(*Ex2(void))[3]{
static int a[3][3] = { 1,0,0,0,1,0,0,0,1 };
return a;
}
void main()
{
int (*dm)[3];
dm = Ex2();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
cout << dm[i][j] << " ";
cout << endl;
}
}
위와 같이 사용하면 다차원 배열을 반환할 수 있다.
---------------------------------------------------------------------------------------------------

팁으로 (int(*)[3])와 같은 표현은 가독성이 떨어진다. 따라서 다음과 같이 typedef 해주는 것이 좋다.
typedef int(*Dimension)[3];
Dimension Ex2(void){
static int a[3][3] = { 1,0,0,0,1,0,0,0,1 };
return a;
}
void main()
{
Dimension dm;
dm = Ex2();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
cout << dm[i][j] << " ";
cout << endl;
}
}
---------------------------------------------------------------------------------------------------

3차원 이상 배열도 다음과 같은 방법으로 코딩 하면 된다.
int (*Ex2(void))[2][2]{
static int a[][2][2] = { { {1,2} ,{1,2} },{ {1,2} ,{1,2} } };
return a;
}
void main() {
int (*dm)[2][2] = Ex2();

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++)
cout << dm[i][j][k] << " ";
cout << endl;
}
cout << endl;
}
}
---------------------------------------------------------------------------------------------------

가독성을 생각한다면 다음과 같이 코딩한다.
typedef int(*Dimension)[2][2];
Dimension Ex2(void){
static int a[2][2][2] = { { {1,2} ,{1,2} },{ {1,2} ,{1,2} } };
return a;
}
void main() {
Dimension dm = Ex2();

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++)
cout << dm[i][j][k] << " ";
cout << endl;
}
cout << endl;
}
}
---------------------------------------------------------------------------------------------------

3차원 배열은 문자열에 많이 사용 된다.
typedef char(*Dimension)[2][10];
Dimension Ex2(void){
static char a[2][2][10] = { { "abcd" , "qqqq" },{ "adzoo" , "vvvv" } };
return a;
}
void main() {
Dimension dm = Ex2();

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
cout << dm[i][j] << endl;
}
cout << endl;
}
}
---------------------------------------------------------------------------------------------------

 이와 같은 형식들은 외우는 것이 아니다. 포인터에 대한 메모리 관점에서 생각해보면 전혀 외울 필요가 없을 것이다.