일반적으로 쓰는 정렬 알고리즘도 참 많은게 있다.

 

그중, 흔히 말하는 시간 복잡도 O(n²) 정렬들은 구현도 간단하여 자주 사용하는 편인데, 정작 각 정렬에 대한 차이를 명확하게 답변하지 못할때가 종종 있어서 복습 겸 정리를 좀 할까 싶다.

 

 

 

1. 해당 문서의 예시 코드는 C/C++ 기반으로 작성되었습니다.

2. 모든 설명은 오름차순을 기준으로 합니다.


삽입 정렬


삽입정렬은 "맨 처음부터 진행하면서 내 위치일 것 같은곳으로 삽입" 하는 정렬이다.

예를들어, [5 3 2 1 4] 의 순서로 자료가 있다고 하면

 

시작 - [5 3 2 1 4]

1회차 - [5 3 2 1 4] --> [3 5 2 1 4]

2회차 - [3 5 2 1 4] --> [2 3 5 1 4]

3회차 - [2 3 5 1 4] --> [1 2 3 5 4]

4회차 - [1 2 3 5 4] --> [1 2 3 4 5]

종료 - [1 2 3 4 5]

 

의 방식으로 정렬이 이루어진다.

 

삽입 정렬의 특징이자 문제점이, "삽입" 방식으로 이루어지다보니 중간에 데이터를 삽입하면 뒤로 밀어버려야 한다는 특징이 있는데..

 

코드를 보면서 설명하도록 하겠다.

 

void Insertion_Sort(int *data, int n)	//data = int 동적배열, n = 배열 길이
{
	int i, j;
    int temp;		//임시 기억용 변수
    
    for (i = 1 ; i < n ; i++)  //2번째 원소부터 검사하므로 index가 1부터 시작함.
    {
    	j = i;
    	temp = data[i];
        
        while (--j >= 0 && data[j] > temp)	//검사중인 원소부터
        {					//0번 인덱스 원소까지
        	data[j+1] = data[j];		//한칸씩 뒤로 당김
        }					//* while문 최초 실행때 [j+1] 원소는 temp에 들어간 원소와 같다.
        data[j+1] = temp;			// j = i = 1일때, j + 1은 0이 될 수 있다.
    }
}

 

흐름은 다음과 같다.

 

시작을 i 번 원소부터 했을때 

1. [i - 1] 원소가 [i] 보다 클때 = i 원소의 값을 [i - 1]로 바꾼다. 

2. [i - 1] 원소가 [i] 보다 작을때 = 값을 바꾸지 않고 종료한다.

3. while 루프 종료시 = 최종 위치에 임시값을 삽입한다. (1, 2번을 수행하면서 생긴 "중복 값" (=빈 자리) 에 삽입)

 

 

여기 나오는 모든 정렬들이 그렇겠지만, 삽입정렬도 N번째 값 정렬 시 N-1만큼의 순회를 돌아야 하므로,
시간 복잡도는 O (N(N-1)) = O(n² - n) = O(n²) 로 수렴한다.

 

구현이 간단하고, 나름 빠른 편 (선택 정렬과 버블정렬은 순수하게 O(n²) 의 시간 복잡도를 가진다) 이나, 배열이 길어질수록 효율이 떨어진다는 단점이 존재한다.

 

이미 정렬이 되어있는 경우에는 비교를 제외한 추가적인 연산이 필요 없으므로 최선의 케이스가 나오며, 이때 시간 복잡도는 O(n) 이 나온다.

 

 

 

 

 

 

선택 정렬


선택 정렬은 "리스트의 최소값부터 정렬하는" 알고리즘이다.

 

삽입정렬과 헷깔릴 수 있는데, 선택정렬은 N번째 자료를 정렬시에 N-1번까지는 이미 정렬이 완전히 끝난 상태라는것이 가장 큰 차이. (위에도 언급했지만 삽입정렬은 N번째 자료 정렬시 정렬이 끝난다는 보장을 할 수 없다.)

 

선택 정렬은 다음과 같이 작동한다.

 

시작 - [5 3 2 1 4]

1회차 - [5 3 2 1 4] --> [1 3 2 5 4]

2회차 - [1 3 2 5 4] --> [1 2 3 5 4]

3회차 - [1 2 3 5 4] --> [1 2 3 5 4]  // 이미 순서가 맞으므로 추가 정렬 X

4회차 - [1 2 3 5 4] --> [1 2 3 4 5]

종료 - [1 2 3 4 5]

 

 

선택 정렬은 N번째 자료 정렬시 그 앞까지 (N-1까지)는 정렬이 완료되었다는 보장을 할 수 있는데, 현재 값이 "어느 위치인지" 알기 위해서 결국 모든 자료를 검사해야한다는 불상사가 발생한다.

 

이게 무슨소리냐면... 시간 복잡도가 어떤 상황에서든 O(n²) 라는 것이다.

삽입 정렬이나 버블정렬의 경우에는, 비교하는 대상과 본인이 정렬이 되어있다면 추가적인 연산을 하지 않는데 선택정렬은 그런거 모르겠고 이미 끝난 정렬도 함수 안에서 정렬을 수행한다는 뜻이다.

 

선택정렬은 "다음 최소값"이 어떤 값인지 알아야 하는 문제가 있어, 삽입정렬이나 버블정렬보다는 로직이 조금 복잡한 편이다.

 

void SelectionSort(int *data, int n)
{
	int i, j, indexMin, temp;		//최소값 인덱스와 임시값 변수 2종이 추가로 필요함.
    
    for (i = 0 ; i < n-1 ; i++)
    {
    	indexMin = i;
        
        for (j = i+1 ; j < n ; j++)
        {
        	//해당 for문은 정렬되지 않은 구역에서 "최소값"을 찾는 로직이다.
            
        	if (data[j] < data[indexMin])
            {
            	indexMin = j;				
            }
        }
        temp = data[indexMin];	//찾아낸 최소값을 temp에 저장.
        data [indexMin] = data[i];	//위치 교환
        data[i] = temp;	//temp값을 위치에 저장
    }    
}

 

 

흐름은 다음과 같다.

 

시작을 i 번 원소부터 했을때 

1. i번 부터 N번까지 데이터 중 최소값을 찾는다.

2. 최소값이 발견되면 해당 값의 인덱스를 기록한다.

3. 최소값을 임시 변수에 저장한다.

4. i번 값과 최소값이 위치한 인덱스의 값을 교체한다.

5. i번 값에 임시 변수에 저장된 값을 집어넣는다.

 

해당 과정은 값이 이미 정렬이 되어있던, 아니던 상관없이 작동한다. 그러니까 시간 복잡도가 그모양이지

 

 

 

버블 정렬


참 자주 얘기하게 되는 정렬 중 하나인 버블 정렬이다.

 

버블 정렬도 길게 설명하면 어지러운 얘기가 많지만, 요약하면 단 한가지로 요약 가능하다.

 

N번째와 N+1번째가 정렬되어 있지 않으면 교체, 정렬 되어있으면 넘어간다.

 

이 문장이 바로 버블 정렬의 본질이라고도 볼 수 있겠다..

 

 

버블 정렬은 다음과 같이 작동한다.

 

시작 - [5 3 2 1 4]

1회차 - [5 3 2 1 4] -> [3 5 2 1 4] -> [3 2 5 1 4] -> [3 2 1 5 4] -> [3 2 1 4 5]

2회차 - [3 2 1 4 5] -> [2 3 1 4 5] -> [2 1 3 4 5] -> [2 1 3 4 5] // 3이 4보다 작으므로 종료

3회차 - [2 1 3 4 5] -> [1 2 3 4 5] -> [1 2 3 4 5] // 2가 3보다 작으므로 종료

종료 - [1 2 3 4 5]

 

void BubbleSort (int *data, int n)
{
	int i, j, temp;
    for (i = 0 ; i < n-1 ; i++)
    {
    	for (j = 0 ; j < n-1-i ; j++)		//이미 정렬된 후위 i번은 확인 할 필요 없음.
        {
        	if (data[j+1] < data[j])
            {
            	temp = data[j+1];
                data[j+1] = data[j];
                data[j] = temp;
            }        
        }    
    }
}

 

흐름은 다음과 같다.

 

시작을 i 번 원소부터 했을때 

1. i번 원소와 i+1원소를 비교한다.

2. i번 원소가 i+1 보다 크면 둘을 교환한다.

3. i번 윈소가 i+1 보다 작거나 같으면 교환하지 않는다.

4. 1~3을 n번 반복한다.

 

 

 

 

 

 

요약


 

  삽입 정렬 선택 정렬 버블 정렬
최악의 경우 O(n²) 비교 및 교환 O(n²) 비교, O(n) 교환 O(n²) 비교, O(n²) 교환
최선의 경우 (순정렬인 경우) O(n) 비교, O(1) 교환 (미실시) O(n²) 비교, O(n) 교환 O(n) 비교, O(1) 교환 (미실시)
평균인 경우 O(n²) 비교 및 교환 O(n²) 비교, O(n) 교환 O(n²) 비교, O(n²) 교환

 

 

 

 

자주 쓰이지만, 그렇다고 설명하려고 하면 종종 헷깔리는 O(n²) 3종류에 대해 간단히 비교해보았다.

 

 

좀 옛날에 작성한거라 정리가 덜 되어있습니다.. 시간날때마다 다듬을게요


1. 포인터 & 레퍼런스 

1) 포인터 : 특정 객체 / 변수 / 함수의 메모리 상 주소.

데이터를 복사하지 않고 메모리에 직접 접근하여 사용하기 위함.

 

2) 레퍼런스 : 특정 객체 / 변수 / 함수의 별명

특정 데이터의 원본이 필요할 때 사용.

 

3) 레퍼런스 접근 (&a)와 포인터 접근 (*a)를 쓰는 이유
(ex - void foo(* data) )

* 레퍼런스 : NULL 비허용, 대상 직접 할당, 참조 대상 변경 불가 (레퍼런스 변수가 참조중인 대상 변경불가)

* 포인터 접근 : nullptr 가능, 대상 주소 값 할당, 참조 대상 변경 가능 (포인터가 참조중인 대상 변경가능)

 

2. C++언어에서 추상 클래스와 인터페이스의 차이는?

: 추상 클래스란 하나 이상의 순수 가상 함수를 가지는 클래스.

순수 가상 함수란 반드시 구체적인 동작이 정의되어야 하는 함수로 "=0" 혹은 "PURE“ 을 선언과 함께 표기.

순수 가상 함수만으로 클래스가 이루어질 때, 이를 의미적으로는 인터페이스라고 함.

기본 C++에서는 "Interface"라는 명칭을 쓰지 않음. (언리얼 C++ 등 타 환경에서는 쓰는 경우가 있음)

 

3. 상속 관계 관련 1

1) 일반 상속으로는 기존 클래스의 필드와 함수를 재사용 할 수 있음.

2) Virtual 키워드가 붙은 케이스의 경우 함수를 재정의 가능. 이 경우 함수 호출 시 가상함수 테이블을 통해 호출된 함수가 부모의 것인지 자식의 것인지를 탐색하게 됨.

3) 상속은 A is a관계 (A는 B의 일종이다.)

4) 생성시 : 부모 클래스 생성자 -> 자식 클래스 생성자

5) 소멸시 : 자식 클래스 소멸자 -> 부모 클래스 소멸자.

* 이 때문에 소멸자에 virtual 키워드 빼먹으면 자식 클래스 소멸자만 호출하고 끝나서 메모리 누수 발생.

class AAAA
{
public :
AAAA() { cout << “+A” << endl;}
virtual ~AAAA() {cout << “-A” << endl;}
}

class BBBB : public AAAA
{
public :
BBBB() { cout << “+B” << endl;}
virtual ~BBBB() {cout << “-B” << endl;}
}

int main()
{
AAAA a = new A;
BBBB b = new B;

return();
}

---Answer
+A
+A
+B
-B
-A
-A

 

 

 

 

4. 상속 관련 2 : 가상함수 테이블

1) C++에서 가상함수 테이블에 대해 설명해보세요.

virtual 함수는 상속 시 Overriding 될 수 있는 함수.

Overriding시 부모 객체에서 함수 호출시 부모의 함수, 자식 객체에서 호출시 자식의 함수를 호출해야하는데,
이를 구별하기 위해 virtual 키워드가 붙은 함수들을 관리하려 만든 것이 가상함수 테이블.

 

2) 가상함수 테이블 포인터

상속관계가 구축되면 void * _vptr; 이라는 가상함수 테이블을 가리키는 포인터가 생김.

해당 포인터가 가리키는 값에 따라 호출되는 오브젝트가 부모인지, 자식인지를 찾고 결정함.

 

 

 

5. 상속 관련 3 : 오버라이딩과 "변수 잘림"

1) 오버라이딩 특수 케이스

class AA
{	public:
	virtual ~AA() = default;
		void fooA() { cout << "A's fooA" << endl; }
	virtual void fooB() { cout << "A's fooB" << endl; }
};
class BB : public AA
{	public:
	virtual ~BB() = default;
		void fooA() { cout << "B's fooA" << endl; }
	virtual void fooB() { cout << "B's fooB" << endl; }
};

int main() {
	AA* bar = new BB();
	bar->fooA();
	bar->fooB();
	return 0;
}

 

결과

코드 설명 :

virtual 키워드가 붙지 않은 함수는 가상함수테이블에 등록되지 않아 정의 타입과 관계없이 선언 타입 (부모로 선언되었다면 부모타입) 의 함수가 호출됨.

하지만 virtual 키워드가 붙은 함수는 정의 타입 의 함수가 호출됨.

, virtual 키워드가 붙지 않은 함수는 오버라이딩 해도 각 클래스에 종속적이며, virtual 키워드를 이용해 오버라이드 하면 실제 정의를 어떤 타입으로 했냐에 따라 다른 것이 호출됨.

 

6. 스마트포인터

스마트 포인터는 포인터처럼 동작하는 클래스, 자동으로 할당을 해제해주는 특성을 가짐.

auto_ptr은 복사 시 소유권 이전, shared_ptr은 포인터 자체적으로 레퍼런스 카운팅 실시.

따라서 auto_ptr은 사용도중 복사된 포인터가 삭제 시 원본 또한 할당 해제가 되는 문제가 발생할 수 있었기 때문에 C++11 표준에서는 다른 스마트 포인터들이 추가됨. C++17에서는 완전 삭제됨.

 

C++17 기준 유지되고 있는 스마트 포인터 리스트

1) unique_ptr : 소유권이 생긴 스마트 포인터. 일반적인 스마트 포인터.

2) shared_ptr : boost 라이브러리에 있던 그것. 레퍼런스 카운트를 하는 스마트 포인터.

3) weak_ptr : sheard_ptr의 순환 참조 제거를 위해 사용. 레퍼런스 카운트를 하지 않는 Shared_ptr

 

7. 기본 C++ 에서의 구조체 / 클래스 차이

기본 접근 한정자 차이 외의 나머지 동일

* struct 기본 접근 한정자 : public

* class 기본 접근 한정자 : private

 

* 타 언어 및 네이티브 C++이 아닌 타 C++에서는 다를 수 있습니다.

 

8. 메모리 초기화 함수 (malloc / calloc)

둘 다 메모리 할당 함수이나 그냥 할당만 하느냐 / 0으로 모든 비트를 미느냐 는 차이점이 있음.


malloc
는 그냥 할당만, calloc0으로 밀어버림.

 

팁 - calloc을 이용해서 0으로 초기화 한다 = 단순한 비트 클리어.

예를들어, float 배열을 calloc으로 할당한다고 해서 0.0이 되는 것이 아님.

또한, 메모리 할당 = 그 메모리에 무언가 쓴다는 목적인데, 정말 필요해서 0으로 밀어야하는것이 아니라면, 굳이 밀 필요는 없음.

 

 

9. C++ 환경에서의 32비트 / 64비트 차이

1) 포인터 크기 달라짐 (4바이트 / 8바이트)

2) 일부 자료형 크기 다름

(1) Windows 환경의 경우(LLP64) : long long 64비트, 나머지 동일

(2) 특정 환경 (ILP64) : int, long 64비트, 나머지 동일

(3) 리눅스 환경 (LP64) : long 64비트, 나머지 동일

3) 인라인 어셈블러 사용 가능 여부 (비주얼 스튜디오 기준) - 64비트에서는 인라인 어셈블러 사용불가.

따라서, 64비트에서 어셈블러 사용시 .asm 파일을 생성하여 따로 함수화 시켜서 사용함.

 

 

10. 포인터 관련

void foo (int* x, int *y)
{
    x = y;
    *x = 2;
}

int main()
{
    int a = 0;
    int b = 1;

    foo (&a,&b);

    cout << a << “:” << b << endl;
}

-- answer
a = 0
b = 2

 

설명 : foo 들어가면 int* x, int* y라는 지역 변수가 형성됨.

xy의 값을 집어넣음. (x = y )

x,y는 포인터이므로, x가 가리키는 값 = y가 가리키는 값이 됨.

yb를 가리키고 있고, x = y 이므로

*x = 2 => *y = 2 => b = 2 가 됨.

하지만 a는 값이 바뀐적이 없으니 그대로 0이 나옴.

 

 


C++ STL이나 기타는 다른 문서로 작성할게요 

필요해서 메모함

 

 

 

NBT 관련

  1. Lock : "전용 인벤토리를 가진 블록"에 대해, 특정 이름을 가진 아이템 소지시 작동
    레버를 들고 우클릭하면 안되나, 레버의 이름을 모루를 이용해 "레버"로 변경하면 작동하는 식
    (=원래 아이템 이름은 영향을 받지 않음)

 

 

단축키

  1. F1 : GUI 토글
  2. F3 : 현재 좌표 등 정보 확인
  3. F3 + A : 청크 새로고침
  4. F3 + B : 히트박스 토글
  5. F3 + C : 현재 위치 클립보드에 복사 (execute tp 명령어 상태로, location과 rotation이 저장됨)
  6. F3 + G : 청크 경계선
  7. F3 + I : 바라보고 있는 블록의 nbt 정보 복사
  8. F3 + L : 프로파일링 (기본 10초)
  9. F3 + P : 창 포커스 손실 시 일시정지 여부
  10. F3 + N : 관전모드 토글
  11. F3 + T : 리소스팩 리스타트

적으려고 하다보니 쓰레드 관련 내용이 하도 많아서 쓰레드부분만 분리하려고 새 글 팠음...

 

 

아래 내용은 'Windows via C/C++'을 기반으로 하고 있습니다.

 

* 틀린거 많으니 교차검증 꼭 하세요 *


[1] 스레드

  1. 스레드 이론
    1. 스레드란? : '작업 흐름', 코드를 수행하는 흐름 단위. 프로세스의 주소 공간내에 있는 코드를 수행하고 데이터를 이용함.
    2. 스레드에는 '스레드 커널 오브젝트' (운영체제가 관리하기 위한 커널 오브젝트) / '스레드 스택' (코드 수행시 필요한 데이터를 담아두는 스택)이 포함
    3. 하나의 프로세스 내에 둘 이상의 스레드가 존재 할 수 있음. 이때 스레드들은 단일 주소 공간을 공유하며 동일한 코드를 수행할 수도 있으며 동일 데이터를 조작할수도 있음.
    4. 커널 오브젝트 핸들 테이블은 스레드별이 아닌 프로세스별로 존재함으로 동일 프로세스 내의 스레드들은 커널 오브젝트 핸들도 공유하게 됨
    5. 프로세스는 스레드보다 더 많은 리소스를 사용하기 때문에 프로세스를 생성하는 대신 추가적인 스레드를 생성하여 사용하는편이 좋음. 하지만 때때로는 추가적인 프로세스를 생성하는것이 더 좋을 수 있음.
      (ex : 구글 크롬의 경우 멀티프로세스를 생성하여 작동시키는 방법을 사용함)
  2. 스레드 이론 2
    1. 스레드 생성이 필요한 경우 : 스레드 별 우선순위를 설정하여 일부 작업을 백그라운드로 밀어 작업을 수행하게 하거나 입력 / 출력등의 중요한 업무를 분리시켜 작업할 수 있음.
    2. 스레드 생성을 하지 않아야 하는 경우 : 다중 스레드로 인해 동일 파일에 입출력이 동시에 적용되는경우 (프린트 되는 문서에 직접 수정가하는 등), 사용자 인터페이스 같은 단일화가 필요한 작업은 멀티 스레드를 사용하지 않는것이 좋음
    3. 멀티스레드보다 멀티 프로세스를 사용하는 경우 : 애플리케이션 내부의 실행단위를 독립적으로 수행하는 경우. (메모리를 공유하지 않고 독립적으로 사용하는 경우) 구글 크롬의 경우 각 탭별로 다른 프로세스를 할당하여 타 탭이 꺼지더라도 앱 전체가 꺼지지 않음.

[2] 스레드 스케쥴링

  1. 스케쥴링 기본 :
    1. 스레드는 정지(Suspended), 대기(Wait), 실행 (Run) 상태를 가짐. 정지된 스레드는 스케쥴링될 수 없음.
    2. 'Suspended thread' 명령 수행시, 커널모드에서 수행중인 코드는 비동기적으로 수행을 완료하나, 유저모드에서 수행중인 코드는 즉시 정지됨. (메모리 잠금이 일어날 수 있음)
      따라서, 해당 명령은 정지시키고자 하는 스레드가 어떤 작업을 수행중인지, 정지시 생길 수 있는 문제점과 해결책을 명확하게 파악한 후 사용해야함.
    3. 특정 프로세스 내 스레드를 전부 순회하며 정지를 시키면 프로세스를 정지시킨것과 유사한 효과를 낼 수는 있음. 하지만 자주 쓰이지는 않음.
    4. 스레드는 sleep() 함수를 호출하여 일정시간동안 스케쥴링 대상이 되지 않도록 명령을 내릴 수 있음. sleep 호출시 스레드는 자신에게 남은 Time slice (할당시간)을 포기함.
      함수의 인자로 ms 단위의 시간을 제공할 수 있으며, 0을 입력할 수 있음.
  2. 스레드 우선순위 :
    1.  

[3] 스레드 동기화

  1. 유저모드 동기화 :
    1. 동기화란? : 다수의 스레드가 공유 리소스에 접근해야 하며, 리소스가 손상되지 않아야 하는 경우, 특정 스레드가 타 스레드에게 작업이 완료되었음을 알려야 하는 경우에 이루어지는 행동. 이 경우 각 스레드들은 상호 통신을 수행함. 
      동기화가 수행되지 않으면 데이터 오염 및 경쟁-교착상태(Race Condition)에 빠질 수 있음
    2. '원자적 접근' (Atomic) : Atomic은 일반적으로 'Instruction 단위' (명령어 수행 단위)를 의미함. 특정 리소스에 Instruction 단위로 수행중일 때, 타 스레드는 동일 시간에 동일 리소스로 접근해서는 안됨.
    3. 인터락 : 명령 수행중 인터럽트 되지 않고 값을 원자적으로 조작할 수 있음. 인터락 함수를 사용 시 값의 변화를 보장 할 수 있으나, 인터락 함수의 대상인 리소스는 타 함수 (인터락이 아닌 함수)가 호출해서는 안됨.
      인터락 함수는 유저모드/커널모드 전환을 일으키지 않으며 속도가 매우 빠름.
      인터락 함수로 전달되는 값은 반드시 메모리 상에 정렬되어 있어야 함. (아니면 호출 실패)
    4. 스핀 락 : 공유 리소스 사용시 리소스 사용여부를 지속적으로 검사하는 방식. (Busy Wait)
      스핀락은 CPU 시간을 많이 낭비할 수 있으며, 스핀락을 사용하는 모든 스레드가 동일한 우선순위에 놓여 있어야만 함.
      또한, 스핀락 변수와 락을 걸고자 하는 데이터는 서로 다른 캐시 라인에 있는것이 좋음. 동일 캐시 라인이라면 리소스 사용중인 CPU는 동일 리소스에 접근하고자 하는 타 CPU와 경쟁하게 될 것.
      단일 코어 프로세서에서는 스핀락을 사용하지 않는것이 좋음. (CPU 시간 낭비)
    5. 크리티컬 섹션 : 공유 리소스에 대해 배타적으로 접근해야 하는 코드 집합. 공유 리소스를 다루는 여러줄의 코드를 "원자적으로 수행"하기 위한 방법.
      여기서 원자적이란, 위에 적힌것과 같이 현재 스레드가 리소스에 접근중일때는 다른 스레드가 동일 리소스에 접근 할 수 없음을 의미.
      크리티컬 섹션 사용시에는 Enter -> Leave 순으로 함수를 호출해야하며, Enter시 타 스레드가 크리티컬 섹션의 데이터를 사용 시 진입 불가, 미 사용시에는 진입이 가능하여 사용 할 수 있다.
      크리티컬 섹션 사용시, 데이터를 사용완료하고 나면 반드시 Leave를 호출해주어야 한다. (그렇지 않으면 나머지 모든 스레드가 해당 자원을 사용하지 못하게 됨)
    6. 크리티컬 섹션 2 : 크리티컬 섹션에 진입한 스레드가 매우 빠르게 자원을 리턴하여, 이후 스케쥴링 타임까지 시간이 낭비되는 등의 경우를 방지하기 위해 크리티컬 섹션 사용시 스핀락 메커니즘을 투입하여, 리소스 획득을 반복적으로 검사하게 할 수 있음. 
      크리티컬 섹션에 스핀락 메커니즘을 사용시, 일정 시간동안 스핀락을 수행하며 공유 리소스의 자원획득을 시도하고 실패시 스레드를 대기시키기 위해 커널모드로의 전환을 시도 함.
    7. SRWLock (Slim Reader-Writer Lock) : 크리티컬 섹션과 유사하나 값을 읽는 Reader와 값을 쓰는 Writer가 분리되어 있음. 공유 리소스에 동시에 Read를 실시하는것은 공유 리소스 값을 손상시키지 않기 때문에 동시에 다수의 수행이 일어나더라도 상관없음. 단, Writer가 리소스를 수정하는 동안에는 어떠한 Reader/Writer도 추가적으로 접근 해서는 안됨
    8. 조건변수
  2. 커널모드 동기화 :
    1. 개요 : 정확히는 커널 오브젝트를 이용한 스레드 동기화. 스레드를 동기화 하기 위해 커널 오브젝트 등을 어떻게 사용할것인가 에 대한 설명을 적을 예정.
    2. 시그널 / 논 시그널 : 시그널 상태 - 사용 가능 상태, 논 시그널 - 대기 상태 (사용중인 상태)
    3. 대기함수 : 대기함수 호출시 인자로 전달된 커널 오브젝트가 시그널 상태가 될때까지 '대기함수를 호출한 스레드'를 대기상태로 유지시킴.
      대기함수 호출시 커널 오브젝트가 시그널 상태면 대기상태로 전환되지 않음.
    4. 대기 함수 사용시 'dwMilliseconds'와 같은 대기시간 인자에 'INFINITE'를 전달 할 수 있음. 이때에는 스레드가 영원히 블로킹 되지 않도록 주의가 필요.
    5. '성공적인 호출', '성공적인 대기의 부가적인 영향' : 성공적인 호출을 통해 오브젝트의 상태가 변경 시 '성공적인 대기의 부가적인 영향' 이라고 칭함. (책에선 그러더라구요)
      '성공적인 호출'은 매개변수로 전달된 커널 오브젝트가 정상적으로 시그널 상태가 된 것을 의미.
  3. 커널 오브젝트 리스트 :
    1. 이벤트 커널 오브젝트 (이벤트 기반 동기화) : 이벤트 = '작업이 완료되었음을 알림'. 수동 리셋 이벤트 / 자동 리셋 이벤트가 존재.
      수동 리셋 이벤트 : 시그널 상태 시 대기중인 모든 스레드는 동시에 스케줄 가능한 상태가 됨
      자동 리셋 이벤트 : 시그널 상태 시 대기중인 오브젝트 중 하나만이 스케쥴 가능한 상태가 됨
    2. 대기 타이머 커널 오브젝트 (Waitable Timer) : 특정 시간 / 일정 시간 간격을 두고 시그널 상태가 되는 커널 오브젝트.
      특정 시간 / 간격에 맞춰서 작업을 수행해야하는 경우 사용.
      대기 타이머는 항상 논 시그널 상태로 생성됨.
      유저 타이머 (SetTimer를 사용)와의 차이점 - 대기 타이머는 커널 오브젝트임으로 다수의 스레드에서 공유될 수 있고 좀 더 보안에 안정적임.
    3. 세마포어 커널 오브젝트 : 리소스의 개수를 고려해야 하는 상황에서 주로 사용됨.
      최대 리소스 카운트, 현재 리소스 카운트를 저장하고 사용함.
      대기함수가 세마포어를 전달받을 시 현재 리소스 카운트 값을 확인하고 0보다 크면 값을 1 감소시키고 대기함수를 호출한 스레드를 스케줄 가능상태로 만듬. 
      현재 리소스 카운트가 0이면 (=세마포어가 논 시그널이면) 호출한 스레드를 대기상태로 유지시킴.

      작동 규칙 : 
      1. 현재 리소스 카운트가 0보다 크면 시그널
      2. 현재 리소스 카운트가 0이면 논시그널
      3. 현재 리소스 카운트는 0 미만이 될 수 없음
      4. 현재 리소스 카운트는 최대 리소스 카운트보다 커질 수 없음
    4. 뮤텍스 커널 오브젝트 (Mutual Exclusion ; 상호배제) : 크리티컬 섹션과 유사.

      작동 규칙 :
      1. 스레드 ID가 0 (유효하지 않은 스레드 ID)일 시 : 시그널 상태 ; 어떠한 스레드도 뮤텍스를 소지하지 않음
      2. 스레드 ID가 0이 아닌 경우 : 논 시그널 상태 ; 특정 스레드에 의해 소지중 
      3. (그 외 특수한 경우 규칙 위반 가능)
        1. 논시그널 상태에서도 스레드가 뮤텍스를 다시 소지가능
        2. 뮤텍스를 소지한 스레드가 소유권을 해지하지 않고 종료되는 경우, '버림' 처리 되어 소유권을 강제로 해제시킴

그냥 혼자 책읽고 하기에는 내용을 자꾸 빼먹는거 같아서 필기도 할겸...

 

아래 내용은 'Windows via C/C++'을 기반으로 하고 있습니다.

 

* 틀린거 많으니 교차검증 꼭 하세요 *


  1. 커널 오브젝트 이론
    1. 커널 오브젝트는 OS가 제공/사용하는 커널레벨의 기능을 추상화 시킨 오브젝트 타입이다.
      커널에 의해 할당된 메모리 블럭들로, 이 메모리 블럭들은 커널 오브젝트에 대한 세부 정보드를 저장하고 있으며 커널에 의해서만 접근이 가능한 구조체로 구성되어 있다.
    2. 커널 오브젝트의 데이터 구조체는 커널에서 접근이 가능하기 때문에 애플리케이션 레벨 (유저레벨)에서 해당 정보를 직접 변경하는것은 불가능. 
    3. 커널 오브젝트 사용은 각각의 호출 함수를 통해 핸들 (HANDLE 타입 변수들)을 얻어 조작이 가능하다.
    4. 커널 오브젝트는 프로세스에서 소유하는것이 아닌 커널에서 소유함. 즉, 특정 프로세스에서 커널 오브젝트 생성을 지시하였어도 해당 프로세스가 소멸시 커널 오브젝트도 같이 소멸한다는 보장은 할 수 없음.

 

 

 

 

git에 특정 조건을 걸어서 메시지등을 출력하는 hook이라는 기능이 있다.

(더 상세한 내용은 이 블로그 말고 다른곳을 검색해보기 바란다)

 

훅을 처음 써보면서, shell script도 제대로 쓸줄몰라 엄청 해멨으나 일단 원하는걸 하나 구현해놨기에 기록해둔다.

 

원하는건 코드 컨벤션을 맞추기 위해 설정한 문구 중

모든 코드 스타일을

void foo()
{
codes;
}

  방식으로 픽스하기 위한 조건이었다.

 

그걸 위해, 검색해서 pre-commit 을 수정해보려고 하니... 에러가 뜬다.

 

error: cannot spawn .git/hooks/pre-commit: No such file or directory

 

대충 이런 문구다.

 

요약하면 pre-commit이라는 hook이 없다는 얘긴데...

 

정확한 이유는 파악하지 못했으나, git 용 hook에 필수적으로 포함되어야 하는 문구들이 있는 모양이었다.

 

해서, 검색을 통해 어떤 문구가 있어야하는지 파악을 해두고, 문구 적용을 시켜보도록 했다.

 

 

 

#단순히 ") {" 스타일이 들어오면 밴 먹임

disallowed="){"


git diff --cached --name-status | while read x file; do
	if ["$x" == 'D']; then continue; fi #뭔 의미지? 'D'가 뭐지?
	
	if grep ')\s{' $file ; then
    echo "ERROR: 코드 스타일 에러입니다."
    exit 1
	fi	
	
	for word in "$disallowed"
	do
	    if egrep $word $file ; then
        echo "ERROR: Disallowed expression \"${word}\" in file: ${file}"
        exit 1
    fi
    done
		
done || exit $?

 

완성된건 이런 스타일인데, 정리하면 다음과 같다.

 

1. disalowed에 "없어야 될 문자열"을 기록한다.

2. git에서 수정된 사항을 읽는다.

3 (은 이해를 못했습니다...)

4. 파일을 전체 검색하며, 조건에 맞는 문자열이 있는지 검사후, 조건에 만족하면 echo  메시지를 띄운 후 에러를 리턴한다.

5. 정상 통과 하면 while문을 종료시키고 끝낸다.

 

의 구조이다.

 

웃긴점은, 이를 좀 "있어보이게" / "다른 사용자들이 큰 수정할 필요없이" 적용을 시키기 위해 core.hooksPath를 수정해줬더니 저 hook 조차 에러를 검사하여 리턴해버리는게 아닌가...

이거 뭔... 제 발등 찍기도 아니고

해서, 이런 경우에는 hookspath를 임의로 수정해준 뒤, 커밋 갱신을 해줘야 하는 소소한 불편함(?)이 발생하고 있다.

 


 

해당 문서를 위해

woowabros.github.io/tools/2017/07/12/git_hook.html

 

훅으로 Git에 훅 들어가기 - 우아한형제들 기술 블로그

들어가며…

woowabros.github.io

singun.github.io/2019/03/16/git-hook-pre-commit/

 

특정 문자열이 커밋되는 것을 막아보자 - Programming Singun

페이스북이나 인스타그림 등의 open api를 사용하기 위해서는 인증을 위한 토큰 값이나 키 값을 사용해야 할 때가 있습니다. 민감할 수 있는 정보라 git 에 커밋하는게 그리 달갑지 않아, 이런 경우

singun.github.io

두 문서를 참고하였습니다. 감사합니다.

기타 항목

(웹 접근성 등)


1. 저작권 관리 요소

 

2. 웹 접근성 4대 지침

  1) 인식의 용이성 : 모든 컨텐츠는 사용자가 인식할 수 있어야 한다.

  2) 운용의 용이성 : 사용자 인터페이스 요소는 조작 가능하고 네비게이션 가능해야 함.

  3) 이해의 용이성 : 컨텐츠는 이해 할 수 있어야 한다

  4) 견고성 : 웹 컨텐츠는 미래 기술로도 접근 가능하도록 견고하게 만들어야 한다.

 

3. 클라우드 서비스 3가지 타입

  1) IaaS : 인프라 관련 클라우드 서비스 - AWS (EC2 등)

  2) PaaS : 플랫폼 관련 클라우드 서비스 - Windows Azure, Google Cloud Platform / Naver Cloud Platform 등

  3) SaaS : 소프트웨어 관련 클라우드 서비스 - 클라우드 저장소 등

소프트웨어 설계 / 구현 / 소프트웨어 테스트

 


1. UI 설계 원칙

  1) 직관성 : 누구나 큰 노력없이 쉽게 이해하고 사용 가능해야 함

  2) 유효성 : 사용자의 목적이 정확하고 완벽하게 달성될 수 있어야 함

  3) 학습성 : 초보와 숙력자 모두 쉽게 배우고 익힐 수 있어야 함

  4) 유연성 : 사용자의 요구사항을 최대한 수용하고 오류를 최소화할 수 있어야 함

 

2. UI 품질 요구사항 (ISO/ISE 9126)

  1) 기능성 : 실제 수행 결과와 품질요구사항간의 차이를 분석, 정확하지 않은 결과 발생율 등과 관련한 동작 관찰.

  2) 신뢰성 : 시스템이 일정 시간 또는 작동되는 시간 동안 의도하는 기능을 수행하는것을 보장

  3) 사용성 : 사용자와 컴퓨터 사이에 발생하는 행위를 정확하고 쉽게 인지 가능

  4) 효율성 : 할당된 시간에 한정된 자원으로 얼마나 빨리 처리하는가

  5) 유지보수성 : 요구사항을 개선하고 확장하는 데 있어 얼마나 용이한가

  6) 이식성 : 다른 플랫폼에서도 많은 추가 작업 없이 얼마나 쉽게 적용이 가능한가.

 

3. UI 요구사항

  1) 기능적 요구사항

    (1) 시스템의 입력 / 출력으로 무엇이 포함되어야 하는가

    (2) 시스템이 어떤 데이터를 저장해야하는가

    (3) 시스템이 어떤 연산을 수행해야 하는가

 

  2) 비기능적 요구사항

    (1) 기능적인 부분 이외의 요구사항

    (2) 사용성, 효율성, 신뢰성, 유지보수성, 재사용 성 등 품질에 관한 요구사항

    (3) 플랫폼, 사용 기술 등 시스템 환경에 관한 요구사항

    (4) 비용 일정 등 프로젝트 계획에 관한 요구사항

 

4. 네트워크 : OSI 7계층

  7) 응용 계층 : HTTP, FTP 등

  6) 표현 계층 :

  5) 세션 계층 :

  4) 전송 계층 : TCP, UDP

  3) 네트워크 계층 : IP

  2) 데이터링크 계층

  1) 물리 계층 : RS232C

 

5. TCP/IP 4계층

  4) 응용 계층 (5~7) : TCP/UDP 기반의 응용 프로그램 구축.

  3) 전송 계층 (= 4) : 연결 제어, 데이터 전송, TCP, UDP

  2) 인터넷 계층 (= 3)  : 통신 노드간의 패킷 전송, 라우팅, IP, ARP, RARP 등

  1) 네트워크 엑세스 (1~2) : 물리적 주소, MAC 등, LAN 패킷망 등에 사용

 

6. 라우팅 프로토콜

  1) RIP : 최초의 라우팅 프로토콜, 거리 벡터 알고리즘 활용, 30초 주기 갱신, 라우팅 루프 발생 가능.

  2) IGRP : RIP 개선, 네트워크 상태 고려

  3) OSPF : 링크 상태 알고리즘 사용, 변경 정보에 대해 RIP보다 빠른 업데이트. 토폴로지 정보가 전체 라우터에 동일하게 유지

  4) BGP : 규모가 큰 네트워크 (ISP 등)의 상호연결

 

7. 소프트웨어 테스트 프로세스

  1) 테스트 계획 : 목적 범위 정의, 대상시스템 구조 파악, 테스트 일정 / 종료 조건 정의 등

  2) 테스트 분석 및 디자인 : 테스트 목적 / 원칙 검토, 요구사항 분석, 데이터 및 환경 준비

  3) 테스트 케이스 및 시나리오 작성 : 테스트케이스 작성 및 검토, 테스트 시나리오 작성

  4) 테스트 수행

  5) 테스트 결과 평가 및 리포팅 : 테스트 결과 정리, 결과 평가, 리포팅

 

8. 소프트웨어 테스트 산출물

  1) 테스트 계획서 : 테스트 활동의 범위, 접근법, 자원, 일정, 업무 배정, 환경, 리스크 식별

  2) 테스트 케이스 : 테스트를 위한 설계 산출물, 테스트 케이스의 집합을 명세화.

  3) 테스트 시나리오 : 테스트 실행을 위한 동작 순서 기술

  4) 테스트 결과서 : 테스트 결과를 정리한 문서. 결과 평가 및 리포팅

 

9. 소프트웨어 테스트 원리 및 특성

  1) 테스팅은 결함이 존재함을 밝히는 활동 : 결함이 발견되지 않더라도 결함이 없다는것을 증명할 수 없음.

    따라서 소프트웨어 테스팅은 시스템에 결함이 존재함을 증명하는 것.  

  2) 완벽한 테스팅은 불가능하다. : 모든 것을 테스팅하는것은 불가능. (무한 경로/입력값/타이밍 이슈 존재)

    리스크 분석과 우선순위를 토대로 한 테스트에 노력을 집중하는것이 좋음.

  3) 조기 테스팅 (Early Testing)으로 시간 절약 가능 :

    수명 초기부터 테스팅을 함으로써 나중에 큰 비용이 동반되는 수정을 줄이거나 없엘 수 있음.

  4) 결함 집중 : 대부분의 결함은 소수의 모듈에 집중되어 발생된다. 80 / 20 법칙

  5) 살충제 페러독스 : 같은 테스트 연속 반복시에는 추가적인 결함 발견이 불가능하다.

  6) 테스트는 정황 (Context)에 의존한다. : 테스트는 환경 및 구조에 따라 다르게 진행해야한다.

  + 오류-부재의 궤변 : 개발된 시스템이 요구사항을 만족시키지 못하거나 사용성이 낮다면 오류를 발견하고 제거해도 품질이 높다고 할 수 없음.

 

10. 테스트 커버리지

  1) 기능 기반 커버리지 : 테스트 대상의 전체 기능을 모수로 설정, 실제 테스트가 수행된 기능의 수를 측정

    일반적으로 100% 달성을 목표로 하며, UI가 많은 경우 화면 수를 모수로 사용.

  2) 라인 커버리지 : 전체 소스코드의 Line 수를 모수로, 테스트 시나리오가 수행한 Line 수를 측정.

    단위 테스트에서는 라인 커버리지를 척도로 삼는다.

  3) 코드 커버리지 : 프로그램의 소스코드가 얼마나 테스트되었는가를 나타냄.

    소스코드의 구문, 조건, 결정 등의 구조 코드 자체의 테스트 정도를 측정.

    (1) 구문 커버리지 : 코드 구조내의 모든 구문에 대해 한번 이상 수행.

      100% 구문 커버리지 = 모든 구문에 대해 1회 이상 테스트 수행

    (2) 조건 커버리지 : 결정 포인트 내의 모든 개별 조건식에 대해 수행

    (3) 결정 커버리지 : 결정 포인트 내의 모든 분기문에 대해 수행

    (4) 변형 조건/결정 커버리지 : 각 개별 조건식이 다른 개별 조건식에 영향을 받지 않고, 독립적으로 영향을 줌.

 

11. 테스트 유형

  1) 프로그램 실행 여부 :

    (1) 정적 테스트 : 소프트웨어의 실행없이 명세 또는 구현, 개발 단계에서 컴포넌트나 시스템 테스트

    (2) 동적 테스트 : 컴포넌트나 소프트웨어를 실행하면서 수행하는 테스트. 블랙박스/화이트박스 등

 

  2) 테스트 기법

    (1) 블랙박스 테스트 : 적절한 테스트 베이스 (공식 요구사항 문서, 명세서, 유스케이스 등)에 대한 분석을 통해

    '구현된 기능'을 위주로 테스트

    (2) 화이트박스 테스트 : 프로그램의 내부구조나 구현을 기반으로 테스트 도출.

      코드, 아키텍쳐, 워크/데이터플로우 분석 

 

  3) 테스트 시각

    (1) 검증 : 명세된 요구사항이 충족되었는지를 조사 혹은 객관적인 증거로 확인. 제품의 생산과정 테스트

    (2) 확인 : 요구사항이 컴포넌트나 시스템을 의도적으로 사용/활용 충족여부. 정상동작 테스트

 

  4) 테스트 목적

    (1) 회복 테스트 : 시스템에 고의로 실패/오류를 유도한 뒤 정상적으로 복귀하는지 여부

    (2) 안전 테스트 : 소프트웨어의 보안 결함 테스트

    (3) 강도 테스트 : 시스템에 과다 정보량을 부과하여 과부화 시킨 뒤 정상 작동 검증

    (4) 성능 테스트 : 응답시간, 업무량, 반응속도 등 테스트

    (5) 구조 테스트 : 시스템 내부의 논리 경로, 소스코드의 복잡도 테스트

    (6) 회귀 테스트 : 이전에 정상 작동중이던 소프트웨어의 변경/수정에 대해 새로운 결함 발견 여부 테스트

    (7) 병행 테스트 : 기존 시스템과 변경된 시스템에 동일한 테스트 케이스를 이용하여 비교

 

  5) 테스트 종류

    (1) 명세 기반 테스트 : 명세를 바탕으로 테스트 케이스를 도출. TC를 통해 결함없음 보장. 블랙박스 테스트

    (2) 구조 기반 테스트 : 소프트웨어나 시스템의 구조, 논리 흐름에 따라 테스트 케이스 작성. 화이트박스 테스트

    (3) 경험 기반 테스트 : 유사 애플리케이션이나 기술에서의 경험,직관,테스터의 기술 능력으로부터 TC 추출

 

 

 

12. 애플리케이션 성능 지표

  1) 처리량 : 주어진 시간에 처리 가능한 트랜젝션의 수 / 시간당 페이지 수

  2) 응답 시간 : 입력이 끝난 후 애플리케이션의 응답 출력까지의 시간

  3) 경과 시간 : 요구를 입력한 시점부터 결과 출력시까지의 시간

  4) 자원 사용률 : CPU/메모리 사용량 등

 

13. 사용자 중심 모듈 패키징 순서

  1) 기능 식별

  2) 모듈화

  3) 빌드 진행

  4) 사용자 환경 분석

  5) 패키징 적용 시험

  6) 패키징 변경 개선

 

14. 제품 릴리즈 노트 작성 수행 순서

  1) 모듈 식별

  2) 릴리즈 정보 확인

  3) 릴리즈 노트 개요 작성

  4) 영향도 체크

  5) 정식 릴리즈 노트 작성

  6) 추가 개선 항목 식별

 

15. 패키징 도구를 활용한 설치 및 배포 수행 순서

  1) 빌드 내용 식별

  2) 패키징 도구 식별

  3) 패키징 수행

  4) 패키징 도구 설치

  5) 배포 작업

  6) 정상 배포 확인

 

+ Recent posts