ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Class의 생성 과정
    C++ 언어 2020. 3. 31. 17:46

    C 언어의 제작자 작성 구조체와 함수

    가장 먼저 코드의 영역을 2가지로 나누어 생각해보자

    첫 번째는 제작자 코드 영역이다. 제작자 코드 영역은 재활용 가능한 코드를 추후 사용자에게 제공하는 것이다. 즉 제작자 코드란 제작자가 만든 기능을 사용자가 원할 때 사용할 수 있게끔 만든 것이다.

    두 번째는 사용자 코드 영역이다. 사용자 코드 영역은 제작자가 만들어서 제공해주는 코드를 실질적으로 가져다 사용하는 주체가 되는 영역이다. 아래 예시를 통해 이해해 보자

    // HelloNew_C+.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
    //
    
    #include "stdafx.h"
    #include<iostream>
    
    // 제작자 코드 //
    // 제작자 코드의 내용으로는 사용자로부터
    // 나이와, 이름을 입력받는다.
    typedef struct USERDATA
    {
    	int nAge;
    	char szName[32];
    } USERDATA ;
    
    
    
    
    // 사용자 코드
    int _tmain(int argc, _TCHAR* argv[]){
    	// 제작자가 제공하는 USERDATA 구조체를 사용하여
    	// 본인의 나이와 이름을 가지고 있는 객체 My_data 생성
    	USERDATA My_data = { 20, "Jongseok" };
    	
    
    	// 제작자가 제공한 자료구조를 사용한 출력
    	printf("나이 : %d\n이름 : %s\n", My_data.nAge, My_data.szName);
    
    	return 0;
    }
    
    

    가장 먼저 USERDATA라는 제작자 정의 자료구조를 선언하였으며 사용자는 해당 자료구조를 사용할 수 있다. 이때 사용자는 USERDATA 자료구조의 객체인 My_data를 생성하며 해당 객체의 nAge, szName의 값으로 본인의 나이와 이름을 입력하였다. 그 후 printf 함수를 동작시켜 출력하는 프로그램을 작성하였다.

    만약 프로그램 작성 도중 USERDATA 자료구조가 변경되었다 생각해 보자 사용자 코드 영역의 내용들은 더 이상 사용할 수 없게 된다. 따라서 해당 프로그램은 더 이상 올바르게 동작하지 않을 것이다.

    // 사용자 코드
    int _tmain(int argc, _TCHAR* argv[]){
    	// 제작자가 제공하는 USERDATA 구조체를 사용하여
    	// 본인의 나이와 이름을 가지고 있는 객체 My_data 생성
    	USERDATA My_data = { 20, "Jongseok" };
    	
    
    	// 제작자가 제공한 자료구조를 사용한 출력
    	printf("나이 : %d\n이름 : %s\n", My_data.nAge, My_data.szName);
    
    	return 0;
    }

    사용자 코드는 USERDATA 자료구조에 의존하고 있으며 해당 자료구조가 변경될 시 사용자 코드의 내용도 같이 변경되어야 사용할 수 있게 된다. 즉 사용자 코드와 제작자 코드 간에 관계가 생성되었다는 것이다.

    위의 동작 구조를 살펴보면 사용자라는 외부의 인물이 제작자가 작성한 자료구조인 USERDATA를 알맞게 사용해야 하는 구조를 가지고 있다. 따라서 사용자는 DATAUSER 자료구조에 대해서 ' 잘 ' 알고 있어야 하여 변경사항에 알맞게 사용해야 하는 단점을 가진다. 그렇다면 이와 같은 문제를 해결하기 위해선 제작자는 항상 사용자를 배려하여 코드를 작성해야 한다. 즉 사용자가 사용하기 쉽고, 사용자가 할 수 있는 실수를 " 사전 차단 " 하여야 한다.

    // HelloNew_C+.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
    //
    
    #include "stdafx.h"
    #include<iostream>
    
    // 제작자 코드 //
    
    // 제작자 코드의 내용으로는 사용자로부터
    // 나이와, 이름을 입력받는다.
    typedef struct USERDATA
    {
    	int nAge;
    	char szName[32];
    } USERDATA ;
    
    // 사용자를 위해 제작자가 해당 자료구조(USERDATA)의 출력 동작 (함수)를 제공한다.
    void Print_user(USERDATA* My_data){
    	printf("나이 : %d\n이름 : %s\n", My_data->nAge, My_data->szName);
    }
    
    
    
    
    // 사용자 코드 //
    int _tmain(int argc, _TCHAR* argv[]){
    	// 제작자가 제공하는 USERDATA 구조체를 사용하여
    	// 본인의 나이와 이름을 가지고 있는 객체 My_data 생성
    	USERDATA My_data = { 20, "Jongseok" };
    	
    
    	// 제작자가 제공한 자료구조를 사용자가 따로 printf 함수를 사용하여 출력
    	printf("나이 : %d\n이름 : %s\n", My_data.nAge, My_data.szName);
    
    	// 제작자가 제공한 자료구조와 출력 함수를 사용하여 출력
    	Print_user(&My_data);
    
    	return 0;
    }
    
    

    위의 코드를 보면 이전 에는 사용자가 직접 USERDATA라는 자료구조를 printf 함수를 통해 출력하였다면 이번에는 제작자가 USERDATA와 더불어 Print_user라는 함수를 제공함으로써 해당 자료구조를 출력하기 위한 동작(함수)까지 제공한다. 

    하지만 위와 같이 제작자가 자신이 작성한 자료구조를 활용하는 동작을 제공 (함수 제공) 하여도 문법적으로 본다면 제작자 자료구조인 USERDATA와 제작자 작성 함수인 Print_user의 연관을 찾기란 쉽지 않다. 따라서 USERDATA와 Print_user 간의 관계를 명시하기 위해 USERDATA 자료구조의 멤버로 제작자 작성 함수의 포인터를 넣어 줄 수 있다.

    // HelloNew_C+.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
    //
    
    #include "stdafx.h"
    #include<iostream>
    
    // 제작자 코드 //
    
    // 제작자 코드의 내용으로는 사용자로부터
    // 나이와, 이름을 입력받는다.
    typedef struct USERDATA
    {
    	int nAge;
    	char szName[32];
    
    	// USERDATA의 멤버로 Print_user 함수의 주소를 담을 포인터 생성 
    	void(*Print)(struct USERDATA *);
    
    } USERDATA ;
    
    // 사용자를 위해 제작자가 해당 자료구조(USERDATA)의 출력 동작 (함수)를 제공한다.
    void Print_user(USERDATA* My_data){
    	printf("나이 : %d\n이름 : %s\n", My_data->nAge, My_data->szName);
    }
    
    
    
    
    // 사용자 코드 //
    int _tmain(int argc, _TCHAR* argv[]){
    	// 제작자가 제공하는 USERDATA 구조체를 사용하여
    	// 본인의 나이와 이름을 가지고 있는 객체 My_data 생성
    	USERDATA My_data = { 20, "Jongseok", Print_user };
    	
    
    	// 제작자가 제공한 자료구조를 사용자가 따로 printf 함수를 사용하여 출력
    	printf("나이 : %d\n이름 : %s\n", My_data.nAge, My_data.szName);
    
    	// 제작자가 제공한 자료구조와 출력 함수를 사용하여 출력
    	Print_user(&My_data);
    
    	// 제작자가 제공한 자료구조 안의 함수 포인터를 사용하여 출력
    	// 이때 함수 포인터 안에는 Print_data 함수의 주소가 담겨있다.
    	My_data.Print(&My_data);
    
    	return 0;
    }
    
    

    제작자가 위와 같이 USERDATA라는 자료구조 안에 제작자 작성 함수의 주소를 넣을 수 있도록 기술함으로써 사용자는 제작자가 작성한 USERDATA의 객체인 My_data 객체 하나만으로 출력에 대한 동작까지 할 수 있게 되며 제작자 작성 함수인 Print_user는 USERDATA의 일부라고 명시적으로 확인할 수 있게 된다.

    실제로 리눅스 커널 내부를 살펴보면 위와 같이 제작자의 자료구조와 해당 자료구조를 사용할 수 있게 만드는 동작(함수)을 제공하며 해당 함수의 주소를 자료구조 내부의 멤버로 넣음으로 함수와 자료구조 간의 관계가 있음을 명시한다. 

     

    C++ 언어의 사용자 지정 Class와 Method

    이제 위의 내용을 이해하였다면 구조체의 멤버로 함수의 주소 (포인터)를 추가함으로써 제작자가 제공하는 객체의 동작인 함수까지 같이 제공해 줄 수 있다는 사실을 알았을 것이다. 

    하지만 C++로 넘어와서는 C의 구조체 문법을 더 이상 지원하지 않는다. But Class와 Method의 개념이 등장한다. 아래 코드를 비교하며 구조체와 Class의 차이를 인식해 보자

    구조는 C언어의 구조체와 비슷하다 하지만 Class는 구조체와는 다르게 멤버의 요소로 자료뿐만 아니라 동작 (Method)가 정의될 수 있다. 또한 해당 Method에 정의된 변수들은 식별자 검색 시 가장 우선으로 해당 Class의 멤버 요소를 검사하게 된다 따라서 아래와 같이 변경할 수도 있다.

    Class USERDATA 내부의 멤버인 nAge와 szName이라는 자료를 Class USERDATA 내부의 메서드가 사용할 수 있다.

     

    마지막으로 Class의 내부 동작을 메모리를 확인하며 이해해 보자

    실제로 Print_user 함수의 매개변수인 My_data 포인터 메모리에 접근해 보면 00 66 fe 6c라는 값이 들어있고 해당 값을 리틀 엔디안 방식으로 포인터로 해석하면 0x0066 fE6 C라는 주소로 볼 수 있다. 따라서 해당 주소는 Class USERDATA의 객체인 My_data의 주소를 뜻한다.

    위의 내용은 당연한 것이다. 매개변수로 Class의 객체인 My_data의 주소를 넘겼으니 해당 주소가 메모리에 표시된다. 그렇다면 매개변수로 아무것도 전달하지 않은 Class의 메서드는 어떨까?

    위의 자료의 test_data 변수의 주소를 따라가 보면 0x0088 F944라는 메모리의 주소가 나온다. 이때 Stack 메모리의 아래쪽을 보면 00 88 fa 30이라는 값을 가진 메모리 0x0088 F950 가 보일 것이다. 해당 메모리의 값을 USERDATA Class의 주소로 형 변환시킨 다음에 살펴보면 바로 USERDATA의 객체인 My_data 가 나온다 즉 0x0088F950이라는 메모리 안의 값이 My_data의 주소인 것이다.

    이는 위에서 살펴본 것과 같이 함수의 매개변수로 USERDATA Class의 주소 값을 넘겨준 것과 동일하다. 따라서 매개변수로 USERDATA Class의 주소를 넘겨주지 않아도 내부적으로 알아서 넘어가는 구조를 같는다.

     

    'C++ 언어' 카테고리의 다른 글

    Class 멤버 함수의 선언 및 정의 분리  (0) 2020.04.02
    생성자 함수와 소멸자 함수  (0) 2020.04.02
    함수 템플릿과 인라인 함수  (0) 2020.03.30
    참조자의 내부 동작  (0) 2020.03.27
    참조형과 포인터  (0) 2020.03.26

    댓글

Designed by Tistory.