waitpid 함수는 wait 함수처럼 자식 프로세스를 기다릴때 사용하는 함수입니다. 즉, 자식 프로세스의 종료상태를 회수할 때 사용합니다.

하지만 waitpid 함수는 자식 프로세스가 종료될 때 까지 차단되는 것을 원하지 않을 경우, 옵션을 사용하여 차단을 방지할 수 있습니다.

그리고 기다릴 자식 프로세스를 좀더 상세히 지정할 수 있습니다.


#include <sys/wait.h>

 pid_t waitpid(pid_t pid, int *statloc , int options);

 성공 : 프로세스 ID 반환

 오류 : -1 



waitpid 함수의 첫번째 인자에 대해서 알아 보겠습니다.

 wiatpid 함수의 첫 번째 인자

 의미

  pid가 -1 일 경우  (pid ==  -1)

   임의의 자식 프로세스를 기다림

  pid가 0 보다 클 경우 (pid > 0)

   프로세스 ID가 pid인 자식 프로세스를 기다림

  pid가 -1 보다 작을 경우 (pid < -1)

  프로세스 그룹 ID가 pid의 절댓값과 같은 자식 프로세스를 기다림

 pid가 0일 경우 (pid == 0)

 waitpid를 호출한 프로세스의 프로세스 그룹 PID와 같은 프로세스 그룹 ID를 가진 프로세스를 기다림 

waitpid 함수의 오류(-1)는 지정한 pid의 프로세스 또는 프로세스 그룹이 없는 경우에 발생하며 그리고 pid가 자식 프로세스가 아닐 때 발생합니다.


두번째 인자에 대해서 알아 보겠습니다.

 

 waitpid 함수 반환 값

 두 번째 인자 인 statloc 값

 자식 프로세스가 정상적으로 종료

 프로세스 ID

 - WIFEXITED(statloc) 매크로가 true를 반환

 - 하위 8비트를 참조하여 자식 프로세스가 exit, _exit, _Exit에 넘겨준 인자값을 얻을 수 있음, WEXITSTATUS(statloc)

  자식 프로세스가 비정상적으로 종료

 프로세스 ID

 - WIFSIGNALED(statloc) 매크로가 true를 반환

 - 비정상 종료 이유를 WTERMSIG(statloc) 매크로를 사용하여 구할 수 있음

 waitpid 함수 오류  -1

 - ECHILD : 호출자의 자식 프로세스가 없는 경우

 - EINTR : 시스템 콜이 인터럽트 되었을 때


세번째 인자에 대해서 알아 보겠습니다.

 세 번째 인자로 사용가능한 상수

 의미 

 WCONTINUED

 중단 되었다가 재개된 자식 프로세스의 상태를 받음

 WNOHANG 

 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수 할 수 없는 상황에서 호출자는 차단되지 않고 반환값으로 0을 받음

 WUNTRACED 

 중단된 자식 프로세스의 상태를 받음


아래 예제는 세 번째 인자에 아무것도 주지 않았을 때 wait 함수와 동일한 동작하는 것을 확인해 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,0); // 세번째 인자에 0을 넣었으므로 wait 함수와 동일한 동작을 함 printf("부모 종료 %d %d %d\n",ret,WIFEXITED(status),WEXITSTATUS(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }


실행 결과를 보겠습니다.

부모 PID : 13444, pid : 13445

자식 시작 PID : 13445

자식 종료

부모 종료 13445 1 0 

부모 프로세스는 3초 동안 동작하고 자식 프로세스는 8초 동안 동작하는 예제 코드입니다. 하지만 결과는 자식 프로세스가 먼저 종료되고 부모 프로세스가 종료되었습니다.

waitpid 함수는 세번째 인자로 0을 주었기 때문에 자식 프로세스가 종료될 때 까지 blocking 되었기 때문입니다.

WIFEXITED 매크로는 1(true)를 리턴하였으며 WEXITSTATUS는 자식 프로세스가 0을 리턴 한 것을 알려주고 있습니다.


다음 예제는 세 번째 인자에 WNOHANG 옵션을 넣어보도록 하겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,WNOHANG); // WNOHANG 옵션을 사용하면 자식 프로세스가 종료되지 않았을 경우 blocking 되지 않고 즉시 0값을 리턴합니다. printf("부모 종료 %d %d\n",ret,WIFEXITED(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 확인해 보겠습니다.

부모 PID : 14289, pid : 14290

자식 시작 PID : 14290

부모 종료 0 0 127

자식 종료 

waitpid 함수는 0을 리턴하였으며 WIFEXITED 매크로도 false를 리턴하였습니다. WNOHANG 옵션을 사용하였기 때문에 자식 프로세스가 종료될 때 까지 기다리지 않고 waitpid 함수가 바로 0값을 리턴하였기 때문입니다.


마지막으로 존재하지 않는 자식 프로세스의 PID를 waitpid 첫 번째 인자로 넣어 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/error.h> #include <sys/string.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(99999,&status,WNOHANG); // pid로 99999(존재하지 않는 프로세스 ID) printf("부모 종료 %d %d %s\n",ret,errno,strerror(errno)); // 오류가 발생하였을 경우 errno값을 확인 exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 보도록 하겠습니다.

부모 PID : 17859, pid : 17860

자식 시작 PID : 17860

부모 종료 -1 10 No child processes

자식 종료 

waitpid 함수는 -1 오류 값을 반환하였습니다. 그리고 에러 넘버(errno) 10, 즉 NO child processes라는 에러를 출력하였습니다. 

99999라는 자식 프로세스가 존재하지 않는다는 의미입니다. 그 이유는 waitpid 함수의 첫 번째 인자로 존재하지 않는 99999 PID값을 입력하였기 때문입니다.


지금까지 wait 함수보다 좀더 다양한 기능을 제공하는 waitpid 함수에 대해서 알아 보았습니다.

이번 포스팅에서는 고아 프로세스와 좀비 프로세스에 대해서 알아 보도록 하겠습니다.


고아 프로세스와 좀비 프로세스 정의는 아래와 같습니다.

부모 프로세스가 자식 프로세스보다 먼저 종료되면 자식 프로세스는 고아프로세스가 됩니다.

그리고 자식 프로세스가 종료되었지만 부모 프로세스가 자식 프로세스의 종료 상태를 회수하지 않았을 경우에 자식 프로세스를 좀비 프로세스라고 합니다.


1. 고아 프로세스

부모 프로세스가 자식 프로세스보다 먼저 종료되면 init 프로세스가 자식 프로세스 새로운 부모 프로세스가 됩니다.

종료되는 프로세스가 발생할 때 커널은 이 프로세스가 누구의 부모 프로세스인지 확인한 후, 커널이 자식 프로세스의 부모 프로세스 ID를 1(init 프로세스)로 바꿔 줍니다.

간단한 예제를 통해서 고아 프로세스의 부모 프로세스가 init 프로세스로 바뀌는지 확인해 보도록 하겠습니다.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    
    pid_t childPid;
    int i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(2);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작\n");
        
        for(i=0;i<10;i++) {
            printf("자식 PID : %ld 부모 PID : %ld\n",(long)getpid(), (long)getppid());
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}



실행 결과는 아래와 같습니다.

 

자식 프로세스의 원래 부모 프로세스는 PID 46797 였으나 부모 프로세스가 종료 된 후에는 1로 바뀐 것을 볼 수 있습니다.

init 프로세스는 유닉스 계열의 운영체제에서 부팅 과정 중 생성되는 최초의 프로세스이며 시스템이 종료될때까지 계속 살아있는 데몬 프로세스입니다.

그리고 init 프로세스의 PID는 일반적으로 1입니다. 

고아 프로세스가 작업을 종료하면 init 프로세스가 wait함수를 호출하여 고아 프로세스의 종료 상태를 회수함으로써 좀비 프로세스가 되는것을 방지합니다.


2. 좀비 프로세스

반대로 자식 프로세스가 부모 프로세스 보다 먼저 종료되는 경우가 있습니다. 

자식 프로세스가 exit 시스템 콜을 호출 하면서 종료되면 이 프로세스에 관련된 모든 메모리와 리소스가 해제되어 다른 프로세스에서 사용할 수 있게 됩니다.

자식 프로세스가 종료된 이후에 부모 프로세스가 자식 프로세스의 상태를 알고 싶을 수 있기 때문에 커널은 자식 프로세스가 종료되더라도 최소한의 정보(프로세스 ID, 프로세스 종료 상태 등)를 가지고 있게 됩니다.

부모 프로세스가 좀비 프로세스의 종료상태를 회수하게 되면(wait 시스템콜을 호출을 통하여)  좀비 프로세스는 제거됩니다.

간단한 예제 코드를 보도록 하겠습니다.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main() {
    
    pid_t childPid;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(30);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작 PID : %ld\n", (long)getpid());
        sleep(1);
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


터미널 창에 아래와 같이 명령어를 입력합니다.

ps aux | grep 'Z' 명령어를 사용하여 좀비 프로세스를 출력합니다.


좀비 프로세스가 쌓이게 되면 리소스의 유출을 야기할 수 있기 때문에 좀비 프로세스 상태를 오래 유지되지 않도록 부모프로세스는 wait 시스템 콜 함수를 사용하여 자식 프로세스의 종료 상태를 읽어들이는 것이 필요합니다.

부모 프로세스가 fork() 함수를 사용하여 자식 프로세스를 생성하였을 때, fork() 함수가 리턴되는 시점부터 2개의 프로세스가 동작하게 됩니다.

부모 프로세스가 자식 프로세스의 종료 상태를 얻기 위해서는 wait() 함수를 사용합니다.

다른 말로 표현하면 wait() 함수를 사용하여 자식 프로세스가 종료 될 때까지 기다릴 수 있습니다.


wait() 함수는 아래와 같이 동작합니다.

1. 자식 프로세스가 동작 중이면 호출 차단이 차단되기 때문에 상태를 얻어올 때까지 대기

2. wait() 함수 호출자가 시그널을 받을 때까지 대기

3. 자식 프로세스가 종료된 상태라면 즉시 호출이 반환되어 상태를 얻음, 이 때 wait() 함수는 자식 프로세스의 프로세스 ID를 반환

4. 자식 프로세스가 없다면 호출이 즉시 반환되며, 에러값을 반환


 #include <sys/wait.h>

 pid_t wait(int *statloc);

 성공 : 프로세스 ID 반환

 오류 : -1


자식 프로세스가 정상 종료되었을 때와 비정상 종료 되었을 때, wait() 함수의 반환값과 statloc 인자값이 다르게 채워집니다.


 

 wait 함수 반환 값

 statloc  값

 자식 프로세스가 정상적으로 종료

 프로세스 ID

 - WIFEXITED(statloc) 매크로가 true를 반환

 - 하위 8비트를 참조하여 자식 프로세스가 exit, _exit, _Exit에 넘겨준 인자값을 얻을 수 있음, WEXITSTATUS(statloc)

  자식 프로세스가 비정상적으로 종료

 프로세스 ID

 - WIFSIGNALED(statloc) 매크로가 true를 반환

 - 비정상 종료 이유를 WTERMSIG(statloc) 매크로를 사용하여 구할 수 있음

 wait 함수 오류  -1

 - ECHILD : 호출자의 자식 프로세스가 없는 경우

 - EINTR : 시스템 콜이 인터럽트 되었을 때


예제를 통하여 살펴 보도록 하겠습니다.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        waitPid = wait(&status);
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 프로세스
        printf("자식 PID : %ld \n",(long)getpid());

        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


fork를 한 후에, 부모 프로세스는 5초간 sleep을 하였기 때문에 wait 함수를 호출하기 전에 자식 프로세스는 종료된 상태입니다.

따라서  wait 함수를 호출하면 자식 프로세스의 상태를 즉시 반환 받을 수 있습니다.


실행 결과를 보겠습니다.

자식 프로세스는 정상 종료 되었으며 WEXISTSTATUS(status) 매크로를 사용하여 자식 프로세스가 반환한 0값을 얻어왔습니다.

부모 PID : 34989, pid : 34999 0 

자식 PID : 34999 

자식 종료

wait : 자식 프로세스 정상 종료 0

부모 종료 34999 0


이번에는 자식 프로세스에 sleep 함수로 지연을 발생시켜서 부모 프로세스가 wait() 함수를 호출하였을 때 자식 프로세스가 종료될 때까지 대기하도록 만들어 보겠습니다.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        waitPid = wait(&status);
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


자식 프로세스가 5초 동안 동작하는 동안 부모 프로세스는 wait() 함수를 호출하여 자식 프로세스의 종료 상태를 얻기 위해 대기합니다.

실행 결과를 살펴 보도록 하겠습니다.

기대했던 것과 다르게 wait함수는 -1를 반환하였습니다. 에러의 원인을 찾기 위해 errno 출력과 perror 함수를 이용하였습니다.

부모 PID : 35650, pid : 35660 0 

자식 PID : 35660 

자식 종료

에러 넘버 : 4 

wait 함수 오류 반환: Interrupted system call

부모 종료 -1 126 

"Interrupted system call" 에러 문구가 wait 함수가 성공하지 못한 이유를 알려주고 있습니다.

Advanced Programming in the UNIX Environment 도서에 10.5 절에 나와 있듯이 유닉스 계열의 시스템에서는 '느린' 시스템 호출에 의해 차단되어 있는 도중에 신호를 잡으며 그 시스템호출이 가로채입니다. 이런 경우 시스템 호출 함수는 errno를 EINTR로 설정하고 오류를 반환합니다.

wait() 함수는 시스템 콜 함수이며  '느린' 시스템 콜 함수에 속합니다.


이 문제를 해결하기 위해서는 오류 반환을 검사하여 오류가 발생한 시스템 콜 함수를 다시 호출하는 방법을 사용합니다.

"Interrupted system call" 오류가 발생하더라도 wait함수를 재실행할 수 있도록 아래와 같이 코드를 수정합니다.

while문을 사용하여 wait 함수가 -1 값을 반환하고 errno 값이 EINTR일 경우에 wait함수를 재실행하도록 코드를 추가하였습니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        while((((waitPid = wait(&status)) == -1) && errno == EINTR));  // 에러 발생시 명시적으로 처리하고 wait 함수를 재호출함
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


실행 결과를 보도록 하겠습니다.

부모 프로세스는 자식 프로세스가 5초 동안 실행되는 동안  wait 함수로 대기하였다가 자식 프로세스가 종료되는 순간 정상적으로 종료 상태를 얻어 왔습니다.

즉 자식 프로세스가 반환한 값 0을 얻은것을 확인 할 수 있습니다.

부모 PID : 37460, pid : 37470 0 

자식 PID : 37470 

자식 종료

wait : 자식 프로세스 정상 종료 0

부모 종료 37470 0 


마지막으로 자식 프로세스를 kill 명령어로 종료시켜 보도록 하겠습니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        while((((waitPid = wait(&status)) == -1) && errno == EINTR));
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<100;i++) { // 충분히 긴 시간 동안 자식 프로세스를 동작 시키놓고 kill 명령어로 종료시켜 봅니다
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


예제 코드를 실행 한 후,

자식 프로세스는 100초 동안 동작을 할 것입니다.

그리고 부모 프로세스는 wait() 함수를 통해 자식 프로세스의 종료 상태를 얻기위해 대기 중입니다.

부모 PID : 40272, pid : 40275 0 

자식 PID : 40275  


100초가 지나기 전에 터미널에서 아래와 같이 명령어를 입력하도록 하겠습니다.

40275는 자식 프로세스의 프로세스 ID입니다.

kill -9 40275 

위 명령어를 입력하는 부모 프로세스에서 호출한 wait()함수는 반환되면서 아래의 정보를 출력하게 됩니다.

부모 PID : 40272, pid : 40275 0 

자식 PID : 40275 

wait : 자식 프로세스 비정상 종료 9

부모 종료 40275 9 

자식 프로세스는 동작중에 kill 명령어에 의해서 비정상 종료 되었고 시그널 값은 9을 전달 받았습니다.


지금까지 wait함수를 사용하여 부모 프로세스가 자식 프로세스의 종료 상태(정상 또는 비정상)를 얻는 과정을 살펴 보았습니다.

그리고 시스템 콜 함수를 사용할 때 발생하는 오류에 대해서도 알아 보았습니다.

일반적으로 프로세스를 종료할 때 return문을 사용합니다. 그리고 exit()함수를 사용하기도 합니다.

return 문과 exit() 함수를 사용하는 것은 시스템 입장에서 보았을 때 동일합니다.


#include <stdlib.h>

void exit(int status); 


int main()
{
    printf("This is test code\n");

    return 0;  // exit(0);과 동일
}


exit() 함수 외에도 _exit() 함수와 _Exit() 함수가 있습니다. 이 둘 함수가 exit() 함수와 다른점은 exit()함수는 atexit() 함수로 등록한 종료 핸들러가 있다면 이 핸들러를 모두 처리하고, 표준 입출력 스트림을 닫는 작업을 수행한 후 커널의 종료 작업을 실행하지만 _exit(), _Exit() 함수는 바로 커널에서 종료작업을 실행한다는 것입니다.

커널 종료 작업이라고 하면 프로세스가 사용하던 메모리를 해제하고 열어놓았던 파일의 descriptor등 을 닫는 작업 등 을 말합니다.

유닉스 계열 시스템에서 _exit() 함수와 _Exit() 함수는 같은 함수라고 생각해도 좋습니다.


#include <stdlib.h>

void _Exit(int status);

#include <unistd.h>

void _exit(int status); 


return문을 사용하던 exit(), _exit(), _Exit() 함수를 사용하던 프로세스의 종료 상태를 함수의 인자로 지정하게 됩니다. 

이 인자는 종료 상태를 부모 프로세스에게 알려줄 수 있는 값입니다.


아래 예제에서 사용된 exit() 함수에 들어 있는 인자 -1과 0 값은 부모 프로세스에게 종료 상태를 알려줄 수 있는 값이 됩니다.

부모 프로세스는 자식 프로세스의 종료 상태 값을 얻어서 자식 프로세스가 어떤 상태로 종료 되었는지를 알 수 있는 것입니다. 

#include <stdio.h>
#include <stdlib.h>

float divide(int a, int b)
{
    if(b == 0) {  
        return -1; // 0으로 나누는 것은 에러!
    }
    
    return a/b;
}

int main()
{
    float ret = 0;
    
    ret = divide(10,5);
    if(ret == -1) { 
        printf("Error!\n");
        exit(-1); // 0으로 나누기 시도
    }
    
    printf("ret : = %f\n", ret);
    
    ret = divide(10,0);
    if(ret == -1) {  
        printf("Error!\n");
        exit(-1); // 0으로 나누기 시도
    }
    
    printf("ret : = %f\n", ret);

    exit(0);
}


하지만 자식 프로세스가 비정상적으로 종료되었을 경우에는 커널에서 비정상 종료 상태를 별도로 설정하게 됩니다.

부모 프로세스는 자식의 종료 상태(정상/비정상)를 wait() 함수 또는 waitpid() 함수로 얻을 수 있습니다.


wait() 함수 포스팅에서 자식 프로세스가 정상/비정상적으로 종료되었을 때 부모 프로세스가 어떻게 상태를 얻어 오는지 좀더 자세히 살펴 보도록 하겠습니다.


 

Kotlin에서는 Range expression 을 통해서 개발자들이 편하게 사용할 수 있도록 해줍니다.

 

가장 흔하게 if 문이나 for문에서 사용될 수 있습니다.

".." operator 를 갖는 rangeTo 함수로 표현합니다.

아래는 C / JAVA 언어의 if (i>=1 && i<= 10) 조건문과 같은 의미를 가집니다.

if (i in 1..10) { // equivalent of 1 <= i && i <= 10
    println(i)
}

아래와 같이 반복문에서도 사용합니다

이때 1..4 와 같이 커지는 순서로 for문을 동작 시키게 되는데요

아래와 같이 4..1 이 되면 아무것도 수행되지 않게 됩니다.

for (i in 1..4) print(i) // prints "1234"

for (i in 4..1) print(i) // prints nothing

그렇다면 C언어에서처럼 i값을 점점 작아지게 하고 싶으면 어떻게 하면 될까요?

C언어에서 for (int i = 4 ; i > 0 ; i --) 와 같이 사용되는 반복문을

Kotlin에서는 downTo() 함수를 통해 아래와 같이 표현할 수 있습니다.

for (i in 4 downTo 1) print(i) // prints "4321"

 

또한 step을 사용하여 일정한 간격으로 i를 증가시킬 수도 있고,

downTo와 step을 함께 사용하여 역순으로 일정간격으로 i를 감소시킬 수도 있습니다.

첫번째 for문은 1부터 4까지 2씩 건너뛰므로, 1과 3이 출력됩니다.

두번째 for문은 4부터 1까지 2씩 건너뛰므로, 4와 2가 출력됩니다.

for (i in 1..4 step 2) print(i) // prints "13"

for (i in 4 downTo 1 step 2) print(i) // prints "42"

 

until 함수를 사용하여 범위를 결정할 수도 있습니다.

(아래의 식에서 i는 1에서 9까지만 수행합니다. 10은 포함되지 않습니다.)

C언어에서보다는 쉽고 다양하게 표현할 수 있지만,

처음 접한다면 익숙한 C언어의 표현을 바꿔서 하기에 좀 불편하기도 할 것 같아요.

for (i in 1 until 10) { // i in [1, 10), 10 is excluded
     println(i)
}

 

마지막으로 last 함수를 사용하여 마지막 i 값을 확인할 수 있습니다.

(1..12 step 2).last == 11  // progression with values [1, 3, 5, 7, 9, 11]
(1..12 step 3).last == 10  // progression with values [1, 4, 7, 10]
(1..12 step 4).last == 9   // progression with values [1, 5, 9]

 

 

 

프로세스를 생성하고자 할 때 fork 함수를 사용하면 됩니다.

fork 함수를 호출하는 프로세스는 부모 프로세스가 되고 새롭게 생성되는 프로세스는 자식 프로세스가 됩니다.

fork 함수에 의해 생성된 자식 프로세스는 부모 프로세스의 메모리를 그대로 복사하여 가지게 됩니다.

그리고 fork 함수 호출 이후 코드부터 각자의 메모리를 사용하여 실행됩니다


fork 함수를 사용하기 위해서는 unistd.h를 include하면 됩니다.

fork 함수는 unistd.h 파일에 system call로 정의되어 있습니다.

// unistd.h header file

pid_t fork(void); // 성공 시 : 부모 프로세스에서는 자식 프로세스의 PID값을 반환 받음

// 자식 프로세스에서는 0 값을 반환 받음

// 실패 시 : 음수 값(-1) 반환


간단한 예제 코드를 살펴 보겠습니다.

#include <stdio.h>
#include <unistd.h>

int main() {
    int x;
    x = 0;
    
    fork();
    
    x = 1;
    printf("PID : %ld,  x : %d\n",getpid(), x);
    
    return 0;
}


실행 결과는 다음과 같습니다.

fork 함수 코드 이후부터는 부모 프로세스와 자식 프로세스가 각자의 x = 1, printf() 코드를 실행하였습니다.

그렇기 때문에 PID 43889(부모 프로세스)에서 x 값은 1을 출력하였고 PID 43895(자식 프로세스)에서도 x 값은 1이라고 출력하였습니다.

PID : 43889,  x : 1

PID : 43895,  x : 1 


그림을 통하여 다시 한번 보도록 하겠습니다.

fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

fork 함수 실행 이후 , 부모와 자식 프로세스는 동일한 코드 (x = 1, printf())를 각자 메모리상에서 실행하고 있습니다.


이번에는 fork 함수의 리턴값을 검사하여 부모와 자식 코드를 각각 분리하도록 하겠습니다.

fork 함수는 부모 프로세스에게는 자식프로세스의 PID를 반환하며 자식 프로세스에게는 0을 반환합니다.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    
    pid_t pid;
    
    int x;
    x = 0;
    
    pid = fork();
    
    if(pid > 0) {  // 부모 코드
        x = 1;
        printf("부모 PID : %ld,  x : %d , pid : %d\n",(long)getpid(), x, pid);
    }
    else if(pid == 0){  // 자식 코드
        x = 2;
        printf("자식 PID : %ld,  x : %d\n",(long)getpid(), x);
    }
    else {  // fork 실패
        printf("fork Fail! \n");
        return -1;
    }
    
    return 0;

}


실행 결과는 다음과 같습니다. 

pid 반환값을 검사하여 부모 프로세스와 자식 프로세스에서 실행될 코드를 별도로 작성할 수 있는것을 확인 하였습니다.

부모 PID : 46834,  x : 1 , pid : 46838

자식 PID : 46838,  x : 2 


fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

하지만 fork 함수 이후의 코드가 pid값을 기준으로 분리되어 있습니다.

따라서 각 프로세스의 메모리 공간의 x값은 서로 달라지게 됩니다. 


지금까지 fork 함수 사용에 대해서 살펴 보았습니다.

BeautifulSoup 은 HTML 및 XML 파일에서 원하는 데이터를 손쉽게 Parsing 할 수 있는 Python 라이브러리 입니다.

오늘은 Beautiful Soup 라이브러리를 활용하여 HTML 코드 안에서 원하는 Data 를 뽑아내는 예제를 다뤄보려고 합니다.

 

1. Beautiful Soup 설치하기

Beautiful Soup 라이브러리는 Python 에서 기본적으로 제공하는 라이브러리에 해당하지 않기 때문에 별도의 설치가 필요합니다.

Windows 에서는 cmd 창에서 간단하게 pip 명령으로 설치할 수 있습니다.

- pip install beautifulsoup4


pip 는 Python 설치 디렉토리의 Scripts 폴더 밑에 있기 때문에, 해당 위치로 이동하여 명령어를 실행해 주어야 합니다.

설치가 완료되면 Python 설치 디렉토리 밑의 Lib/site-packages/ 위치에 bs4 디렉토리가 생성된 것을 확인 할 수 있습니다.

 

2. HTML 에서 원하는 정보 추출하기 예제

먼저 BeautifulSoup 라이브러리 사용을 위해 bs4 를 import 합니다.

from bs4 import BeautifulSoup

 예제에 사용될 HTML 문서는 다음과 같습니다.

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

문서를 Parsing 하기 위해서 BeautifulSoup 생성자에 해당 문서를 인자로 전달 해주어야 합니다.

아래와 같이 문자 열을 전달할 수도 있고, file handle 을 전달 할 수도 있습니다.

from bs4 import BeautifulSoup

with open("index.html") as fp:
    soup = BeautifulSoup(fp)

soup = BeautifulSoup("<html>data</html>")

위의 html_doc 문자열을 전달하여 BeautifulSoup 객체를 생성 합니다.

그렇게 생성한 soup 객체를 통해 HTML Parsing 을 위해 제공되는 BeautifulSoup 라이브러리의 여러가지 API 들을 사용해 보겠습니다.

if __name__  == "__main__":
    soup = BeautifulSoup(html_doc, 'html.parser')
    print('1. ', soup.title)
    print('2. ', soup.title.name)
    print('3. ', soup.title.string)
    print('4. ', soup.title.parent.name)
    print('5. ', soup.p)
    print('6. ', soup.p['class'])
    print('7. ', soup.a)
    print('8. ', soup.find_all('a'))
    print('9. ', soup.find(id="link3"))

 

1번 부터 9번까지 파싱 결과를 살펴보겠습니다.

원하는 tag 를 가지고 오고 싶을 때 1번과 5번 경우처럼 간단하게 soup.(원하는tag 명) 을 사용할 수 있습니다.

특정 tag 로 감싸진 내용만 가져오고 싶다면 3번과 같이 soup.title.string 을 사용하면 됩니다.

.(tag 명)의 경우 해당 태그를 포함하여 그 태그가 끝날 까지의 문장을 가지고 오고, .name, .string, .parent.name 등을 통해 더 자세한 정보들을 얻어 올 수 있습니다.

6번의 경우 soup.p['class'] 라고 하면 tag 가 p 인 것들 중 속성이 class 인 부분을 파싱 합니다.

7번과 8번의 다른점은 soup.a 의 경우 html 중 tag 가 a 인 첫번째 항목을 뽑아내지만,  find_all 을 이용하면 tag 가 a 인 것들을 모두 리스트의 형태로 뽑아 낼 수 있습니다.

 

3. 네이버 영화 랭킹 페이지에서 영화 목록 가져오기

이번에는 자주 들어가는 Naver 포탈 사이트에서 제공하는 영화 목록을 가져와 보도록 하겠습니다.

먼저 Parsing 할 주소는 http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20180117 입니다.

페이지에 들어가 보면 평점이 가장 높은 영화부터 쭉 리스팅이 되어있습니다.  많은 정보들 중에 영화의 제목만 파싱해 오도록 하겠습니다.

 

먼저 BeautifulSoup 을 하나 생성하고, 인자로 네이버 영화 랭킹 페이지를 전달 합니다.

soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')

 

해당 페이지가 어떤 tag 들로 어떻게 구성되어있는지 보고싶다면,

print(soup.prettify())

를 이용할 수도 있지만, Chrome 에서 제공하는 개발자도구를 이용하면 더 편리합니다. (도구더보기 -> 개발자도구)

html 코드로 보면 아래와 같이 영화 제목은 'div' tag 의 'tit5'를 속성으로 가진 항목들에 포함되어 있습니다.

<div class="tit5"> <a href="/movie/bi/mi/basic.nhn?code=17421" title="쇼생크 탈출"> 쇼생크 탈출 </a> </div>

그래서 생성한 BeautifulSoup 객체의 find_all 함수를 이용해서 영화 제목이 담긴 div tag 를 가진 항목을 list 로 뽑아 내었습니다.

if __name__  == "__main__":
    soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')
    res = soup.find_all('div', 'tit5')
    print(res)

 

위 코드에서 res 를 출력해보면, 의도했던대로 모든 div tag 항목이 저장되어 있는 것을 확인 할 수 있습니다.

위와 같이 1차적으로 Parsing 이 되었고, 완벽하게 영화 제목만 가져오기 위해 다시한번 파싱을 해줍니다.

동일 Tag 로 잘 분리가 되어있다면, get_text() 를 이용해 해당 tag 에서 text 정보면 가져올 수 있습니다.

그래서 최종적으로 완성된 코드는 아래와 같이 간단합니다.

if __name__  == "__main__":
    soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')
    res = soup.find_all('div', 'tit5')

    for n in res:
        print(n.get_text())

결과를 출력해 보면 원하는 데로 제목만 잘 출력이 되었습니다. ^^

결과적으로, 네이버 페이지에 있는 많은 영화 제목들을 단 몇줄로 간단하게 출력해본 예제입니다.

예제는 네이버 페이지이지만, 다양한 사이트에서 원하는 정보를 뽑아낼 때 활용할 수 있겠습니다.

 

 

이번 포스팅에서는 Kotline 의 기본 제어문(반복문/조건문) 사용법을 알아보도록 하겠습니다.

모든 언어들이 비슷하기 때문에 하나의 언어만 잘 알아두면 나머지 언어의 문법도 금방금방 익힐 수 있는 것 같습니다.

Kotline 의 제어문도 다른 언어들과 비슷 하지만, 함축적으로 사용되는 부분이 있어 다른 언어들과는 다르게 Kotline 의 문법을 모르면 잘 알아보기가 힘들게 되어 있는 부분도 있습니다.

익숙해 지면 다른 언어들보다 편할것이라는 생각도 드네요. ^^

 

1. If 문

Kotline 에서 if 문은 Expression 입니다. 즉, value 를 return 합니다. if 문 자체로 기존의 3항 연산자의 역할을 대체하기 때문에 더이상 삼항 연산자는 사용되지 않습니다. (조건 ? true : false)

기존 우리는 if 문을 아래와 같이 사용했습니다.

max 에 a 변수를 넣고, 만약 a 보다 b 가 크다면 max 변수에 b 의 값을 넣는 코드입니다.

// Traditional usage 
var max = a 
if (a < b) max = b

// With else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}

 Kotline 에서는 위의 if 문을 아래와 같이 간단하게 사용할 수 있습니다.

// As expression 
val max = if (a > b) a else b

즉, " if (a>b) a else b " 구문 자체가 a 혹은 b 의 값을 return 하기 때문에, max 변수에 바로 이 값을 대입할 수 있습니다.

만약, if 문 자체에 블럭이 포함되어야 한다면 위 구문은 아래와 같이 사용할 수 있습니다.

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

위의 경우 블럭의 끝에 쓰여진 값이 return 되는 값이 됩니다.

 

2. When

When 은 일반 언어에서 사용되던 Switch 문을 대체합니다.

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // Note the block
        print("x is neither 1 nor 2")
    }
}

When 옆에 쓰여진 x 에 값이, 블럭 안의 조건에 충족이 될 때 까지 모든 인수를 순차적으로 검사 합니다.

아무 조건에도 충족되지 않으면 else 분기문으로 들어가게 되고, Switch 문의 default 문은 필수가 아니지만, When 의 else 문은 필수로 들어가야 합니다.

많은 경우가 동일한 방식으로 처리되어야 하는 경우에는 . (콤마) 를 사용하여 조건을 추가할 수 있습니다.

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

각 조건의 경우에는 위와 같이 특정 상수 값 (0, 1) 이 올 수도 있지만, 함수가 올 수도 있습니다.

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

위의 경우에는 x 와 parseInt() 에서 return 되는 값과 일치하게 되면 "s encodes x" 를 출력할 것입니다.

그리고 in 키워드를 사용하여 특정 값의 범위를 지정 할 수도 있습니다.

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

in 1..10 은 1<= x <=10 의 범위를 의미 합니다. Range 에 대해서는 'https://kotlinlang.org/docs/reference/ranges.html' 를 참고해 보세요 .

마지막으로 when 을 사용할 때 인수를 생략할 수 있습니다.

인수를 생략하게 되면 분기 조건은 단순히 bool 식이 되고, 해당 조건이 참일 때 분기문이 실행 됩니다.

아래의 경우에서는 x 가 홀수 일 경우 "x is odd", 짝수일 경우 'x is even" 둘다 아닐 경우에 "x is funny" 가 출력 됩니다.

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

 

3. For Loops

for loop 는 비교적 기존에 사용하던 for 문과 크게 다르지 않습니다. iterator 를 제공하는 모든 것들을 반복할 수 있습니다.

아래 예제에서는 item 에 collection 에 있는 모든 값들이 하나씩 대입이 되면서 반복 하게 됩니다.

for (item in collection) print(item)

배열이나 리스트를 반복할 경우에 index 를 사용하고 싶다면 indices를 사용 합니다.

for (i in array.indices) {
    print(array[i])
}

혹은 withIndex' fun 을 사용해서 index 와 value 를 return 받아 for문을 돌릴 수도 있습니다.

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

 

4. While Loops

while 문과 do.. while 문은 java 와 완전히 동일합니다.

아래의 예제에서 do 블록 안에 정의한 멤버변수 y 는 while 의 조건 식에서 참조할 수 있습니다.

while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

 

여기까지 Kotline 의 if, when, for, while 제어문 사용법에 대해 간단히 알아보았습니다.

기본 문법이기 때문에 잘 익혀두면 Kotline 으로 코딩하는데 많은 도움이 될 것입니다.

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] Range 사용하기  (0) 2018.01.25
[Kotlin] 함수 사용하기  (0) 2017.12.28
[Kotlin] Class 두번째 이야기  (0) 2017.12.22
[Kotlin] Class 사용하기  (0) 2017.12.15
안드로이드 공식 개발언어 Kotlin  (0) 2017.12.01

Kotlin 에서의 함수는 fun 키워드를 사용합니다.

fun double(x: Int):Int {
    return 2 * x
}

함수는 일반적으로 아래와 같이 부를 수 있습니다.

 

val result = double(2)

.(dot) 을 사용하여 멤버 함수를 부를 수도 있습니다.

 

Math().double()

 

Default Arguments

Parameter 의 전달은 name: type 과 같이 사용하며, default value 를 가질 수 있습니다. 인자가 전달되지 않으면 default value 가 값에 사용되며 불필요한 함수 오버로딩을 막을 수 있습니다.

checkCustomer("Gildong") 와 같이 필요한 인자만 사용하여 함수 호출이 가능합니다.

fun checkCustomer(name: String, phone: String = "NA", age: Int = 30){
    ...
}

checkCustomer("Gildong") // The default value phone = "NA" age = 30 is used

오버라이딩은 상위 메소드와 동일한 인자를 가지게 되므로, default value 를 사용하지 않을 때는 오버라이딩을 하여 default value 를 선언하지 않아야 합니다.

open class A {   open fun foo(i: Int = 10) {...}
}

class B: A(){
   override fun foo(i: Int) {...}   // no default value allowed
}

 

 

Name Arguments

함수를 호출할 때 parameter 의 이름을 사용하는데, 이를 Name Argument 라고 부릅니다.

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char ' ') {
...
}

위와 같은 함수가 있을때 아래와 같이 호출 할 수 있습니다.(위에서 default value 에 대해 설명한 것을 참고하시면 됩니다.)

reformat(str)

default value 가 없다면 reformat(str, true, true, false, ' ') 라고 불러야 할 것입니다. Name Argument 는 좀더 읽기 쉽게 아래와 같이 표현할 수 있도록 해줍니다.

reformat(str,
        normalizeCase = true,
        upperCaseFirstLetter = true,
        divideByCamelHumps = false,
        wordSeparator = '_')

모든 parameter 를 전달하지 않고 아래와 같이 사용가능합니다.

reformat(str, wordSeparator = '_')

단, str 인자가 positional argument 라 부르는데, positional argument는 항상 Name argument 보다 앞에 있어야 합니다.

즉, f(1, y = 2)는 가능하지만 f(x = 1, 2) 는 불가능합니다.

 

Unit-returning Functions

함수에서 return 값이 불필요하다면 return type을 Unit 으로 선언할 수 있습니다. Unit 은 명시적으로 반환할 필요가 없습니다.

return Unit 이나 return 은 선택적으로 할 수 있습니다. 또한 Unit 은 생략 가능합니다.(반환값을 사용하지 않는다면 Unit 이 생략되어 있는것과 같습니다.)

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there")
}

 

Single-Expression functions

함수가 한줄로 표현 가능하다면 괄호는 생략 가능합니다. 또한 반환값도 컴파일러에 의해 유추될 수 있다면 생략 가능합니다.

fun double(x: Int): Int = x * 2

fun double(x: Int) = x * 2

 

Variable number of arguments(Varargs)

함수의 매개 변수는 vararg modifier로 표시될 수 있습니다.

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for(t in ts) // ts is an Array
        result.add(t)
    return result
}

val list = asList(1, 2, 3)

asList 함수에서 T형의 vararg 매개 변수가 T의 배열로 표시됩니다. 하나의 매개 변수만 vararg로 표시 될 수 있습니다.

 

 

 

지난 포스팅에서는 Kotlin의 Class 의 생성자와 상속에 대해 살펴봤다면

이번 포스팅에서는 Class의 여러 쓰임에 대해 알아보고자 합니다.

 

Data Class

data를 저장하기 위해 Class를 사용할 때가 많은데요. Kotlin에서는 이런 Class를 Data Class라고 부릅니다.

data class Customer(val name: String, val phone: String, val age: Int = 0)

 

컴파일러는 data class 를 선언하면 자동으로 primary constructor에 멤버함수를 생성합니다.

equals() / hashCode() / toString() / conponentN() / copy()

 

위의 자동으로 생성된 멤버함수의 동작을 위해 아래의 조건을 만족해야만 합니다.

- primary constructor 는 반드시 하나 이상의 인자를 가져야 합니다.

- 모든 primary constructor 는 val 혹은 var 선언이 되어야 합니다.

- Data class는 abstract, open, seal, inner class 이면 안됩니다.

(abstract class는 java와 동일하며, open class는 상속가능한 class, seal class는 inner class는 말 그대로 다른 class 내부의 class입니다.)

- Kotlin 1.1 이전에는 interface 구현만 가능했으나 Kotlin 1.1 부터는 다른 class 를 상속받아 구현하는 것이 가능합니다.

 

개발을 하다보면 객체를 복사해서 사용하는 경우가 많은데요.

일부만 복사하고 일부는 변경해서 사용 할 수 있습니다.

 

fun copy(name:String = this.name, phone: String = this.phone, age: Int = this.age) = Customer(name, phone, age)

 

위와 같은 copy 함수가 자동으로 생성되면 아래와 같이 사용 가능합니다.

val newCustomer = User(name = "Gil-Dong", phone = "0101112222", age = 24)
val addCustomer = newCustomer.copy(phone = "0102223333")

또한 Data class 에서는 아래와 같이 사용하여 변수에 Data class의 값을 얻어올 수 있습니다.

val checkCustomer = User("Gil-Dong", "0101112222", 24)
val (name, phone, age) = checkCustomer
println("customer info : $name, $phone, $age")

 

Sealed Class

sealed class 는 JAVA 에서도 들어본적이 없어서 생소한 이름인데요.

제한된 형태의 클래스를 나타내기 위해 사용됩니다. enum 의 확장이라고 생각하시면 됩니다.

enum 타입과 Sealed class 모두 값이 제한되지만, enum 상수는 단일 인스턴스로만 존재하고 sealed class의 하위클래스는 state를 포함하는 여러 인스턴스를  가질 수 있습니다.

Sealed class의 하위클래스는 Sealed class와 같은 파일에 선언해야만 합니다.

 

아래 예제 코드는 Expr 이라는 sealed class 가 선언되고,

Expr 을 상속받는 Const 클래스와, Expr을 상속받으며 인자로 Expr 을 받는 Sum 이라는 클래스가 있습니다.

또한 Expr 타입의 object도 선언해서 사용할 수 있습니다.

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber: Expr()

sealed class는 abstract class 이며 직접 인스턴스화 할수 없습니다. abstract class처럼 abstract member 를 가질 수 있습니다.

sealed class 를 상속받는 Const, Sum 은 같은 파일에 있어야 하지만, 이들을 상속받는 다른 클래스들은 동일한 파일에 있을 필요는 없습니다.

sealed class를 사용할때 큰 이점은 when 문을 사용할 때인데요.

아래 예제 코드에서 eval 함수는 Expr을 인자로 받아 Double 형을 리턴하는 함수입니다.

when 문에서 인자로 받은 expr 이 어떤 형태인지 아래와 같이 확인이 가능합니다.

JAVA로 하면 인자로 받은 expr 이 어떤 객체인지 확인을 하고 구현을 해야하는데 아래와 같이 구현이 가능하니

코드량이 확실히 줄어드는 것을 바로 확인할 수 있습니다.

fun eval(expr: Expr) : Double = when(expr) {
    is Const-> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NAN
}

 

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] Range 사용하기  (0) 2018.01.25
[Kotlin] if, when, for, while 제어문 사용하기  (0) 2018.01.04
[Kotlin] 함수 사용하기  (0) 2017.12.28
[Kotlin] Class 사용하기  (0) 2017.12.15
안드로이드 공식 개발언어 Kotlin  (0) 2017.12.01

+ Recent posts