[TOC]

이 글은 C와 C++을 한권이라도 읽은 사람 대상으로 적었습니다. 개인 정리용에 가까우므로, 이걸로 공부하지마세요. !

메모리 구조

CODE 함수, 제어문, 상수 영역(컴파일시 결정)
DATA 전역 변수(컴파일시 결정)
BSS 초기화값이 없는 전역변수(컴파일시 결정)
HEAP 동적 할당(런타임)
Stack 지역 변수(런타임)
  • 스택에서 자료(로컬변수)를 만지는게 제일 빠르다.
  • 가능한 전역변수는 사용하지않는게 속도상에서 좋다.
  • 동적할당은 어느정도되면 안만들어진다. 32비트 컴퓨터일경우 (1.98G)

전처리기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__DATA__ // 컴파일 날짜를 나타내는 문자열 
__TIME__ // 현재 시간 문자열
__FILE__ // 파일 이름 포함 문자열
__LINE__ // 지금 현재 라인수

// 조건부 컴파일
#ifdef X
// - #define이 되어있다면 처리한다.
#else
// 안되어있다면
#endif #if문 종료

#ifndef X
// #define X이 안되어있다면
#else //#elif = else if
// 되어 있다면
#endif

assert(/* bool statement */) // 수식의 값이 기대하고 있는 값인가 확인할때 사용 -> 실패하면 메세지 출력후 중단해버림.

함수 호출 스택

1
2
3
4
5
Debug 중 일때, BreakPoint를 통해 인터럽트를 걸어주면(멈추게 하면), 함수 스택이 있다.
영어로는 Call Stack이라고 하고, 사용하는 이유는, 현재 실행중인 서브루틴(함수)를 실행하고 어디로 돌아가야할지 절차들을 따르기 위함이다. ( Recursive : 재귀 연산할때 잘못하면 Stack Overflow가 뜨는 이유)

<Tip 1>
static 변수는 가능한 쓰지 않은게 좋은데, 이 변수는 다른 소스파일에서 extern으로 가져오려고 해도 메모리가 따로 할당받아진다. (cpp에서 선언한 변수 다른 cpp에서 사용하고싶을때의 상황)

바이트 순서

  • 빅 엔디안은 큰 단위의 바이트가 앞에 오는 방식, 리틀엔디안은 작은 단위의 바이트가 뒤에 오는 방식이다.

  • 네트워크 장비들은 빅 엔디안을 쓰고, 우리와 같은 PC는 리틀 엔디안 식으로 저장한다.

Pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* 접근 자료 크기 */
int *p1 = 0x1000;
char *p2 = 0x1000;
short *p3 = 0x1000;

p1++; // p1 = 0x1004 // 접근하는 크기만큼 더해진다. sizeof(자료형 *)
p2++; // p2 = 0x1001
p3++; // p3 = 0x1002

// 32bit에서는 Pointer의 크기는 4 Bytes, 64bit에서는 8 Bytes이며, 접근하는 크기는 각 자료형에 따라 다르다.

/* 리틀 엔디안의 포인터 */
char Buffer[10]; // 0x11 0x22 0x33 0x44 ... 가 들어있을때..
int *p = (int *)buffer;
int a = *p;//를 할경우 0x44332211으로 저장된다.(리틀 엔디안)

int a = 0xffffff00;
char *p = &a;
char x = *p; // 딱 1만 가져온다. 1바이트 크기만큼 x는 0x00만 가지고 있다.

short *p = &a;
short x = *p; // 이 경우 x 는 0xff00을 가지고있다.

/* 배열 인덱싱 */
*(p + 1) = p[1]; // 포인터 p에서 포인터의 크기만큼 1 * (32일때 4, 64일때 8) 씩 이동한다.
a[1][2] = *(*(a + 1) + 2) // 2차원 배열 같은 경우 한칸 아래 옆 2칸이므로,
// a + (1 * 최대 열수 * 32일때 4, 64일때 8) + (2 * (32일때 4, 64일때 8)) 씩 움직인다.

/* void형 포인터 */
// 아무거나 가르킬 용도로 쓴다. 캐스팅 전용 포인터. 대부분 책에서 추천하지않은 이유가 이 포인터의 자료형을 모를때 무슨일이 일어날지 모른다. 오버해서 캐스팅하면 Corruption이 일어난다. (다른 자료에 까지 Write 할수도 있음.)

Const Pointer

1
2
3
4
5
// C/C++ 사용하면서 조낸 헤깔린다
const int num = 10; // 일때 const가 맨 앞에 있으면 화살표를 뒤로 그려서 num을 가르켜서 num을 상수화한다.
const int * ptr1 = &val1; // 일때 const가 맨앞이니 역시나 화살표 그리면 ptr1을 가르킨다. ptr1은 int *형이기 때문에 ptr1을 통해 val1은 수정하지못한다. ptr1은 수정가능!
int * const ptr1 = &val1; // 이경우 const는 중간에 있으니 뒤로 화살표 그리면 ptr1을 가르키고, 그 뜻은 ptr1은 수정 불가능이 된다. 단 val1은 수정가능!
const int * const ptr1 = &val1; // 앞에 있는 const는 val1을 상수화, 중간에 있는 const는 ptr1을 상수화한다. 둘다 수정 불가능!

Function Pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 콜백함수처럼 사용하는 방법 중 하나인 함수포인터이다.
// 함수도 메모리를 가진다.

int function(int a, int b, int c);
void Test(void (*ptr) (void))
{
ptr(); // Draw의 시작주소를 통해 호출!
}

void Draw(void)
{
}

int main()
{
int (*ptr1) (int a,int b, int c); // 함수 포인터 변수 선언
ptr1 = function; // 함수 포인터에 함수 시작주소 넣음.

ptr1(1,2,3); // function(1,2,3) 이 호출된다.
Test(Draw); // Draw 전역 함수를 매개변수로 넘긴다.
}

구조체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// C++ 규약이 발전하면서 구조체랑 클래스랑 다른건 접근지정자 말곤 없는것 같다.
// 클래스는 기본 private, 구조체는 public 이다.
// 구조체 바이트 정렬이 중요하다.

struct Test
{
char a;
int b;
};
// 이렇게 있을때, Test는 1 + 4 = 5 바이트를 가지고 있지않다.
// 컴파일러가 구조체 정렬할때 가장 큰 자료형 기준으로 정렬한다.
// int가 제일 크므로 int + int = 8바이트

#pragma pack(push, 1) // 정렬 크기를 1바이트
struct Test
{
char a;
int b;
};
#pragma pack(pop)
// 이 경우 5바이트가 나온다. 정렬 기법은 대부분 사용하지않지만, _aligned_malloc 같은 물리적으로 연속할당이 필수적인 상황이 있는데, 그 경우에 사용해야된다.<거의 잘안씀.>

인라인

1
2
3
4
C에서의 매크로함수의 단점을 달피하고자 나왔다.
함수 선언 앞에 inline을 쓰면된다. 그리고 헤더에 넣어야 한다.
컴파일러가 최적화 해주는데 visual studio에서 컴파일러 최적화 옵션을 켜야만 동작한다. 껏다면 일반 함수.
프로그래머가 인라인화 하더라도 컴파일러가 거부하면 일반함수로 된다.

정보은닉, 캡슐화

1
2
3
4
5
6
7
8
대부분 책에서는 정보은닉 및 캡슐화 예제를 적어놓고

GetX, SetX 같은 함수를 만드는데 이럴경우 의미가 없어진다.
Move함수를 만들어서 내부 변수만 움직이게 해주는게 좋다.

<Tip 2>
class 안에 맴버함수로 const를 넣게 되면, class 의 내부 변수를 만질수 없다.
const 함수 안에서는 일반 함수또한 호출할수 없다. (const 끼리 호출가능하다.)

생성자, 소멸자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 생성자
//Object Instance가 Alloc 될때 호출된다.
class BB
{
BB(int X)
{

}
};

BB Data(0);
// 위 코드를 어셈블로 보자면,
// push 0
// lea ecx, [Data]
// 클래스 객체 대해서의 데이터 확보는 함수 호출시에 확보 되지만, 생성자는 바로 호출되지 않는다.
// 호출되는 순간은, 해당라인이 지나갈때 호출된다.
// 가능하면 if ~ else 로 두개의 객체를 선언하면 좋지않음.

// 파괴자
// 함수를 빠져나오거나, delete가 선언되었을때 호출된다.
// 맴버함수로써 사용가능(생성자는 불가능) -> 임의 호출은 좋지않다.

Placement New

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* malloc으로 생성한 객체를 생성자따로 호출할 방법이 바로 Placement New이다. */
BB *px = (BB *)malloc(sizeof(BB));
BB *p3 = new (px) BB; // Placement New - 참고로 px와 p3은 같다. new해서 받은 리턴값을 굳이 안받아도 된다.

// 단 이럴경우 delete로 호출할수 없으며
free(px); // 이럴경우도 문제가 생긴다.

px->~BB(); // 소멸자를 호출해서 파괴시켜야된다.

// 재미난 캐스팅 -> 잘 안쓰지만 C++가 시발 존나 어려운 이유 중 하나
int A[10000];
BB *px = new (A) BB; // int 배열 A를 메모리 공간으로 쓴다.
px->~BB();

int Array[100];
CPlayer *p = (CPlayer *)Array; // Placement New가 없고, 이 경우 이상한값이 나올수있음.
p->Print();

//동적할당을 받거나 캐스팅을 한 객체에 대해서 생성자호출을
//하고싶을때 Placement New를 해줘야 한다.
//물론 이건 상속 및 가상함수가 안에 포함되어 있다면 바로 뻑난다.

맴버 이니셜라이저

const 맴버 변수를 사용한다면 필수 사용! has-a 상속관계에서도 사용!

class 맴버 변수에 대해 자체적으로 사용해야된다면 포인터 변수를 따로 빼서 사용하면 훨씬 빠르다.

this 포인터는 너무 느림.!

복사 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 일반 대입연산은 얇게 복사해버린다.
class C
{};
C A;
C B = A; // 가있을때
// 완전 똑같이 복사해버려서 얇게 복사!
// A가 소멸자 호출을 해버리면 B는 쓰레기값이 되어버린다. -> 또는 에러 핸들러로 꺼져버린다.
// 두번째로는 둘이 가르키는게 다 같아버려서 A가 변경되면 같이 변경된다. (포인터값이 있을때)

// 이때 복사생성자를 통해 깊게 복사시켜준다.
C(C& CopyC)
{
~
}

Friend, Const, Static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* 이것도 오지게 짜증난다. */
// const 객체는 무조건 const 맴버 함수끼리만 가능 - 복습
// non-const, const 함수는 오버로딩이 가능함.

// Friend
class Boy
{
private:
friend class Girl; // Girl에서 Boy 클래스를 맘대로 건들일수 있음.
}

class Girl
{
Boy Boy; // 이 객체를 통해 맘대로 건든다.
}

class Point
{
public:
friend Point PointAdd(); // 외부에서 사용가능하게 하는 메소드이다.
};

Point PointAdd()
{
// 여기로 구현가능!
}

// Static
// 전역변수에 선언된 Static의 의미
// = 선언된 파일 내에서는 참조를 허용!
// 함수 내에 선언된 Static
// = 한번만 초기화 되고, 지역변수와 달리 변수가 살아 있다.
// 맴버 함수나 맴버 변수에 static
// = 맴버변수 -> 전역변수
// = 맴버함수 -> static 변수만 가능
class CTest
{
public:
static int X; // Static Member Variable

static void Print() // 주로 스레드 함수로 사용가능하며 this 포인터 사용불가능하다.
{
printf(x); // 오직 애만 가능!
}
}

int CTest::X; // 한번 선언을 해줘야 한다.

상속, 다형성

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person;
class Student : public Person;
class PartimeStudent : public Student

// 부모 Person 객체 포인터는 자식 클래스들을 받을수 있다.
// 이미 자기자신을 가르키는 파트가 있기때문이다. 이를 경우 Up Casting이라고 한다.
Student P1 = Person; // 단 만약에 Student만 존재하는 함수나 변수를 실행하면 에러 발생한다.
// 업캐스팅의 경우 여러 객체들을 동시처리할때 사용된다.

// 그외에 클라이언트 개발할때 다운캐스팅은 잘쓰면 엄청 좋다.
Person P1 = (Person *)Student;
// CBaseObject라는 객체가 아이템, 플레이어 , 액션을 모두 담을수 있게 변수를 만든뒤 순수가상함수와 가상함수를 통해 연산을 할수 있도록 만들어둔다.
// 이와 같이 특정객체로 한정 지을떄 사용된다.

가상함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 오버라이딩 된 함수가 virtual 이라면, 오버라이딩 함수 또한 자동으로 virtual이어야만 한다.
// 부모클래스에서 자식클래스를 호출하고 싶을때, 가상함수가 좋은 방법이다.
class A
{
virtual AA();
virtual BB();
}

class B : public A
{
virtual AA();
// virtual BB(); 주석처리되어 있다면 A::BB()가 호출된다.
}

A *pa = new A;
A *pa2 = new B;

// 만약 클래스안에 가상함수가 있다면 클래스 첫부분에 __vfptr이 생긴다.
// 모든 가상함수들이 테이블로써 리스트에 들어가게 된다. 하지만 이 메모리가 변조된다면, 끝장난다!!
// 그다가 가상함수 테이블은 공용이다 (같은 클래스 객체는 같이 사용) 이러니 문제가 미친듯이 터지게된다.

// 만약 문제가 생기면 일단 Pointer를 확인하고 가상함수 테이블을 확인! -> 이런 문제는 찾기 너무 힘들다.
// 가상함수 테이블에서 주소와 함수이름이 안나온다면, 가상함수 테이블에 문제가 생긴거

순수 가상함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 인터페이스 용도로써 사용하며, (외형 전달), 상속받은 클래스가 무조건 받아서 만들어줘야 한다.
// Down Casting 할때 너무 좋다.
// 순수 가상함수로 하는 이유는, 상속받은 클래스에 대해 이것을 필수조건으로 만들어준다.
// 라이브러리를 만들때, 자식 클래스에 연결하고 싶을때 사용한다.
class A
{
virtual AA()=0;
virtual BB()=0;
}
class B : public A
{
virtual AA();
// virtual BB(); 주석처리되어 있다면 컴파일 에러!
}

가상 소멸자

1
2
3
4
5
6
7
8
9
10
// Up Cast 상황이든, Down Cast 상황이든, 상속관계에서 
class A
{};
class B : public A
{};

A *ptr = new B; // 을 선언했을때,
// 생성자는 B클래스를 했다. 만약 B클래스가 더 많은 맴버변수 및 함수를 가지고 있다면, 그 크기만큼 누수가 생긴다.
// 이를 해결하기 위해 가상소멸자를 사용한다.
// 그러면 생성된 B 클래스의 소멸자가 호출된다. -> 이것또한 가상함수 테이블에 참조된다.

연산자 오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Point operator+(const Point &p2)
//-- 단항연산자(전위)
Point& operator++(Point& ref)
{
}

Point& operator--(Point& ref)
{}
//--단항연산자(후위)
const Point operator++(int)
{
const Point retojb(x, y); // 사본
x++;
y++;
return retojb;
}

// cout과 cin, endl도 오버로딩되어 있다.

// new 오버로딩
/*
1. 메모리공간의 할당
2. 생성자의 호출
3. 할당하고자 하는 자료형에 맞게 주소값반환
*/

void * operator new (size_t size)
{
void *ad = new char[size]; // 연산자 오버로딩에서 다시 new 해도 된다.
return adr;
}
//--> delete 문제 // 하지만 delete 함수에서 delete 함수를 선언하면 안된다.

void operator delete (void * adr) // 이 함수가 호출이 안된다.
{
}

템플릿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// C++ 11 ,14 개념까지 들어가면 대박난다.
// 쉽게하자면 템플릿은 모든타입에 맞춰서 클래스나 함수를 만든다.
// 단 템플릿은 Intelisense가 정상적으로 동작되지않고, 정적바인딩에 속한다. (컴파일에 결정된다. -> 가상함수는 동적바인딩이라고 할수있다. -> 런타임에 결졍된다.)

template <class T> // 원래 클래스가 쓰던 데이터타입을 T로 바꿔주면된다.
/*
* function or class implement
*
*/

template <typename T>
class SimpleTemplate
{
T Simplefunc(const T& ref);
}

template <typename T>
T SimpleTemplate<T>::Simplefunc(const T& ref);

Exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
try
{
if(num == 0)
throw num2; // 수동으로 예외를 던진 케이스

cout << "~~~~";
}
catch(int expn)
{
// .....
}

// 예외처리
// 예외발생시 catch를 통해 예외처리한다.
// 만약 예외발생시켯지만 아무도 catch하지않는다면 프로그래머가 선언안해도 있는 상위 catch를 통해 호출되고, 뻗는다.

//Stack Unwinding(스택 풀기)

void Divide(int num1, int num2)
{
if(num2 == 0)
throw num2;
// 예외가 처리되지 않으면, 예외가 발생한 함수를 호출한 영역으로 예외 데이터가 전달된다
}
try
{
Divide(num1, num2);
}
catch(int expn)
{

}

int SimpleFunc(void)
{
try{
if()
throw -1;
} catch(char expn) // 여기서 못받아서 위로 계속 올라간다.
{
//...
}

//자료형이 일치하지 않으면 그 자료형에 맞는 catch가 나올때까지 위로 올라간다.
//하나의 try 영역 내에서 종류가 다른 둘 이상의 예외가 발생할 수 있기 때문에,
// 하나의 try 블록에 다수의 catch 블록을 추가할 수 있다.

// 전달되는 예외의 명시
try{
ThrowFunc(20)
}
catch(int expn){}
catch(char expn){}

int ThrowFunc(int num) throw (int, char) // 함수 내에서 예외 데이터가 전달될 있음을 명시

스택 풀기

1
2
//Stack Unwinding(스택 풀기)
//자료형이 일치하지 않으면 그 자료형에 맞는 catch가 나올때까지 위로 올라간다. -> 이를 스택풀기라고 한다.

C++ Casting

1
2
3
4
5
6
7
8
9
10
// 이제부터 습관들려야 할 캐스팅

static_cast
강제 형변환<상속관계에서는 모두 허용해준다.> 이외 말도 안되는거면 그냥 컴파일단계에서 무시한다.
const_cast
const 성향 제거
dynamic_cast
상속관계에서의 안전한 형 변환 -> 런타임시에 말도 안되는 포인터면 캐스팅 X nullptr 리턴
reinterpret_cast
상관없는 자료형으로의 형 변환(C Style 캐스팅)