티스토리 뷰

728x90

이번 문제는 약간의 난이도가 있는 문제 입니다. C언어를 어렵다고 느끼는 사람들이 싫어하는 구조체와 포인터를 사용해야 하고 메모리 할당과 재귀형(Recursive) 함수를 사용해야 하는등 알고리즘 뿐만아니라 주요 라이브러리 함수 사용에 대한 이해도 필요합니다. 도전적 과제이기는 하지만 개발에 성공하면 실제적으로도 도움이 될만한 프로그램 문제입니다. 설계부터 차분히 도전해 보세요.  


■ 문제

# 지정 아규먼트와 옵션에 따라 디렉토리의 용량을 분석하는 콘솔 프로그램을 작성합니다.

- dirana [디렉토리] [-n]과 같은 아규먼트를 입력 받습니다.

  디렉토리는 분석 대상인 디렉토리로 생략시 현재 디렉토리(.)를 분석합니다.

  -n는 분석후 출력할 하위 디렉토리의 깊이로 생략시 -1로 간주하며 -1은 현재 디렉토리만 출력하고 -2는 바로 다음 하위 디렉토리까지 출력합니다. (n의 값은 1~5만 지원합니다)


- 화면 출력의 형태는 다음과 같습니다.

Analysis start : mydir

doc: *********

  23.4MB(20%), 2 Subdirs, 5 Files

test :*************************

  300MB(60%), 39 Files

objs : ***************

   70MB(20%), 10 Subdirs, 4 Files

Total : 393.4MB, 3 Subdirs, 0 Files

Processing time : 3 Seconds 

분석 대상 디렉토리를 "Analysis start"로 출력합니다.

디렉토리명: 막대 그래프를 표시합니다.

   디렉토리내 파일 및 하위 디렉토리의 용량을 합산하여 MB단위로 표시하고 상위 디렉토리를 100%라 가정했을때의 점유율을 백분율로 표시합니다.

   *로 표시하는 막대 그래프는 전체 표시 폴더중 가장 큰 값을 가진 폴더를 100%로 간주하고 해당 폴더에 대비한 비율을 길이로 표시합니다.

   디렉토리내 폴더 및 파일 개수를 표시합니다.

   하위 폴더는 한칸 들여쓰기 및 앞에 -문자를 깊이만큼 표시하는 것으로 구분합니다. 예를 들어 3단계의 폴더는 ---dirtest : ...와 같이 출력합니다.


- 수행 과정에 오류가 없다면 프로그램 수행시간을 초 단위로 출력합니다.


■ 코드와 해설

프로그램은 크게 아규먼트를 식별해서 플래그에 저장하는 입력 검사부와 디렉토리 구조를 따라서 통계를 작성하는 부분, 수집한 정보를 사용자 요구에 따라 출력하는 부분, 작업한 사용한 메모리를 해제하는 부분으로 나뉩니다. 먼저 자신 나름의 프로그램을 작성해 보고 샘플을 막힐 때 참고하는 것이 좋습니다. 코드를 그냥 따라하기 보다 개발자의 의도를 정확하게 파악해서 자신의 필요에 따라 사용하는 방법이 좋습니다..

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

struct node {
    char regname[12];
    int Dcount, Rcount;
    long long dsize;
    struct node *next;
    struct node *child;
};

const long long GB_BASE = 1024LL * 1024LL * 1024LL;
const long long MB_BASE = 1024LL * 1024LL;
const long long KB_BASE = 1024LL;

char *human_size(long long size, char *buf)
{
    if (size > GB_BASE)
        sprintf(buf, "%dGB", (int)(size / GB_BASE));
    else if (size > MB_BASE)
        sprintf(buf, "%dMB", (int)(size / MB_BASE));
    else if (size > KB_BASE)
        sprintf(buf, "%dKB", (int)(size / KB_BASE));
    else
        sprintf(buf, "%dBytes", (int)size);
    return buf;
}

const char *lvlchar1 = "-----";
const char *lvlchar2 = "     ";
const char *graphchar = "************************************************************";
const int BAR_MAX = 60;
void printdir(struct node *dnode, int curlvl, int prtlvl)
{
    struct node *pnode;
    char tmp[16];
    
    for (pnode = dnode->child; pnode != NULL; pnode = pnode->next) {
        printf("\n%.*s", curlvl, lvlchar1);
        printf("%-12s:%.*s", pnode->regname, (int)(pnode->dsize * BAR_MAX / dnode->dsize), graphchar);
        printf("\n%.*s", curlvl, lvlchar2);
        printf("  %s(%.2f%%), %d Subdirs, %d Files", human_size(pnode->dsize, tmp), 
            pnode->dsize*100.0/dnode->dsize, pnode->Dcount, pnode->Rcount);
        if (curlvl + 1 < prtlvl)
            printdir(pnode, curlvl + 1, prtlvl);
    }
    if (curlvl == 0)
        printf("\nTotal : %s, %d Subdirs, %d Files", human_size(dnode->dsize, tmp), dnode->Dcount, dnode->Rcount);
}

void freedir(struct node *dnode)
{
    if (dnode->child != NULL) freedir(dnode->child);
    if (dnode->next != NULL) freedir(dnode->next);
    free(dnode);
}

 
int scandir(char *path, int level, struct node *dnode)
{
    DIR * dp = NULL;
    struct dirent *de = NULL;
    struct stat finfo;
    struct node *snode;
    char actpath[256];
    
    if (dnode == NULL) return -1;
    if((dp = opendir(path)) == NULL)
    {
        return -1;
    }
    
    while((de = readdir(dp)) != NULL)
    {
        if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue;
        sprintf(actpath, "%s/%s", path, de->d_name);
        if(stat(actpath, &finfo) == -1) continue;
      
        if(S_ISREG(finfo.st_mode))
        {
            dnode->Rcount++;
            dnode->dsize += finfo.st_size;
        }
        if(S_ISDIR(finfo.st_mode))
        {
            dnode->Dcount++;
            if ((snode = (struct node *)malloc(sizeof(struct node))) == NULL) return -1;
            memset(snode, 0, sizeof(struct node));
            if (dnode->child == NULL) {
                dnode->child = snode;
            } else {
                if (dnode->child->next != NULL) snode->next = dnode->child->next;
                dnode->child->next = snode;
            }
            if (strlen(de->d_name) > 8) {
                memcpy(snode->regname, de->d_name, 8);
                strcpy(&snode->regname[8], "...");
            } else {
                strcpy(snode->regname, de->d_name);
            }
            if (scandir(actpath, level + 1, snode) < 0) return -1;
            dnode->dsize += snode->dsize;
        }
    }
    
    closedir(dp);
    return 0;
}

void Usage() 
{
    printf("\nUsage ==>\ndirana [Directory name] [/n]\n\tn:Output depth of sub directory");
    exit(0);
}

char *default_dir = ".";
int main(int argc, char**argv)
{
    time_t statime, endtime;
    struct node *root;
    char *stadir = NULL, *wkpt;
    int i, prtlvl = 1;
    
    //Input check
    if (argc < 2) {
        stadir = default_dir;
    } else {
        for (i = 1; i < argc; i++) {
            wkpt = argv[i];
            if (wkpt[0] == '-' && wkpt[1] >= '1' && wkpt[1] <= '5') {
                prtlvl = wkpt[1] - '0';
            } else {
                stadir = wkpt;
            }
        }
        if (stadir == NULL) stadir = default_dir;
    }

    printf("\nAnalysis start : %s", stadir);
    
    time(&statime);
    if ((root = (struct node *)malloc(sizeof(struct node))) == NULL) {
        printf("\n memory allocation fail!");
        exit(0);
    }
    memset(root, 0, sizeof(struct node));
    if (scandir(stadir, 0, root) < 0) {
        printf("\n %s scan fail!", argv[1]);
        freedir(root);
        exit(0);
    }
    printdir(root, 0, prtlvl);
    freedir(root);
    time(&endtime);
    printf("\nProcessing time is %d seconds", endtime - statime);
}



  • 문자의 정수 변환
    디렉토리 출력의 깊이를 지정하는 옵션 -n 을 처리 하는 로직을 보면 "
    (wkpt[0] == '-' && wkpt[1] >= '1' && wkpt[1] <= '5')"로 검증합니다. '-' 문자 다음에 오는 문자 값의 범위로 1~5사이의 값을 입력 했는지 검사합니다. ASCII 코드 값을 보면 '0'부터 '9'까지 차례대로 배열되어 있기 때문에 코드 값의 범위로 조건을 제시해도 문제가 없고 코드 값의 차이로 정수값을 취득해도 된다는 것입니다.

  • 메모리 할당과 해제
    C#, Java와 같은 최근의 언어에서는 가비지 컬렉션(GC, Garbage collection) 개념을 도입해서 사용하지 않는 메모리를 자동적으로 정리하지만 
    C언어에서의 메모리 관리는 개발자가 직접 수행해야 합니다. malloc()으로 메모리를 할당 받았다면 반드시 free()로 해제해 주어야 한다는 것입니다. 이 과정이 정확하지 않다면 프로그램의 성능을 떨어뜨리고 심지어 시스템에 피해를 주는 상황 까지도 초래할 수 있습니다. 구조체 들을 포인터로 연결했다면 맨 끝 포인터부터 차례대로 메모리를 해제하는 정확한 메모리 관리로 링크가 끊어져서 미아가 생기지 않도록 해야 합니다.

  • 구조체 포인터를 통한 트리 구현
    위의 예제에서 struct node 구조체는 각 디렉토리를 통계를 담기도 하지만 디렉토리 구조를 트리 형태로 저장하기 위해서 두가지의 링크 포인터를 사용하고 있습니다. 한 디렉토리에 같이 존재하는 폴더들은 next 링크로 이어지고 하위 디렉토리는 child 링크로 이어지는 구조입니다. 특정 디렉토리를 탐색하고 싶다면 우선 child 링크로 접근해서 해당 노드의 next 링크를 따라가면 됩니다
    .

  • 타입 캐스팅(Type casting)
    malloc() 호출로 전달되는 메모리는 기본적으로 void 타입입니다. 차입이 정의되지 않았다는 의미입니다. 이렇게 타입이없는 포인터를 특정 변수에 할당할때는 해당 타입을 명시적으로 지정해 주는 것이 좋습니다. 컴파일러에 따라서 경고 메시지를 발생시키지 않는 경우도 있지만 프로그래머의 명확한 의도를 제시하는 것이 좋습니다. 예제에서 사용한 또다른 캐스팅은 표현식 결과를 강제 변환한 사례로 printf()에서 항목의 길이를 인수로 전달할때는 정수형 값을 지정해야 하는데 예제의 표현식 결과는 (long long)이기 때문에 (int)로 강제 캐스팅한 것입니다. 함수 호출과 표현식 사용에 있어 주의를 기울일 필요가 있습니다..

  • printf()의 항목 길이 지정하기
    예제 코드에서 그래프를 출력하거나 서브 디렉토리의 구분자 표시를 위해서 "
    %.*s" 처럼 지정한 문장이 있는데 "*"는 길이를 정수형 값으로 먼저 전달하겠다는 의미입니다. 포인터로 전달하는 문자열에 앞서 정수값을 전달해서 문자열 중에 해당 값만큼을 출력하는 것입니다. 

  • 서로 다른 타입이 섞인 연산
    백분율을 구하는 "pnode->dsize*100.0/dnode->dsize" 문장은 long long 타입과 float  상수값이 섞여 있고 그래프 길이를 결정하는 "pnode->dsize * BAR_MAX / dnode->dsize"는 long long 타입과 int 값이 섞여 있습니다. 이런 경우 크기가 큰 타입으로 변환되어서 연산합니다. 문제는 정수를 정수로 나눈 경우에는 소수부가 없어진다는 것입니다. 고로 백분율을 다루어야 하는 부분에서는 소수부를 잃지 않도록 감안해서 표현식을 작성해야 할 필요가 있습니다. 정수 끼리의 연산인 "pnode->dsize * BAR_MAX / dnode->dsize"를 순서를 바꾸어 "pnode->dsize / dnode->dsize * BAR_MAX
    "로 수행하면 정상적인 값을 얻을 수 없습니다. 연산 순서상 "pnode->dsize / dnode->dsize"를 먼저 수행하면 이 값은 1보다 작거나 같은 결과만을 산출하기 때문입니다.


728x90
댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/03   »
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 29 30
31
글 보관함