티스토리 뷰

class Child
{
public:
    int age;
    void run() {
        std::cout << "자식 run" << std::endl;
    }

    virtual void run2() {
        std::cout << "자식 run" << std::endl;
    }
};

int main(void)
{	

	// case 1 non-virtual 함수 : 실행됨
	Child *temp = NULL;
	temp->run();

	// case 2 virtual 함수 : segfualt
	Child *temp2 = NULL;
	temp2->run2();

	// case3 멤버변수 : segfault
	Child *temp3 = NULL;
	std::cout << temp3->age << std::endl;

}

 

"클래스의 멤버변수와 멤버함수는 객체가 할당될 때 메모리에 올라가기 때문에, 할당 후에 접근이 가능하다." 고 생각했었다.

그래서 null pointer로 접근한 case 2나 case 3에서 에러가 발생하는 건 납득했지만, case 1의 경우 출력이 되는 이유를 알지 못했다.

 

이를 확인하기 위해선 '클래스의 메모리 구조'에 대한 이해가 필요하다 (static, virtual, binding 등 키워드도 알아야 함)


1. 기본적인 메모리 구조에 대해서는 아래 글을 참고하자

메모리구조, 정적할당과 동적할당

 

http://tcpschool.com/c/c_memory_structure

2. 클래스의 멤버

클래스에는 멤버변수와 멤버함수가 있고 각각 정적(static), 비정적(non-static)으로 선언이 가능하다.

또한 멤버함수는 가상(virtiual)으로 선언이 가능한데, virtual과 static은 동시 사용이 불가능하다. 

표로 정리하면 아래와 같다.

C++ 클래스 static non-static
멤버변수 static non-static
멤버함수 static non-static non-static + virtual

 

3. static, virtual, binding은 간단하게 정리

static 멤버를 사용하는 이유

  • static 멤버변수: 여러 인스턴스가 공유해서 사용할 수 있는 변수
  • static 멤버함수: 인스턴스가 생성되지 않더라도 호출한 필요한 함수

가상함수를 사용하는 이유

: 동적바인딩을 통한 다형성 - 인스턴스의 실제 클래스에 따라 오버라이딩된 함수 호출이 가능

 


4. 클래스의 메모리 구조

 

클래스 안에 멤버변수와 멤버함수가 있기 때문에 둘이 같은 메모리 공간에 위치한다고 생각할 수 있지만, 그렇지 않다

 

  • 인스턴스: 생성될 때마다 각각 다른 메모리 주소에 할당된다. (정적할당:스택 or 동적할당:힙)
  • non-static 변수: 인스턴스의 생성과 동시에 할당된다. (정적할당: 스택 or 동적할당: 힙)
  • static 변수:  인스턴스 생성 시가 아니라, 프로그램 시작 시 저장되고 프로그램이 종료되어야 소멸한다. (데이터영역) -> 여러 인스턴스가 공유한다.

https://42place.innovationacademy.kr/archives/8470

  • 멤버함수: static, non-static 모두 프로그램 시작 시 코드영역에 저장된다. 멤버함수는 인스턴스마다 다르게 동작하는 것이 아니기 때문에 인스턴스마다 함수가 할당되면 비효율적일 것이다. 따라서 해당 클래스의 모든 인스턴스는 코드 영역에 있는 멤버함수를 공유하면서 사용한다. (코드영역) 
  • 함수를 공유할 때, 함수를 호출한 인스턴스의 구분은 어떻게 하는 것일까? (ex 함수 내부에서 멤버변수를 사용한다면, 해당 인스턴스 정보가 필요) 멤버 함수가 코드영역에 올라갈 때 객체의 포인터를 hidden 인자로 전달받도록 변경되기 때문에, 어느 인스턴스에서 호출한 함수인지 확인할 수 있다.

 

인스턴스가 생성될 때 같이 생성되는 멤버는 밑줄친 non-static 멤버변수뿐이다. 따라서 클래스의 자체의 크기에도 non-static 멤버 변수만 영향을 미친다.

 

여기서 추가로 생각해볼 부분이 바로 virtual 함수다.

  • 클래스에 가상함수를 선언하면 가상함수테이블이 생성되고(어디에? 아마 코드영역..??) 클래스 내부적으로 가상함수테이블을 가리키는 포인터를 갖는다. (4 or 8바이트)
  • 가상함수의 개수에 관계없이 포인터의 개수는 1개고, 클래스의 크기에 영향을 준다. 
  • 가상함수테이블은 클래스단위로 생성되고, 가상함수테이블 포인터는 인스턴스단위로 생성된다.

정리해보면...

인스턴스가 할당될 때 같이 생성되는, 즉 인스턴스를 통해 접근이 가능한 멤버는 non-static 멤버변수와 가상함수(테이블포인터)다.

따라서 얘네들은 null pointer를 통해 접근하면 바로 에러가 발생한다.

 

static은 본래 인스턴스 없이 클래스이름을 통해 접근이 가능하다.

 

non-static, non-virtual 멤버함수는 코드영역에 생성되어서 null포인터를 통해 접근은 가능하다.

다만, 해당 멤버함수 내부에서 객체에 접근(non-static, non-virtual 멤버함수 외의 모든 멤버들)할 때 에러가 발생한다.

 

 

다시 한 번 위의 코드를 살펴보자 (static case 추가)

class Child
{
public:
    int age;
    void run() {
        std::cout << "자식 run" << std::endl;
    }

    virtual void run2() {
        std::cout << "자식 run" << std::endl;
    }
    
    static void run3() {
        std::cout << "자식 run" << std::endl;    
    }
    
};

int main(void)
{	

	// case 1 non-virtual 함수 : 실행됨
	Child *temp = NULL;
	temp->run();

	// case 2 virtual 함수 : segfualt
	Child *temp2 = NULL;
	temp2->run2();

	// case 3 멤버변수 : segfault
	Child *temp3 = NULL;
	std::cout << temp3->age << std::endl;

	// case 4 static 함수 : 실행됨
	Child *temp4 = NULL;
	temp4->run3();
}

case 1 :

출력이 가능한 이유는 run() 함수가 객체에 접근하고 있지 않기 때문이다.

이 멤버함수 내부에 객체에 접근하는 코드를 추가한다면 case 2, 3과 동일하게 segfault가 발생하게 된다.

 

case 4:

출력이 가능한 이유는 run3() 함수가 static이기 때문에 인스턴스와 상관없기 때문이다.

이 멤버함수 내부에서 static 멤버 변수에 접근하는 코드를 추가해도 정상적으로 실행이 된다. (당연히 non-static은 컴파일조차 안됨)

 

 

 

표로 마지막 정리

NULL 포인터로 호출가능? static non-static
멤버변수 O X
멤버함수 O non-static
(객체와 관계 없는 함수만 O)
non-static + virtual
(X)

하지만 null pointer를 통한 모든 접근은 undefined behavior이기 때문에 이렇게 사용해서는 안됨! (자바에서는 전부 NullPointerException 발생함)

왜 얘는 에러고 쟤는 아닌지 궁금해서 정리한 것일 뿐!

static 호출은 클래스 이름을 통해서, non-static 호출은 인스턴스를 통해서 하자!

 


참조

 

 


추가 정리가 필요한 부분

1. 가상함수테이블은 코드영역에 생성되는 것이 맞는지

2. null->(non-virtual, non-static 함수)에서 객체에 접근하면 에러가 발생하는데.. 이 객체의 범위에 static이 포함되는 이유는??

클래스이름::변수 를 사용하면 인스턴스가 필요 없으니까 접근 가능하다고 생각했는데 아니던데..

'42 > cpp' 카테고리의 다른 글

생성자, 복사생성자, 복사대입연산자, 소멸자  (0) 2021.05.27
참조자  (0) 2021.05.26
메모리구조, 정적할당과 동적할당  (0) 2021.05.25
객체지향, 접근지정자  (0) 2021.05.25
네임스페이스, 표준입출력  (0) 2021.05.18
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함