티스토리 뷰



이번 문제는 그리 어려운 문제는 아닙니다. 다만 쉽고 단순한 알고리즘으로 풀 수 있는 문제라 하더라도 함수명, 변수명을 가독성(Readability)이 좋도록 명명하고 들여쓰기(Indentation)에 유의하면서 최적의 알고리즘을 적용하려는 노력을 기울여야 할 것입니다. 간단한 프로그램이지만 효율적인 프로그램 수행을 위한 다양한 기법을 익힐 수 있습니다.


■ 문제

# 지정 옵션에 따라 다양한 형태의 ASCII 테이블을 출력하는 콘솔 프로그램을(asciitbl) 작성하세요.

    • https://en.wikipedia.org/wiki/ASCII  를 참고합니다.

    • 화면 출력은 고정 화면폭(80자)을 기준으로 합니다.

    • 옵션을 지정하지 않으면 제어문자를 제외한(printable) 문자들만 출력합니다.

    • /a 옵션은 모든 문자를 출력합니다. 제어 문자는 위키 문서를 참고해서 약자(Symbol)로 표시합니다.

    • 각 문자는 "십진 코드값 문자 또는 제어문자의 약자"의 형태로 표시합니다.

    • /b 옵션은 2진 코드값을 부가합니다.

    • /x 옵션은 16진 코드값을 부가합니다.

    • /o 옵션은 8진 코드값을 부가합니다.

    • 사용예 : asciitbl /a /x
      제어 문자를 포함한 모든 문자를 표시하면서 "십진 코드값 문자 또는 제어문자의 약자 16진 코드값"의 형태로 출력합니다.


■ 코드와 해설

프로그램은 크게 아규먼트를 식별해서 플래그에 저장하는 입력 검사부와 플래그 값에 따라 아스키 테이블을 출력하는 테이블 출력부로 나뉩니다. 여러 옵션을 나타내는 플래그는 정수 변수를 사용하여 각 비트의 On/Off에 따라 옵션이 주어졌는지를 확인하는 방법을 사용했습니다. 출력은 한줄에 두개의 문자를 표시하는 단순한 방법을 사용했으나 주어진 옵션 개수에 따라 한 줄에 표시하는 문자의 개수를 조정할 수도 있을 것입니다.

#include<stdio.h>
#include<string.h>

char *cntlchar[32] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", 
                        "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
                        "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", 
                        "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" };
enum flag_enum { OPT_ALL = 1, OPT_BIN = 2, OPT_HEX = 4, OPT_OCT = 8};                    
char *options[4] = { "/a", "/b", "/x", "/o" };
int option_bits[4] = { OPT_ALL, OPT_BIN, OPT_HEX, OPT_OCT };

// val : 출력할 값, byte_size : 출력 바이트 수(1~4)
// return : 0-fail, 1-OK
int print_binary(int val, int byte_size )
{
    int i;
    
    if (byte_size < 1 || byte_size > 4) return 0;
    for (i = 1 << ((byte_size * 8)-1); i > 0; i /= 2)
    {
        if (val & i) 
            printf("1");
        else
            printf("0");
    }
    return 1;
}

//플래그에 따라 아스키 테이블 출력
void tableprint(int inflag)
{
    int ch;
    
    for (ch=0; ch < 128; ch ++)
    {
        if (ch < 32)
        {
            // /a 옵션이 없으면 제어문자 출력 안함
            if ((inflag & OPT_ALL) == 0) continue;
            printf("%3d = %-3s ", ch, cntlchar[ch]);
        }
        else
        {
            printf("%3d = %-3c ", ch, ch);
        }
        if (inflag & OPT_BIN)
        {
            printf(" ");
            print_binary(ch, 1);
        }
        if (inflag & OPT_HEX)
        {
            printf(" %02x", ch);
        }
        if (inflag & OPT_OCT)
        {
            printf(" %03o", ch);
        }
        if (ch % 2 == 1)
            printf("\n");
        else
            printf("  | ");
    }
}

void UsageExit()
{
    printf("asciitbl [/a][/b][/x][/o]\n");
    printf("\t옵션이 없으면 제어문자를 제외한 아스키코드 출력\n");
    printf("\t/a : 제어문자 포함\n");
    printf("\t/b : 2진수 출력\n");
    printf("\t/o : 8진수 출력\n");
    printf("\t/x : 16진수 출력\n");
    exit(0);
}

int main(int argc, char **argv)
{
    int i, j, flag = 0;

    for (i = 1; i < argc; i++)
    {
        for (j = 0; j < 4; j++)
        {
            if (strcmp(strlwr(argv[i]), options[j]) == 0)
            {
                flag |= option_bits[j];
                break;
            }
        }
        if (j >= 4)
        {
            printf("\nInvalid  option : %s",argv[i]);
            UsageExit();
        }
    }
    tableprint(flag);
}


  • 플래그와 비트(Bitwise) 조작
    main()함수에서 프로그램 아규먼트로 건네진 각 옵션들을 분석한 결과는 flag라는 정수 변수에 담기게 됩니다. 여러 옵션을 문자열 배열(
    options[])에 준비했다가 개별 아규먼트와 비교해서(소문자 변환 비교를 위해서 사전에 strlwr() 함수 호출) 지원하는 옵션이 있으면 해당 옵션의 비트 위치값을 가지고 있는 배열을(option_bits[]) 참조해서 플래그의 해당 비트를 On합니다. 비트를 켤때는 Bitwise OR 연산(|)을 사용합니다. 테이블 출력 로직을 보면 플래그의 특정 비트가 켜있는지 확인하기 위해서 Bitwise AND 연산(&)을 사용하고 있는데 이처럼 특정 비트나 비트 그룹을 걸러내는 과정을 마스킹(Masking)이라 합니다.

  •  열거형 변수 정의하기
    C언어에서 상수를 정의하는 방법으로 #define 구문을 사용하거나 "const int ...." 처럼 전역 변수를 정의하면서 const를 붙여 상수화하는 방법도 있지만 enum을 통해서 간편하게 열거형 변수를 정의할 수 있습니다. 정의 및 사용 방법은 구조체(union, struct 등)와 유사합니다. 문법은 아래와 같습니다.
    enum enum태그 { enum목록, ......}
    enum태그는 생략할 수도 있지만 enum 타입의 변수를 여러개 선언하려면 enum태그를 지정하는 것이 좋습니다. const변수나 #define 정의 처럼 사용하는 부분은 enum목록 부분으로 값은 0부터 시작하지만 "변수명=값"의 형태로 값을 직접 지정하면 그 다음 목록 부터는 이전 값의 +1 값으로 정의됩니다. enum enum태그 변수로 열거형 값을 가지는 변수를 정의할 수 있고 의미적으로 해당 변수는 enum목록에 기술된 값중에 하나를 갖지만, 실제로는 정수형 변수처럼 사용할 수 있습니다.

  • 이진수 출력과 시프트(Shift) 연산
    print_binary(int val, int byte_size) 함수는 정수값과 출력 바이트수를 인수로 해서 이진수를 출력하는 함수입니다. 함수의 구조를 보면 지정한 바이트 크기에 해당하는 비트 스트림의 최상위 비트(MSB, Most Significant Bit)부터 최하위 비트(LSB, Least Significant Bit) 쪽 우측 방향으로 차례대로 비트 검사를 해서 '1' 또는 '0'을 출력하는 방식입니다. 주목할 부분은 MSB의 값을 갖도록 for 구문의 시작점에 <<(Left Shift) 연산자를 사용한 것과 루프의 단계 마다 우측으로 검사할 비트를 지정하도록 2로 값을 나눈 것입니다. 1바이트의 MSB는 1 << 7, 2바이트의 MSB는 1<<15로 비트를 시프트하는 것으로 얻을 수 있습니다. 좌시프트는 *2와 같은 효과를 나타내면 /2는 우시프트(>>)와 동일한 결과를 얻을 수 있습니다.

  • printf()의 좌우맞춤(Align)과 공백 채우기(Padding), 자르기
    printf()함수는 sprintf()나 fprintf() 등으로 확장되는등 매우 유용한 라이브러리 함수입니다. 숫자나 문자열을 출력하는 과정에서 "%d"나 "%s"같은 단순 값 출력을 넘어서 좌우 맞춤 및 공백 채우기를 적용하는 방법입니다. "%3d"나 "%3s"처럼 출력할 값의 자리수를 지정하면 우측으로 맞추어지고 값이 지정한 자리수 보다 작으면 좌측을 공백문자로 채웁니다. "%-3d"나 "%-3s"처럼 자리수를 지정하면서 바로 앞에 "-"문자를 붙이면 좌측으로 맞추어지고 
    값이 지정한 자리수 보다 작으면 우측을 공백문자로 채웁니다. 



댓글
댓글쓰기 폼