1. JNI 변수 타입


JNI 변수 타입은 Java의 변수를 C++/C 에서 사용 할수 있게끔 호환해주는 변수 타입이다. 

jni.h를 인클루드하면 사용이 가능한데 자세한 정보는 아래의 표를 보면 알 수 있다.



JAVA 

C++/C 

C++/C 배열 

boolean

jboolean 

jbooleanArray 

byte

jbyte 

jbytArray

char 

jchar 

jcharArray 

short 

jshort 

jshortArray 

int 

jint 

jintArray 

long

jlong 

jlongArray 

float 

jfloat 

jfloatArray 

void 

jvoid 

jvoidArray 

Object 

jobject 

jobjectArray 

String 

jstring 

jstringArray 


java의 기본 변수형은 c++/c언어에서는 j가 붙은 클래스로 제공된다. int -> jint로 객체를 선언해서 사용해주면 되고, 배열도 마찬가지로 j+자료형+Array 객체형태로 생성해서 사용하면 된다.



c++ 에서의 사용 예


void main() {     jstring str;     jint num;     jobject obj;     ... }




2. JNI 시그니처

JNI 시그니처를 사용하는 이유는 JAVA에서 생성한 클래스나 함수에 접근하기위해 C++/C에서 사용하는 구분자라고 보면 된다. 


시그니처 

Type 

byte 

char 

double 

float 

int 

long 

short 

void 

boolean 

L클래스_이름 

패키지 경로/ 클래스 이름 

[ type 

type[] 배열 


public class aa {
	public int testInt(int a) {return a;}
	public String testString(String a) {return a;}
}


위와같은 java 소스에서 aa클래스 안에는 int testint(int) 메서드와 String testString(String) 메서드가 있다.

c++에서 위의 메서드를 콜하기 위해서는 아래와 같은 문법으로 접근해야한다.



void main() {
	jclass cls = (*env)->GetObjectClass(env, obj);
	jmethodID testInt = (*env)->GetMethodID(env, cls, "testInt", "(I)I");
	jint jnum = (*env)->CallIntMethod(env, obj, testInt, num);

	jmethodID testString = (*env)->GetMethodID(env, cls, "testString", "(Ljava.lang.String;)Ljava.lang.String;");
	jstring jstr = (*env)->CallObjectMethod(env, obj, testString, str);
}


GetMethodID()를 이용하여 java함수를 콜할수 있는데 JvmEnv, jclass, "함수명", 시그니처가 매개변수로 들어간다. aa클래스를 담고있는 jclass와 함게 함수명 "testInt" 그리고 시그니처 (I)I 를 넣어줘야 위의 Java에서 생성한  int testInt(int)를 가져올 수 있다.

String 형 같은 경우에는 기본 변수타입이 아니기때문에 Object형식으로 접근해야한다. Ljava.lang.String;으로 시그니처를 입력해야하는데 주의할 점은 ; 세미클론을 빼먹을 경우 인식을 하지 못한다. 이점 유의해서 사용하길 바란다.


String형과같은 Object형을 함수에서 return시키거나 input시킬때에는 해당 클래스의 임포트 경로를 입력해주면된다. 간단하져?




앞서 C++에서 JNI를 사용하기위해 프로젝트 셋팅작업을 끝냈습니다.

이번에는 간단하게 Java class를 생성하여 클래스안에 있는 메서드를 호출하는 예제를 만들어보겠습니다. 

코드는 어려운게 없으니 천천히 따라오시면 쉽게 구현하실수 있습니다.




1. Hello.class 만들기


public class Hello {
	void sayHello() {
		System.out.println("Say HELLOOO");
	}
}


생성자가없는 Hello.java 파일입니다. 간단하게 sysout으로 인사말을 프린트 하는 메서드입니다. 위의 코드를 코딩하셔서 java파일로 만드시고

javac 명령어를 사용하여 java파일은 class파일로 컴파일 시켜주세요


javac Hello.java


위에서 생성한 Hello.class파일을 C:\Java\src 경로에 넣어주세요! 해당 경로는 c++ 에서 java 클래스파일을 읽어올 경로입니다. c++ 프로젝트 내부에 상대경로로 설정하셔도 됩니다만 저는 C드라이브 밑에  간단하게 테스트용으로 생성했습니다.



2. c++ 코딩


#include 
#include 
#include "stdafx.h"
#include 

using namespace std;

void main() {
	// JavaVM 생성 & JVM 환경설정
	JavaVMOption options;
	JavaVMInitArgs vm_args;
	JNIEnv *env;
	JavaVM *jvm;
	long status;

	options.optionString = "-Djava.class.path=C:\\Java\\src"; // 자바 클래스파일이 있는 경로
	memset(&vm_args, 0, sizeof(vm_args));
	vm_args.version = JNI_VERSION_1_6;
	vm_args.nOptions = 1;
	vm_args.options = &options;
	status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

	cout << "JVM 구동!" << endl;
	jclass cls;
	jmethodID mid;
	jobject obj;
	int staticresult = 0;
	int result = 0;

	if (status != JNI_ERR)
	{
		cls = env->FindClass("Hello");		// Hello.class를 찾아 jclass를 생성한다.

		if (cls != 0)
		{
			jmethodID cls_constructor = env->GetMethodID(cls, "", "()V");		// Hello.class의 기본생성자를 찾아 생성자 메소드를 생성한다.
			if (cls_constructor != 0) {
				obj = env->NewObject(cls, cls_constructor, "()V");		// 클래스와 클래스 생성자를 이용해 객체(object)를 생성한다.
				mid = env->GetMethodID(cls, "sayHello", "()V");			// Hello.class에 정의한 sayHello() 메소드를 찾아 JmethodID 담는다.
				env->CallIntMethod(obj, mid);							// 메소드를 호출하는데 GetMethodID 함수를 이용해 위의 JmethodID를 파라메터로 넘겨준다.
			}
		}
		else
		{
			cout << "클래스를 찾을 수 없습니다." << endl;
			return;
		}

		jvm->DestroyJavaVM();
		cout << "JVM 삭제" << endl;
	}
}

}


간단하게 메인함수에서 호출하게 만들어봤습니다. 1편에서 셋팅을 제대로 하셨다면 컴파일 에러가 없으실꺼에요~


JNI란 JAVA에서 NATIVE영역으로 들어가 호출 또는 NATAVIE에서 JAVA로 호출하는 인터페이스를 말한다.

쉽게 말해 JNI는 JAVA와 다른 언어를 연동하는 라이브러리? 솔루션? 이라고 말할 수있다.



JAVA <---> JNI <---> C/C++



이런 형태로 서로 다른 언어에서 메소드(함수)를 호출하여 사용할 수 있는데 기존에 작성된 방대한 C/C++ 라이브러리를 JAVA에서 이용하기 위한 방법으로 사용된다고 한다.


회사 프로젝트로 이번에 JNI를 담당하게 됬는데 생각보다 C++에서 사용하기위해 JAVA함수를 호출하는 예제가 없거나 너무 옛날 글들이 많았다. 

수많은 뻘짓을 통해 성공을 했는데 막상 해보고나니 소스가 어렵다기보단 프로젝트 환경설정해주는거에서 막혔던 부분이였다.


포스팅을 하기 앞서 이번 가이드에서 사용할 환경을 미리 안내하고 시작하려고 한다.


1. Visual Studio 2015

2. JDK 1.8 (32 or 64)


사실 비쥬얼 스튜디오 버전은 크게 상관없을것이다. 문제는 빌드하려는 환경(32비트,64비트)에 맞는 jdk를 설치해줘야 한다는점.




1) JDK 설치 및 환경 변수 셋팅

- 오라클 홈페이지에서 jdk 1.8 버전을 다운로드 받은뒤 설치를 하도록 하자.


http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html


나는 32 / 64비트 모두 필요하기에 전부 받아 설치하였다.

우선 32비트 환경에서 빌드하기 위해 32비트 환경 설정을 하도록 하겠다. 64비트 운영체제에서 32비트 프로그램은C:\Program Files (x86) 에 저장이 된다.



시스템 속성 -> 환경 변수 버튼 클릭





시스템 변수 새로만들기 버튼 클릭





변수 이름 JAVA_HOME 변수 값 JDK 설치 경로 지정 후 확인





시스템 변수 Path 선택하여 편집버튼 클릭





새로 만들기 버튼 클릭하여 %JAVA_HOME%\bin, %JAVA_HOME%\jre\bin\server 작성 후 확인 버튼



여기까지하면 시스템 변수 잡아주는 과정은 끝났다. 제대로 잡혔는지 확인해보고 싶으면 cmd창을 띄운뒤 javac -version 을 입력해보길 바란다.






2) Visual Studio Project 생성 및 속성 수정



Win32 콘솔 어플리케이션으로 프로젝트를 생성한다.





뷰에서 프로젝트 우클릭하여 프로퍼티 속성을 클릭하거나 alt + enter 키를 눌러 속성창을 킨다





좌측 메뉴에서 C/C++ -> General 탭에서 Addtionnal Include Directories에 패스를 입력해준다.




C:\Program Files (x86)\Java\jdk1.8.0_181\include\win32;

C:\Program Files (x86)\Java\jdk1.8.0_181\include;%(AdditionalIncludeDirectories)


위의 2개 경로 입력




Linker -> General 탭에서 Addtional Library Directories 수정


C:\Program Files (x86)\Java\jdk1.8.0_181\lib




Linker -> input

jvm.lib 추가



여기까지가 프로젝트 속성 셋팅이 끝났다

다음 포스팅에서는 간단하게 구현을 해보겠다~


1. 배열의 선언과 사용

- 동일한 자료형을 저장할 저장공간이 많이 필요한 경우 일일이 변수를 선언하는것을 비효율적이다. 이때 배열을 사용하여 대체할수 있다.

배열을 사용하기 위해서는 선언을 먼저 해야한다. 배열의 선언은 간단하다. 요소의 자료형에 이름을 붙이고 필요한 요소 수를 표시한다.


int ary[5];


int = 요소 형태

ary = 배열명

[5] = 요소 개수


 

#include<stdio.h>


int main(void)

{

int ary[5];


ary[0] = 10;

ary[1] = 20;

ary[2] = ary[0] + ary[1];

scanf("%d", &ary[3]);


printf("%d\n", ary[2]);

printf("%d\n", ary[3]);

printf("%d\n", ary[4]);


return 0;

}


INT형 변수 5개를 하나씩 선언하는 것과 전체 저장 공간의 크기는 같다. 그러나 메모리할당되는 방식에는 차이가 있다. 변수를 선언하면 각 변수는 독립적인 저장공간을 갖고 각각의 이름으로 사용되는데

int a, b, c, d, e;
a(4byte), b(4byte), c(4byte), d(4byte), e(4byte)


반면에 배열은 저장공간이 연속으로 할당되어 배열명이 전체 공간의 이름이 된다.

int ary[5]; // 메모리 공간이 연속적으로 할당되며 이름은 하나다.

ary(20byte)


int형 변수의 크기가 4바이트이므로 5개를 연속으로 할당하면 총 20바이트를 할당된다. 배열의 나누어진 조각을 배열 요소라 하는데, 각각의 배열 요소는 int형 변수와 똑같이 쓰인다. 배열 요소는 배열명에 첨자(index)를 붙여 표현하며 첨자는 0부터 시작한다.


ary[0], ary[1], ary[2], ary[3], ary[4]



배열을 선언할 때와 배열 요소를 사용할때 대괄호 안의 숫자는 의미가 다른다. 선언할 때는 배열 요소의 전체 개수를 표시하며, 사용할 때는 각 요소가 ㅁ배열에서 몇 번째에 있는지 의미한다. 이값이 첨자며 첨자를 사용할 때는 다음 내용을 반드시 지켜야한다.


배열의 첨자는 0부터 시작하므로 최대 '배열 요소 수 -1'까지 사용가능하다. 

-> 배열의 ary 크기가 5일때 ary[5]를 사용하면? compile error

배열의 요소를 사용할때 첨자의 사용 범위를 벗어나면 그 결과를 예측할 수 없다. 배열에 할당된 영역을 벗어난 메모리를 사용하므로 침범함 영역이 어떤 용도에 사용되느냐에 따라 결과가 달라진다. 







2. 배열의 초기화

- 변수와 마찬가지로 배열도 최초 할당된 저장공간에는 쓰레기값이 있다. 배열이 선언과 동시에 원하는 값을 갖도록 하려면 초기화를 해야 한다.


배열은 중괄호로 묶어서 초기화한다.


단, 반드시 선언과 동시에 초기화해야하며, 선언하고 난 후에 값을 저장하려면 배열 요소에 하나씩 값을 대입해야한다.


int main(void)

{

int ary1[5] = { 1,2,3,4,5 };

int ary2[5] = { 1,2,3 };

int ary3[] = { 1,2,3 };

double ary4[5] = { 1.0, 2.0, 3.0, 4.0, 5.4 };

char ary5[5] = { 'a','b','c','d','e' };


ary1[0] = 10;

ary1[1] = 20;

ary1[2] = 30;

ary1[3] = 40;

ary1[4] = 50;


return 0;

}


초깃값은 첫 번째 요소부터 차례로 초기화된다. ary2의 경우 초깃값이 배열 요소 수보다 적은 경우인데 이대는 다음의 규칙을 따른다

왼쪽에서 차례로 초기화되고 남는 배열의 요소는 모두 0으로 채운다.

이런 자동초기화 기능을 사용하면 배욜 요소 수가 많아도 모든 요소를 쉽게 0으로 초기화 가능하다.







우선순위

종류 

연산자 

연산 방향 

 1

 1차 연산자

( ) [ ] . -> 

-> 

 2

 단항 연산자

 - + + - - ~ ! * & sizeof (type) 

<-

 3, 4

 산술 연산자

 * / % + -

 -> 

 5

 비트 이동 연산자

 << >>

 -> 

 6

 관계 연산자

 < <= > >=

 -> 

 7

 동등 연산자

== != 

 -> 

 8, 9, 10

 비트 논리 연산자 

& ^ | 

 -> 

 11, 12

 논리 연산자 

&& || 

 -> 

 13

 조건 연산자 

? : 

  -> 

 14

 대입 연산자 

= += -= *= /= %= &= ^= |= <<= >>= 

 <-

 15

 콤마 연산자 

 -> 

(연산자의 종류)



1. 연산자와 피연산자


- 프로그램은 CPU가 처리하는 명령들의 묶음이다. 일반적으로 연산자는 컴파일되면 명령어로 바뀌므로 연산자를 배우는 것은 결국 명령을 익히는것이다. 이든 연산 명령에는 연산의 대상이 되는 데이터가 필요한데 이를 피연산자라고 한다.


++a    10%3    (a>10)&&(a<20)


연산자는 보통 기능에 따라 분류되지만 피연사자의 개수에 따라 나눌수도 있다.

피연산자가 하나면 단항연산자, 2개면 이항연산자, 3개명 삼항연산자





2. 산술 연산자와 대입 연산자


- 산술 연산자는 +, -, *, /, %가 있는데 모두 2개의 피연산자를 사용한다. 이중 -연산자는 피연산자를 하나만 사용하여 그 부호를 바꾸는 연산자로도 쓰인다.


a = 10;    sum= a+b;


대입연산자 오른쪽의 변수는 값의 변화가 없고 왼쪽의 변수는 값이 바뀐다.


일반적으로 a와b같은 변수는 데이터를 저장하는 메모리(ram)에 존재하는 반면 연산은 CPU에서 이뤄진다. 연산할 때는 메모리에 있는 변수의 값을 CPU로 복사하여 사용하므로 아무리 많은 ㅇ녀산을 수행해도 피연산자 a, b값은 변하지 않는다. 반면 대입 연산을 수행한 sum, sub, mul, dnv의 값은 변하게 된다.





3. 나눗셈 연산자와 나머지 연산자


- 나눗셈에는 / 연산자를 사용한다. 나눗셈 연산자를 사용할 때는 피연산자의 형태에 따라 결과가 달라지니 주의해야한다.


나눗셈 연산자는 정수로 연산할 때 몫을 구하고 실수로 연산할 때는 소수점까지 구한다.


나눗셈 연산자의 이런 특징은 수학적 나눗셈을 기대한 경우 활당한 결과가 되겠지만 몫을 구할 때에는 유용하게 쓰인다. 반면에 몫은 뺀 나머지만을 구할 때는 나머지 연산자인 %기호를 사용한다. 실수 연산에는 나머지 개념이 없으므로 나머지 연산자의 피연산자로는 반드시 정수만을 사용한다.





4. 증감 연산자


증감 연산자는 단항 연산자를 피연산자의 값을 1증가시키거나 1감소시킨다.

증가시킬 때는 '++' 연산자를, 감소시킬 때는 '--' 연산자를 사용한다. 증감 연산자는 하나의 연산자로 대인 연산까지 수행하므로 변수의 값을 1씩 증가시키거나 감소시킬때 쉽게 표현할수 있다.


++a --a


증감 연산자는 특이하게도 ++a 뿐만 아니라 a++ 형태와 같이 사용할수있따. 피연산자의 앞에 놓이면 전위형, 뒤에 놓이면 후위형이라고 한다.


증감 연산식이 단독으로 사용된 경우엔 전위형과 후위형의 결과는 같다. 그러나 다른 연산자와 함께 쓰이면 다른 연산의 결과에 영향을 미친다. 

증감 연산자의 후위형은 다른 연산자와 함께 사용될 때 가장 나중에 연산된다.





5. 관계 연산자


관계 연산자에는 대소 관계 연산자와 동등 관계 연산자가 있다.


대소 관계는 < 또는 > 등의 기호를 사용하고, 동등 관계는 ==나 != 기호를 사용한다. 이들 연산자는 모두 피연산자를 2개 사용하며, 연산의 결과값은 1또는 0이 된다. 컴파일러는 0은거짓으로 -이 아닌 값은 참으로 판단하기에 관계식을 실행 조건 검사에 사용할수있다. 



관계 연산의 결과는 주로 판단의 근거로 사용된다.


예를 들어 a와 b값이 무엇인지 모르지만 둘 중에 큰값을 택한다면 a>b 연산을 수행항 ㅕ결곽밧을 확인하면 된다. 결과가 1이면 a가 b보다 크므로 a를 택하고 결과가 0이면 b가 크거나 최소한 같으므로 b를 택하면 된다. 





6. 논리 연산자


논리 연산자는 &&, ||, ! 세개이다.


&& 연산자는 2개의 피연산자가 모두 참일 때만 연산결과가 참이 되고 || 연산자는 둘 중에 하나라도 참이면 참이 된다. ! 연산자는 피연산자를 하나 사용하여 그 참과 거짓을 바꿀때 사용한다. 

'C' 카테고리의 다른 글

[C언어] 열거형 enum  (0) 2018.08.24
[C언어] 구조체와 공용체 (struct, union)  (0) 2018.08.24
c언어 배열  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (1) 2018.07.14
C언어 포인터 기초 개념  (0) 2018.07.14

포인터의 크기는 애플리케이션의 호환성과 다른 환경으로의 이식 가능성을 고민할때 문제가 된다. 최근에 널리 사용되는 대부분의 운영체제 환경에서 포인터의 크기는 일반적으로 포인터 타입에 상관없다. 예를 들면 char에대한 포인터는 구조체에 대한 포이터와 크기가 같다. C표준에서 모든 데이터 타입에 대한 포인터의 크기가 같아야 한다고 명시하고 있지는 않지만, 일반적으로 포인터의 크기는 동일하다. 하지만 함수에 대한 포인터와 데이터에 대한 포인터의 크기는 다를 수도 있다.



포인터의 크기는 사용하는 장비와 컴파일러에 따라 다르다. 예를 들어 윈도우버전에서 포인터의 크기는 32 또는 64bit이며 오래된 dos 그리고 왼도우 3.1 os에서 포인터는 16또는 32비트다.




1. 메모리 모델

- 64bit 컴퓨터 도입으로 C기본 데이터 타입의 메모리 크기 차이가 활실해졌다. 컴퓨터와 컴파일러들은 c 기본 데이터 타입의 메모리 할당을 위해 저 마다 다른 옵션의 메모리 모델을 가지고 있다. 이 메모리 모델들을 설명하기 위해 일반적으로 아래와 같은 표기법을 사용한다.



I In L Ln LL LLn P Pn



각 대문자는 정수(Integer), 롱(Long), 포인터(Pointer)에 해당하며, 각 소문자는 타입에 할당된 비트 수다. 


 C Data Type

LP64 

ILP64 

LLP64 

ILP32 

LP32 

char 

short

16 

16 

16 

16 

16 

__int32 

 

32 

 

 

 

int 

32 

64 

32 

32 

16 

long 

64 

64 

32 

32 

32 

long long 

 

 

64 

 

 

pointer 

64 

64 

64 

32 

32 




2. 사전 정의된 포인터 관련 데이터 타입

- 포인터를 다룰 때 다음 네가지의 사전 정의된 데이터 타입이 종종 사용된다


1) size_t -> 안전한 크기 타입 제공을 위해 사용

2) ptrdiff_t -> 포인터 연산을 처리하기 위해 사용

3) intptr->t와 uintptr_t -> 포인터 주소를 저장하기 위해 사용



3. size_t 타입에 이해

- size_t 타입은 c언어에서 임의의 객체가 가질 수 있는 최대 크기를 나타낸다. 크기를 표현하는데 음수의 사용은 의미가 없기 때문에 size_t는 부호 없느 ㄴ정수를 사용한다. size_t 타입을 사용하은 이유는 시스템에서 주소 지정이 가능한 메로리 영역과 일치하는 크기를 선언하는 이식 가능한 방법을 제공하기 위해서이다. 


malloc 함수와 strlen 함수가 size_t타입을 반환하는 인자로 사용되는 대표적인 함수이다. 일반적으로 32 system에서는 32bit, 64 system에서는 64bit이다. 



4. 포인터와 sizeof연산자 사용하기

- 포인터의 크기를 확인하는데 sizeof 연산자를 사용할 수 있다. 아래 코드는 char 포인터에 대한 크기를 출력한다.


printf("Size of *char: %d", sizeof(char*));


결과:

size of *char: 4


함수 포인터의 크기는 다를 수 있다. 보통의 주어진 환경의 운영체제와 컴파일러 조합에따라 결정된다 많은 컴파일러는 32비트나 64비트 애플리케이션 생성을 지원하며 같은 프로그램도 컴파일 옵션에 따라 다른 포인터 크기를 가진다. 



5. intptr_t uintptr_t 사용하기

- intptr_t와 uintptr_t 타입은 포인터의 주소를 저장하는데 사용된다. 이 두 타입은 다른 환경으로 이식이 가능하고 안전한 포인터 선언 방법을 제공하며, 시스템 내부에서 사용하는 포인터와 같은 크기다. 포인터를 정수 표현으로 변환할때 유용하게 사용할수 있다. 


uintptr_t타입은 intptr_t 타입의 부호 없는 버전에 해당된ㄷ. 대부분의 연산에서 intptr_t 타입을 사용하는 것이 좋으며, uintptr_t 타입은 intptr_t 타입만큼 유연하지 않고 사용이 제한적이다. 아래는 intptr_t 타입의 사용 방법을 보여준다.


int num;

intptr_t *pi = &num;


다음의 코드를 실행하면 문법 에러가 발생한다.


uintptr_t *pu = &num;


error: invalid converstio nfrom 'int*' to 'uintptr_t* {aka unsigned int*}'


위의 에러는 타입이 맞지 않아 생기는 에러이다. 캐스팅을 해주면 정상 할당된다.


uintptr_t *pu = (uinttpr_t *)&num;

'C' 카테고리의 다른 글

[C언어] 열거형 enum  (0) 2018.08.24
[C언어] 구조체와 공용체 (struct, union)  (0) 2018.08.24
c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터 기초 개념  (0) 2018.07.14

포인터는 C언어 전반에 많은 영향을 미치며 언어 자체에 많은 유연함을 제공한다. 포인터는 동적 메모리 할당의 매우 중요한 부부이기도 하며, 배열의 표기법과 밀접한 관련이 있다. 


오랫동안 포인터는 C언어 학습에 걸림돌이 되었는데 포인터의 기본적이 개념은 매우 간단하다. 포인터는 메모리 위치의 주소를 저장하는 변수이다. 하지만 포인터 연산자를 사용하고, 암호와도 같은 난해한 표기법을 식별하려고 할 때부터 이 개념이 급격하게 복잡해진다. 포인터를 이해하는 열쇠는 C프로그램에서 메모리가 어떻게 관리되는지 이해하는데 있다. 포인터는 메모리의 주소를 담고 있기 때문에 메모리가 구성되고 관리되는 방법을 이해하지 못한다면 포인터의 동작 방식을 이해하기 쉽지 않다. 그렇기에 메모리의 구성 방법을 확실하게 이해하고 나면 포인터를 이해가기가 쉽다.


1. 포인터와 메모리

컴파일된 C 프로그램은 다음의 세 종류의 메모리를 사용한다.



1) 정적/전역

 - 정적(STATIC)으로 선언된 변수들은 정적/전역 메모리에 할당된다. 전역(GLOBAL)변수들 또한 같은 미모레이 할당된다. 정적/전역 변수들은 프로그램이 시작될 때 할당되며 프로그램이 종료될 때까지 메모리 공간에 남아 있다. 모든 함수에서 접그할 수 있는 전역 변수와는 달리 정적 변수의 접근 범위는 해당 변수를 선언한 함수로 제한된다.


2) 자동/로컬

 - 자동 변수는 함수 안에서 선언되고 함수가 호출될 때 생성된다. 자동 변수의 접근 범위는 선언된 함수로 제한되며, 함수가 호출되는 동안에만 존재한다. 일반적으로 블록문 안ㅅ에서 선언된 변수의 범위는 해당 블록을 ㅗ제한된다.


3) 동적

 - 동적메모리는 힙메모리 영역에 할당되고 필요한 경우 해제된다. 포인터를 사용하여 할당된 메모리 영역을 참조하며, 포인터의 의해 접근이 제한된다. 메모리를 해제하지 않는 한 메모리에 존재한다.


 

접근 범위 

수명 

전역(GLOBAL) 

전체파일 

어플리케이션 실행 동안 유지 

정적(STATIC)

선언된 함수 

어플리케이션 실행 동안 유지 

자동(LOCAL) 

선언된 함수 

함수 실행 동안 유지 

동적(DYNAMIC) 

참조하는 포인터 

메모리 해제 전까지 유지 



포인터 변수는 다른 병수 및 객체 또는 함수의 메모리상 주소를 포함하고 있다. 여기서 객채란, malloc 함수 같은 메모리 할당 함삼수를 사용하여 할당된 메모리를 말한다. 포인터는 일반적으로 문자 타입 포인터와 같이 포인터가 가리킬 대상에서 따라 특정 타입을 가지도록 선언되며 정수, 문자, 문자열 또는 구조체와 같은 어떠한 c 데이터 타입도 될 수 있다. 그러나 포인터 자체는 포인터가 참조하는 데이터 타입의 속성을 가지지 않는다. 포인터는 단지 주소만을 가지고 있다.




2. 포인터를 잘 알아야 하는 이유

  • 빠르고 효율적인 코드 작성
  • 다양한 문제에 대한 효과적인 해결 방법 제공
  • 동적 메모리 할당 지원
  • 작고 간결한 표현의 사용
  • 큰 오버헤드 없이 데이터 구조를 포인터로 전달
  • 함수의 매게변수로 전달된 데이터 보호



3. 포인터를 사용할때 발생할 수 있는 문제점

  • 배열이나 데이터 구조의 경계를 넘는 접근
  • 소멸한 자동/로컬 변수의 참조
  • 할당 해제된 힙 메모리의 참조
  • 아직 할당되지 않은 포인터에 대한 역참조


4. 포인터 선언하기
- 포인터 변수는 데이터 타입과 별표 그리고 변수 이름을 순서대로 나열하여 선언한다. 

 int num;
 int *pi;


별표는 변수를 포인터로 선언하는 데도 사용되지만 두 수를 곱하거나 포인터를 역참조하는데 사용 된다. 포인터를 선언하는데 있어 다음과 같은 사항을 기억 해둬야한다. 초기화 하지 않은 메모리에 대한 포인터는 문제가 될 수 있다. 그런 포인터를 역참조하면 포인터가 유효한 주소를 가리지 않을 것이고, 설령 유효한 주소를 가리킨다 하더라도 그 주오세는 유효한 데이터가 없을 가능성이 높다. 유효하지 않은 주소라는 것은 프로그램의 접근이 허용되지 않는 주소를 의미한다. 이러한 유효하지 않은 주소에 대한 접근은 프로그램의 비정상적 종료의 원인이 되는데 수많은 심각한 문제를 일으킬수 있다.
  • pi에는 정수 변수의 주소가 할당되어야 한다.
  • 이 변수들은 초기화되지 않았으므로 쓰레기값을 가진다.
  • 포인터 구현 자체에는 포인터가 어떤 종류의 데이터를 참조하는지 포인터의 내용이 유효한지를 추측할 만한 내용이 포함되어 있지 않다.
  • 그러나 포인터의 타입은 지정되어 있고 포인터가 올바르게 사용되지 않으면 컴파일러는 오류를 표시한다.



5. 포인터 선언을 읽는 방법

 - 포인터 선언을 이해하기 쉽게 읽는 방법의 비결은 뒤에서부터 읽는 것이다. 


 const int *pci;


1) const int *pci;    -> 포인터 변수 pci

2) const int *pci;    -> 정수를 가리키는 포인터 변수 pci

3) const int *pci;    -> 상수 정수를 가리키는 포인터 변수 pci



6. 주소 연산자

- 주소 연산자(&)는 변수의 주소를 반환한다. 아래와 같이 주소연산자를 사용하여 pi 포인터를 num 변수의 주소로 초기화할 수 있다.

 

 int num = 0;

 int *pi = &num;


num변수가 100번지에 위치하고 있다면 포인터 변수 pi가 가리키는 주소는 100번지가 된다.


 num = 0

 pi = num


위와 같은 코드를 컴파일 할경우 컴파일러는 'error: invalid conversion from 'int' to 'int*'와 같은 에러 메세지를 반환할것이다.

변수 pi는 정수형 포인터 변수이고 num은 정수형 변수이다. 결과적으로 num 이갖고 있는 0이라는 값을 정수형 포인터 변수에 초기화 하려고 하는 과정에서 에러가 난다. 두 변수는 같은 크기의 바이트를 사용하지만(4byte) 같은 데이터 타입이 아니다. 그러나 포인터로 캐스팅하는 것은 가능하다.


 pi = (int *)num;


컴파일 하면 오류는 생성되지 않는다. 다만 포인터 변수가 가리키는 0번지는 대부분 운영체제에서 프로그램이 사용할 수 있는 유요한 주소가 아니다. 그러기에 비정상 종료가 될것이다.



7. 포인터값 출력하기

- 위 예제에서 사용한 변수들은 쉬운 설명을 위해 100과 104 같은 주소를 사용하고 있다고 쳤지만 아래의 코드를 실행해보면 다음 과 같은 결과를 볼 수 있다.


int main()

{

int num = 0;

int *pi = &num;


printf("address of num: %d value: %d\n", &num, num);

printf("address of pi: %d value: %d\n", &pi, pi);


    return 0;

}


결과 : 

address of num: 7339564 value: 0

address of pi: 7339552 value: 7339564



8. 간접지정 연산자로 포인터 역참조하기

- 간접지정 연산자(*)는 포인터 변수가 가리키는 위치의 값을 반환하며 '참조 연산자로 포인터 값의 참조'는 종종 포인터 역참조라 불린다. 다음 예제는 변수 num과 pi가 선언되고 초기화 되었다.


 int num = 5;

 int *pi =&num;


그리고 다음 코드에서 변수 num의 값 5를 출력하기 위해 간접지정 연산자를 사용한다.


 printf("%d\n", *pi);


그리고 또한 좌변값에(lvalue)에 참조 연산자의 결과를 사용할 수 있다. 좌변값은 할당 연사자의 좌측에 위치한 피연산자를 말한다. 

 

 *pi = 200;

 printf("%d", num) // 200 출력


포인터 변수 pi가 가리키는 정수(num)에 200을 할당한다. pi는 변수 num을 가리키고 있으며, 200이 변수 num에 할당된다.



9. 함수 포인터

- 포인터는 함수를 가리키도록 선언될 수 있다. 함수포인터 선언은 일반적인 포인터 표기법에 비해 다소 복잡하다. 


 void (*foo)();


이름이 foo이며 매개변수와 반환값이 없는 함수포인터 선언방법을 보여준다.



10. void 포인터

- void 포인터는 어떤 타입의 데이터도 참조할 수 있는 범용 포인터다. 아래의 void 포인터 선언의 예제가 있다.

 

 void *pv;


void 포인터 선언에 두가지 흥미로운 점

  • void 포인터는 char 포인터와 같은 표현과 메모리 정렬 방법을 사용한다.
  • void 포인터는 다른 포인터와 절대 같지 않다. 하지만 NULL 값이 할당된 두 개의 void 포인터는 서로 같다. void 포인터의 실제 동작은 시스템에 따라 다른다.

모든 포인터는 void 포인터에 할당될 수 있으며, 할당된 포인터는 다시 원래의 타입으로 캐스팅하여 사용할 수있다. 원래의 타입으로 캐스팅이일어나면 포인터의 값은 기존 값과 같게된다. 


int main()

{

int num;

int *pi = &num;

printf("value of pi:pp", pi);

void* pv = pi;

pi = (int *)pv;

printf("value of pi:pp", pi);


return 0;

}


결과: 

value of pi:006FF874
value of pi:006FF874

void 포인터는 데이터 타입의 포인터에 사용되며, 함수 포인터에는 사용되지 않는다. void 포인터에 sizeof 연산자가 사용될 수있다. 그러나 void 자체에는 sizeof 연산자를 사용할 수 없다.

size_t size = sizeof(void*);  // 유효함
size_t size = sizeof(void)    // 유효하지 않음



'C' 카테고리의 다른 글

[C언어] 열거형 enum  (0) 2018.08.24
[C언어] 구조체와 공용체 (struct, union)  (0) 2018.08.24
c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (1) 2018.07.14

2018년도 기사 실기 2회차 기사퍼스트 가답안입니다. 


* 알고리즘 (배점 25점)

1. 순서도 (배점 10점)

- 이차원배열 ㄹ: 16년3회 기사기출문제 100% 동일, 16년3회 기출풀이 강의

- C, K, R, TR, -1

 

2. C언어 (배점 5점)

1부터 10까지 숫자들에 대한 약수 구하기, C언어 강의

i % j == 0

 

3. C언어 (배점 4점)

- 5개 입력 주어지고 홀수 갯수 출력, C언어 강의

- !=

 

3. 자바언어 (배점 6점)

- 배열 크기, 자바 강의

- 3, 5

 

* 데이터베이스 (배점 25점)

1. SQL-DCL (배점 10점) 4강

commit, rollback, grant, revoke, cascade

 

2. SQL-속성 추가 (배점 4점) 4강

- alter, add

 

3. 스키마 (배점 3점) 1강

- 내부, 개념, 외부

 

4. 연산자 기호 표시 (배점 8점) 3강

- 합집합 (∪)
- 교집합 (∩)
- 차집합 (-)
- 카티션 프로덕트 (X)
- 셀렉트 (σ)
- 프로젝트 (π)
- 조인 (▷◁)
- 디비젼 (÷)

 

* 신기술 (배점 25점)

1. 용어 (배점 15점) 1강, 시스템 관리 1강, 업무 2강

포렌식

- DDOS: 영문 4글자로 입력 

- 킬스위치 (kill switch): 분실한 정보기기를 원격으로 조작해 개인 데이터를 삭제하고 사용을 막는 일종의 자폭 기능

- Zero day attack(제로데이 공격): 15년3회 산업기사 기출, 2강

BCP

 

2. 용어 (배점 10점) 전산영어 1강, 시스템 관리 2강

- 지그비(zigbee): 집에서 전등을 끄고 켜고 자동으로...

- SAN(storage area network): NAS 와 달리 서버 옆에 있는 고속 네트워크로 서버 간에 정보 교환 가능..서로 다른 저장 장치  

- 크라우드소싱(crowd sourcing): 군중과 아웃소싱의 합성어

- IPV6

- 커넥티드 (스마트 ): 인터넷이 가능한 자동차

 

* 업무프로세스 (배점 15점)

1. 지문속답 & 용어: 07년4회 기사기출문제 (1강)

- AS-IS, 사무국, 서면평가 

 

* 전산영어 (배점 10점)

- GIS: geographic information ... (신기술 기사기출, 시스템 관리2강)
- DHCP: 다이나믹 IP, 아이피 자동 할당 서버이름.... 4글자로 입력
- KERNEL: 시스템 중심..6글자로 입력. (기출, 1강)
- BLUETOOTH (산업기사 기출용어집)
- ALPHAGO: 이세돌 바둑 (시스템 관리1강)



알고리즘      25점

데이터베이스  21점

신기술        14점

업무프로세스  15점

전산영어        2점

총합         77점




몇번의 낙방끝에 가답안 채점을 해보니 합격한거같습니다.

회사다니며 실제로 공부한 시간은 20시간 내외 인데 시험이 너무 쉽게 나왔습니다.

몇번 떨어진 경험치가 축적된건진 모르겠지만 이번엔 서술형이 전혀 없었고 문제 난이도도 최하였던거같네요.



신기술도 나온 문제를 보면 다 한번씩 출제됬던 문제들이 태반이였던점을 보면 이번에는 합격률을 높이고 싶었나 봅니다.

전공자분들이라면 알고리즘은 간단하게 공부하시고

데이터베이스는 깊이있게 공부하시고 신기술과 업무프로세스는 단어암기 위주로 하시는게 효율적인거 같습니다.




아침 7시에 일어날때 시험을 봐야하나 말아야하나 진지하게 고민했는데

오늘 안갔더라면 정말 후회할뻔했네요 ..ㅋㅋ ~_~






소켓  프로그램


프로토콜별 계층 구조

국제표준화기구인 ISO에서는 서로 다른 긱종의 컴퓨터 간에 이루어지는 통신을 위해 OSI 7계층을 규정하고 있다. TCP/I 프로토콜에서는 TCP/IP 4계층 구조를 규정하고 있다. 이와 같이 계층별로 역할을 나누어서 규정하는 이융는 상호 접속에 필요한 통신 규약을 정의하고, 유사 기능을 갖는 모듈을 동일 계층으로 묶기 위해서이다.


OSI 7계층 구조나 TCP/IP 4계층 구조를 설명하기에 앞서 프로토콜 계층 구조를 이해하기 쉽도록 일상의 예를 가지고 설명하고자 한다. 다른 지역의 친구에게 편지를 보내는 경우이다.


1. 사용자 계층

- 사용자 S는 친구에게 보낼 편지를 작성하고 봉투에 넣는다. 겉면에 보내는  사람과 받는 사람의 주소를 적은 후 우편함에 넣는다.

- 이 과정에서 사용자 S는 편지가 어딜 경유해서 목적지에 도착하고, 친구에게 어떻게 전달되는지 알지 못해도된다. 우체국이 역할을 대행해주기 때문


2. 우체국 계층

- 우체국에서는 편지함의 편지를 수거해서 목적지별로 분류하고, 해당 편지를 최종 목적지로 전달하기 위해 다음 경유지를 결정하낟. 예를 들어 사용자 S에게 사용자 D로 편지를 보내는 경우를 살펴보면 사용자 S에서 보낸 우편물은 S눙체국에서 우편물을 분류한다. 그런 다음 목적지에 따라 다음 경유지를 결정하는데 최종 목적지가 D 인 경우 우편물의 다음 경유지는 C1 우체국으로 결정된다.

- 최종 목적지가 D인 우편 봉투에 발신지 S와 다음 경유지 C1을 적은 딱지를 추가로 붙여서 우편 수송차로 보낸다.


3. 우편 수송자 계층

- 우편 수송차에서는 우편 봉투에 무착된 딱지의 착신지, 즉 다음 경유지를 보고 이동을 해서 다음  경유지로 우편물을 전달한다.

- 이 과정에서 최초 발신지가 어딘지, 최종 목적지가 어디인지는 알필요가 없다. 

- 단지 우편 봉투에 추가로 붙은 딱지 정보만을 보고 우편물을 전달한다.


4. 우체국 계층

- 우편물을 받은 C1 우체국에서는 우편 봉투와 발신과 수신정보를 보고 또다심 다음 경유지를 결정한다.


5. 우편 수송차 계층

- 우편봉투에 추가로 붙이는 딱지에는 발신지로 C1과 착신지로 D를 적어 우편 수송차로 보낸다.


6. 우체국 계층

- 우체국 D는 우편 수송차 계층을 통해 우체국 C1에서 받은 우편물을 ㅇ루편봉투의 발신과 수신 정보를 비교한다. 우편물을 사용자에게 전달할 준비를 한다.


7 사용자 계층

- 사용자 D는 사용자 S가 보낸 편지를 최종 우체국인 D를 통해 전달받는다.      


이와 같은 루틴이 통신망에서도 OSI 7 Layer 와 TCP/IP 4 Layer 등의 프로토콜 구조를 제공한다.




OSI 7계층과 TCP/IP 4계층 비교

우편물을 보내는 예에서는 계층을 3개로 나누었다. 그런데 ISO에서는 7개의 수직적인 계층으로 나누고 다른 계층과는 무관하게 저마다 독립적인 기능을 지원하도록 하였다.  OSI 7계층은  최상위 계층인 응용 계층으로 부터 시작해서 표현 계층, 세션계층, 전송 계층, 네트워크 계층, 데이터 링크 계층, 물리 계층으로 구분된다. 가 곅층마다 고유한 서비스를 제공하고 이를 위한 프로토콜이 존재한다.


1. 응용 계층(Application Layer)

- 응용 프로세스를 네트워크에 연결할 수 있게 해서 자료를 송수신할 수 있는 창구를 제공한다. 사용자가 이메일을 전송하고나 웹 브라우저를 통해 웹 서 버에 연결하면 해당 서비스는 응용 계층에서 smtp, pop3, http 등의 프로토콜을 이용해 서비스한다.


2. 표현 계층(Presentation Layer)

-통신하는 컴퓨터간의 데이터 표현의 차이를 해결하기 위해 자료의 형식을 변환해 주거나 공통의 형식을 제공해주는 계층이다. 아스키 코드와 ebcdic 코드의 변환, 그래픽 정보나 영상 정보를 jpeg나 mpeg등으로 변환해서 전송하는 기능을 수행한다. 또한 네트워크 보안을 위해 자료를 암호화해서 전송하고 수시니측에서는 이를 해동하는 기능도 수행한다. 효율적으로 전송하기 위해 자 료를 압축하고 압축을 푸는 기능도 수행한다.


3. 세션 계층(Session Layer)

- 응용 계층 사이에 연결을 설정하고, 유지하고 종료하는 기능을 수행한다. 이를 위해 전송 계층으로 전송할 자료의 순서를 결정하고, 자료의 점검이나 복구를 위해 동기 위치등을 지정한다.


4. 전송 계층(Transport Layer)

- 통신하는 컴퓨터간에 자료를 전송하는 계층이다. 송신측에서는 전송할 데이터를 패킷으로 분할한다. 수신측에서는 분할된 패킷을 다시 조합해서 본래의 자 료로 만들고 상위 계층으로 전달한다. 자료가 수신측에 올바르게 전송될 수 있도록 보장하는 기증도 수행한다.


5.  네트워크 계층(Network Layer)

- 라우팅 프로토콜을 이요해서 최적의 전송 경로를 선택해서 이를 통해 자료를 전송하도록 한다. 이를 위해 IP주소와 같은 논리 주소 체계와, 지리적으로 떨어져 있는 네트워크상의 두 컴퓨터 사이에 최종 목적지까지 정확하게 연결되도록 하는 연결성을 제공한다.


6. 데이터링크 계층(Data Link Layer)

- 물리적인 전송 링크를 통해 자료를 안전하게 전송하는 계층이다. 전송 자료의 비트들을 프레임이라는 놀리 단위로 구성해서 최종 목적지까지 전송하기 위해 인접한 컴퓨터로 자료를 안전하게 전송한다.


7. 물리 계층 (Physical Layer)

- 컴퓨터로 서로 연결하는 물리적인 링크의 활성화/비활성화, 링크 상태를 유지하기 위해 물리적인 링크의 전기적, 기계적, 규약적, 기능적 명세를 정의한다.

- osiy 7계층에 비해 TCP/IP 4계층은 미국 국방성이 개발한 알파넷의 통신 프로토콜이다.

                                                                                                                                                                                                                                                                                                                                         

TCP/IP 4계층에서는 좀더 단순하게 응용계층, 전송 계층, 인터넷 계층 네트워크 액세스 계층으로 규정하고 있다.


1. 응용 계층

- 응용 계층은 OSI 7계층에서 세션, 표현, 응용 계층에 해당한다. 텔넷, FTP, SMTP 등과 같은 TCP와 UDP기반의 응용 프로그램을 구현할 때 사용한다.


2. 전송 계층

- 전송 계층은 SOI 7 계층에서 전송 계층에 해당하며 통신 노드 간의 연결을 제어하고 자료의 송수신을 담당한다. 프로토콜로는 스트림 형태의 연결형 서비스인 TCP와 데이터그램 형태의 비연결형 UDP가 있다.


3. 인터넷 계층

- 인터넷 계층은 OSI 7 계층에서 네트워크 계층에 해당한다. 통신 노드 간의 IP 패킷을 전송하는 기능과 라우팅 기능을 담당한다. 프로토콜로는 IP, ICMP, A RP, RARP가 있다. IP는 데이터그램 방식의 비연결형 서비스만을 제공한다.


4. 네트워크 액세스 계층

- 네트워크 액세스 계층은 OSI 7계층에서 물리 계층과 데이터링크 계층에 해당한다. LAN, X.25, 패킷망, 위성 통신, 다이얼업 모뎀 등에 사용된다. 특히 이더넷에서는 CSMA/CD MAC프로토콜을 사용하며 IEEE 802.3 MAC 표준으로 규정되어 있다.


이렇게 구분된 통신 프로토콜 계층 구조를 기반으로 하여 프로그램으로 각 계층별 자료를 제어하면서 통신할 수 있는 것이다. 소켓 프로그램은 전송 계층에서 제공하는 통신 함수를 호출해서 자료를 송수신한다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

가상함수


가상함수는 virtual 예약어를 앞에 붙여서 선언한 메서드를 말한다. 따라서 가상 함수라는 말은 메서드라는 말을 내포하여, 이 가상 함수는 기본적으로 '자기 부정'을 전제로 작동한다. 달리 말해 파생 형식 에서 메서드를 재정의하면 가거의 정의가 완전히 무시된다는 특징이 있다. java와 비교하자면 자식클래스에서의 부모클래스의 함수를 오버라이드 하는 개념으로 이해했다. 하지만 재정의 이후에 호출하는 과정에서는 자바와는 조금 다른 방식을 취한다.


가상함수는 다음과 같이 선언한다.

virtual void PrintData();


// #include "stdafx.h" #include

using namespace std; class CMyData { public: virtual void PrintData() { cout << "CMyData: " << m_nData << endl; } void TestFunc() { cout << "***TestFunc()***" << endl; PrintData(); cout << "****************" << endl; } protected: int m_nData = 10; }; class CMyDataEx : public CMyData { public: virtual void PrintData() { cout << "CMyDataEx: " << m_nData * 2 << endl; } }; int main() { CMyDataEx a; a.PrintData(); CMyData &b = a; b.PrintData(); a.TestFunc(); }


위의 예제에서 CMyData::PrintData() 함수를 가상 함수로 선언했는데, 이 함수는 내부 멤버 데이터인 m_nData의 값을 출력한다. 그리고 32번 행에서 이 PrintData() 함수를 재정의했다. 여기까지는 기존의 일반 베서드를 재정의한 것과 크게 다르지 않기 때문에 호출하게 되면 CMyDataEx 클래스의 PrintData() 메서드를 호출한게 된다.



CMyData  &b = a;

b.PrintData();

재밌는 코드가 등장하게 되는데 실형식은 CMyDataEx이며 참조 형식은 기본 클래스인 CMyData에 속한 참조자 b가 선언됐다. 그리고 b를 통해 PrintData()를 호출했습니다. 일반 메서드의 경우 실 형식은 중요하지 않고 참조 형식이 무엇인지에 따라 어떤 메 서드가 호출되는지 결정이 났지만 가상함수는 다르다. 가상 함수는 일반 메서와 달리 참조 형식이 무엇이든 실 형식의 메서드를 호출한다.


"일반 메서드는 참조 형식을 따르고, 가상함수는 실 형식을 따른다.



	
void TestFunc()
{
		cout << "***TestFunc()***" << endl;

		PrintData();
		cout << "****************" << endl;
}


PrintData(); 는 누구를 호출할까? TestFunc() 함수는 기본 클래스인 CMyData의 멤버이다. 그러므로 위의 PrintData()는 기본적으로 CMyData 클래스의 PrintData() 함수를 말하는 것이다. 그런데 문제는 PrintData() 함수가 '가상 함수'라는 사실이다. 이렇게 되면 지금 PrintData() 함수는 '미래'에 재정의된 함수일 수 있다.

만일 파생형식(자식)에서 PrintData() 가상 함수를 재정의한다면 위의 PrintData()는 미래의 함수를 호출하는 것이다.


가상함수


가상함수는 virtual 예약어를 앞에 붙여서 선언한 메서드를 말한다. 따라서 가상 함수라는 말은 메서드라는 말을 내포하여, 이 가상 함수는 기본적으로 '자기 부정'을 전제로 작동한다. 달리 말해 파생 형식 에서 메서드를 재정의하면 가거의 정의가 완전히 무시된다는 특징이 있다. java와 비교하자면 자식클래스에서의 부모클래스의 함수를 오버라이드 하는 개념으로 이해했다. 하지만 재정의 이후에 호출하는 과정에서는 자바와는 조금 다른 방식을 취한다.


가상함수는 다음과 같이 선언한다.

virtual void PrintData();


// #include "stdafx.h" #include

using namespace std; class CMyData { public: virtual void PrintData() { cout << "CMyData: " << m_nData << endl; } void TestFunc() { cout << "***TestFunc()***" << endl; PrintData(); cout << "****************" << endl; } protected: int m_nData = 10; }; class CMyDataEx : public CMyData { public: virtual void PrintData() { cout << "CMyDataEx: " << m_nData * 2 << endl; } }; int main() { CMyDataEx a; a.PrintData(); CMyData &b = a; b.PrintData(); a.TestFunc(); }


위의 예제에서 CMyData::PrintData() 함수를 가상 함수로 선언했는데, 이 함수는 내부 멤버 데이터인 m_nData의 값을 출력한다. 그리고 32번 행에서 이 PrintData() 함수를 재정의했다. 여기까지는 기존의 일반 베서드를 재정의한 것과 크게 다르지 않기 때문에 호출하게 되면 CMyDataEx 클래스의 PrintData() 메서드를 호출한게 된다.



CMyData  &b = a;

b.PrintData();

재밌는 코드가 등장하게 되는데 실형식은 CMyDataEx이며 참조 형식은 기본 클래스인 CMyData에 속한 참조자 b가 선언됐다. 그리고 b를 통해 PrintData()를 호출했습니다. 일반 메서드의 경우 실 형식은 중요하지 않고 참조 형식이 무엇인지에 따라 어떤 메 서드가 호출되는지 결정이 났지만 가상함수는 다르다. 가상 함수는 일반 메서와 달리 참조 형식이 무엇이든 실 형식의 메서드를 호출한다.


"일반 메서드는 참조 형식을 따르고, 가상함수는 실 형식을 따른다.



	
void TestFunc()
{
		cout << "***TestFunc()***" << endl;

		PrintData();
		cout << "****************" << endl;
}


PrintData(); 는 누구를 호출할까? TestFunc() 함수는 기본 클래스인 CMyData의 멤버이다. 그러므로 위의 PrintData()는 기본적으로 CMyData 클래스의 PrintData() 함수를 말하는 것이다. 그런데 문제는 PrintData() 함수가 '가상 함수'라는 사실이다. 이렇게 되면 지금 PrintData() 함수는 '미래'에 재정의된 함수일 수 있다.

만일 파생형식(자식)에서 PrintData() 가상 함수를 재정의한다면 위의 PrintData()는 미래의 함수를 호출하는 것이다.



------------- 실행 화면 -------------


CMyDataEx: 20

CMyDataEx: 20

***TestFunc()***

CMyDataEx: 20

****************

계속하려면 아무 키나 누르십시오 . . .

+ Recent posts