버그 유형

1. NULL 크래시 (Null Crash)

개념

  • 정의: 없는 객체(포인터)가 가리키는 메모리(주소)에 접근하려고 할 때 발생.
  • 원인:
    • 포인터 초기화가 누락되었거나, 유효하지 않은 주소를 참조할 경우.
    • 함수 호출 시 nullptr을 처리하지 않는 경우.

코드 예제

class Player {
public:
    int hp = 0;
};

Player* FindPlayer(int id) {
    return nullptr;
}

void Test(Player* p) {
    if (nullptr == p) {
        return; // nullptr 체크 후 함수 종료.
    }
    p->hp = 10; // 안전한 접근.
}

int main() {
    Player* p = FindPlayer(100); // FindPlayer는 nullptr 반환.
    // p->hp = 100; // 여기서 크래시 발생 가능.
    Test(nullptr); // 안전한 호출.
}

분석:

  1. FindPlayer 함수:
    • 플레이어를 찾지 못하면 nullptr을 반환.
    • nullptr을 반환하면, 포인터를 통해 객체에 접근하려고 하면 문제가 발생.
  2. Test 함수:
    • 포인터가 nullptr인지 확인 후 안전한 경우에만 hp 값을 수정.
    • 이렇게 nullptr 체크를 통해 문제를 예방.

해결 방법:

  • nullptr 체크 필수:
    • 모든 포인터 접근 전에 nullptr 여부를 확인.
  • 스마트 포인터 사용:
    • C++11의 std::unique_ptr 또는 std::shared_ptr을 사용해 포인터 관리.

2. 정수 오버플로우 및 언더플로우

개념

  • 정의:
    • 정수 값이 데이터 타입이 표현할 수 있는 범위를 초과할 때 발생.
    • 오버플로우: 최대값 초과 → 최소값으로 롤오버.
    • 언더플로우: 최소값 초과 → 최대값으로 롤오버.
  • 원인:
    • 데이터 타입의 크기를 초과하는 연산.

코드 예제

int main() {
    short hp = 30000;
    while (true) {
        hp += 100; // 32767 초과 시 음수로 돌아감.
    }
}

분석:

  1. short 타입:
    • short는 2바이트 크기(-32768~32767)를 가지며, 초과 시 오버플로우 발생.
  2. 문제 발생:
    • 32767을 초과하면 음수로 롤오버.
    • 논리적 오류를 초래.

해결 방법:

  • 적절한 데이터 타입 선택:
    • 더 큰 데이터 타입(int, long)을 사용.
  • 오버플로우 검출:
    • C++20의 std::numeric_limits로 범위를 초과하는지 확인.

3. 메모리 릭 (Memory Leak)

개념

  • 정의: 동적 할당된 메모리를 해제하지 않아, 사용하지 않는 메모리가 계속 점유되는 현상.
  • 원인:
    • new 또는 malloc으로 할당된 메모리를 delete 또는 free로 해제하지 않음.

코드 예제

class Item {
public:
    Item() {}
    ~Item() {}
};

int main() {
    while (true) {
        Item* item = new Item(); // 동적 메모리 할당.
        // delete item; // 해제 누락 시 메모리 릭 발생.
    }
    return 0;
}

분석:

  1. 메모리 릭 발생:
    • 루프 내에서 new Item()으로 동적 메모리를 할당하지만, 해제(delete)하지 않아 점유된 메모리가 계속 증가.
  2. 결과:
    • 시간이 지날수록 메모리 사용량이 증가하여 시스템이 불안정해짐.

해결 방법:

  • 스마트 포인터 사용:
    • C++11의 std::unique_ptr 또는 std::shared_ptr로 메모리 자동 관리.
  • 디버깅 도구:
    • Visual Studio의 CRT Debugging(_CrtDumpMemoryLeaks) 또는 Valgrind를 사용.

4. 메모리 오염 (Memory Corruption)

개념

  • 정의: 잘못된 메모리 접근 또는 관리로 인해 데이터가 손상되는 현상.
  • 원인:
    • 잘못된 캐스팅.
    • 버퍼 오버플로우.
    • Use-After-Free.

(1) 잘못된 캐스팅

코드 예제
class Player {};
class Dog {};

int main() {
    Player* p = new Player();
    Dog* dog = reinterpret_cast<Dog*>(p); // 위험한 캐스팅.
    // dog-> 특정 멤버 접근 시 프로그램 충돌 가능.
}
분석:
  1. reinterpret_cast 사용:
    • Player*Dog*로 강제로 변환.
  2. 문제:
    • 두 클래스가 전혀 관계가 없으므로, 변환된 포인터를 통해 접근하면 메모리 오염 발생.
해결 방법:
  • 캐스팅 제한:
    • 반드시 타입 간의 관계가 명확한 경우에만 캐스팅 수행.

(2) 버퍼 오버플로우

코드 예제
int arr[100];
arr[101] = 50; // 초과된 인덱스 접근.
분석:
  1. 문제:
    • 배열 크기를 초과하는 인덱스에 접근하면, 다른 메모리 공간을 덮어씌우게 됨.
  2. 결과:
    • 데이터 손상, 충돌 등 심각한 문제를 초래.
해결 방법:
  • 범위 검사:
    • 배열 접근 전에 항상 유효한 인덱스인지 확인.

(3) Use-After-Free

코드 예제
class Monster {
public:
    int _hp = 100;
};

int main() {
    Monster* m = new Monster();
    delete m; // 메모리 해제.
    m->_hp = 200; // Use-After-Free 발생.
}
분석:
  1. 문제:
    • 해제된 메모리 주소에 접근하여 데이터를 수정.
  2. 결과:
    • 다른 메모리 할당과 충돌 가능.
해결 방법:
  • 포인터 초기화:
    • 메모리 해제 후 포인터를 nullptr로 초기화.
  • 스마트 포인터 사용:
    • 메모리 관리가 자동화되어 문제 방지.

B. 디버깅 기술

  1. NULL 크래시 해결:

    • nullptr 체크.
    • 스마트 포인터 사용.
  2. 정수 오버플로우 방지:

    • 적절한 데이터 타입 사용.
    • 범위 초과 검출.
  3. 메모리 릭 방지:

    • 스마트 포인터 사용.
    • 디버깅 도구 활용.
  4. 메모리 오염 방지:

    • 안전한 캐스팅 수행.
    • 배열 접근 시 범위 검사.
    • Use-After-Free 방지 기법 활용.

profile
李家네_공부방

0개의 댓글