2019년 8월 5일 월요일

3.2 C의 모듈화와 객체지향

3.2.1 C와 모듈화

고전적인 스택의 구현(git@github.com:pcw1029/modernC.git)
stack.h
#ifndef _STACK_H_
#define _STACK_H_

bool push(int val);
bool pop(int *pRet);

#endif

고전적인 스택의 구현
stack.c
#include<stdbool.h>
#include"stack.h"

int buf[16];
int top=0;

bool isStackFull(void){
    return top == sizeof(buf)/sizeof(int);
}

bool isStackEmpty(void){
    return top == 0;
}

bool push(Stack *p, int val){
    if(isStackFull(p)) return false;
    p->pBuf[p->top++] = val;
    return true;
}

bool pop(Stack *p, int *pRet){
    if(isStatckEmpty(p)) return false;
    *pRet = p->pBuf[--p->top];
    return true;
}

문제점 :
 변수와 함수의 스코프 : buf, top, isStackFull, isSteckEmpty가 전역 네임스페이스에 공개돼 있다. 만약 다른 소프코드에서 동일한 이름의 변수와 함수가 존재하면 충돌이 발생되어 링크에러를 발생한다. 이는 다른 프로그램에서 사용하기가 어려워진다.

해결책 :
 static 지시자를 사용한다.
static지시자를 붙인 함수나 변수의 이름은 해당 컴파일 단위 내에서만 유효하므로 충돌을 피할수 있다. 따라서 외부에 공개할 함수나 변수만 헤더에서 선언하고, 외부에 공개할 필요가 없는 변수나 함수는 static지시자를 붙이는 것이 C에서 흔히 볼수 있는 모듈화의 방법이다.

stack.c
#include<stdbool.h> 
#include "stack.h"

static int buf[16];
static int top=0;

static bool isStackFull(void){
    return top == sizeof(buf)/sizeof(int);
}

static bool isStatckEmpty(void){
    return top == 0;
}

bool push(int val){
    if(isStackFull()) return false;
    buf[top++] = val;
    return true;
}

bool pop(int *pRet){
    if(isStatckEmpty()) return false;
    *pRet = buf[--top];
    return true;
}


3.2.2 구조체를 이용한 자료 구조와 로직 분리
 앞에서 살펴본 예제는 아직도 문제 있습니다. 동일한 크기의 스택을 하나밖에 가질수 없는 문제입니다. 물론 함수나 변수의 이름에 숫자등을 붙여 비슷한 일을 하는 함수 또는 변수를 추가할수 있지만 그보다 구조체를 사용하여 스택을 구현하는데 필요한 데이터를 한데 모아서 그렇게 모은 데이터를 각 각 여러개 갖게 하는 방법입니다.

stack.h
#ifndef __STACK_H__ 
#define __STACK_H__

#include<stddef.h>

#ifdef __cplusplus
extern "C"{
#endif

typedef struct{
    int top;
    const size_t size;
    int *const pBuf;
}Stack;

bool push(Stack *p, int val);
bool pop(Stack *p, int *pRet);

#define newStack(buf){  \
    0, sizeof(buf)/sizeof(int), (buf) \
}

#ifdef __cplusplus
}
#endif

#endif

stack.c
#include<stdbool.h>
#include "stack.h"

static int buf[16];
static int top=0;

static bool isStackFull(const Stack *p){
    return p->top == p->size;
}

static bool isStatckEmpty(const Stack *p){
    return p->top == 0;
}

bool push(Stack *p, int val){
    if(isStackFull(p)) return false;
    p->pBuf[p->top++] = val;
    return true;
}

bool pop(Stack *p, int *pRet){
    if(isStatckEmpty(p)) return false;
    *pRet = p->pBuf[--p->top];
    return true;
}

stack.h파일에 정의된 newStack(buf) {0, sizeof(buf)/sizeof(int), (buf)} 는 아래와 같다.

int buf[16];
Stack stack = newStack(buf);

int buf[16];
Stack stack = {0, sizeof(buf)/sizeof(int), (buf)};
//top = 0, size = sizeof(buf)/sizeof(int), pBuf = buf

스택 데이터를 구조체로 분리해서 함수에 전달함으로서 간단하게 여러개의 스택을 만들수 있다.

int buf[16];
Stack stack = newStack(buf);

int buf2[18];
Stack stack2 = newStack(buf2);

3.2.3 C를 이용한 객체 지향
 스택에 저장할 수 있는 값의 범위를 제한하고 싶다고 가정해 보자. 예를 들어 0~9사이 값만 Push가 성공하고 그 외의 범위에 해당하는 숫자라면 여유공간이 존재해도 실패가 하도록 만들것이다.
쉽게 생각할수 있는것이 Push함수 전에 값을 확인하고 0~9사이에 값만 Push하도록 하면 된다.

bool pushWithRangeCheck(Stack*p, int val, int min, int max) {
    if(val<min || max<val) return false;
    return push(p, val);
}

위와 같은 방법은 인자의 개수가 많고, 이해하기 어렵고 push할때만다 인자값을 던지기 때문에 번거롭게 생각된다. 아래 코드는 좀 더 세련된 방법으로 스택을 생성하는 시점에 해당 범위를 전달하는 방법이 있다.

stack.h
typedef struct{
    int top;
    const size_t size;
    int *const pBuf;
    
    const bool needRangeCheck;
    const int min;
    const int max;
}Stack;
...
#define newStackWithRangeCheck(buf, min, max) { \ 
    0, sizeof(buf)/sizeof(int), (buf), \ 
    true, min, max
}

stack.c
                                                                                                          
static bool isRangeOk(const Stack *p, int val){                                                           
    return ! p->needRangeCheck || (p->min <= val && val <= p->max)                                        
}   

bool push(Stack *p, int val){                                                                             
    if(! isRangeOk(p,val) || isStackFull(p)) return false;                                                
    p->pBuf[p->top++] = val;
    return true;                                                                                          
}

범위 검사가 필요한지 여부를 나타내는 변수 needRangeCheck 그리고 값의 유효 범위를 나타내는 변수 min, max를 구조체에 포함시키고, 범위 검사 기능을 가진 스택을 생성하기 위한 매크로를 준비했다.

int buf[16];
Stack stack = newStackWithRangeCheck(buf, 0, 9);

이 예제에서는 0에서 9까지의 범위 검사 기능을 가진 스택을 생성한다.

이 코드에는 아래와 같은 문제점을 가지고 있습니다.
1. 범위 검사 기능이 없는 스택을 생성한 경우에도 needRnageCheck, min, max같은 불필요한 맴버를 스택에 보유해야 한다.
2. 스택에 또다른 검사 기능을 추가하고자 한다면 구조체에 멤버를 추가해야 한다. 구조체 내부에 제공하려는 기능에 필요한 모든 멤버를 가져야 하므로 push함수도 그 기능때문에 크기가 커진다. 기능의 수가 많아지면 곧장 걷잡을 수 없을 만큼 복잡해진다.

*생각하기
1. 이번에 추가된 needRangeCheck, min, max 맴버들은 스택의 다른 맴버와 같은 곳에 둘 필요가 있는가?
2. 이들 맴버들은 isRangeOk에서만 사용되는 것들이며,  스택을 구성하는 모든 함수가 접근할수 있는 곳에 두는것이  적절한가?
이 위치면 스코프가 너무 넓다. 우선 이부분을 분리해보겠다.

stack.h

#include<stdbool.h>
#include<stddef.h>

#ifdef __cplusplus
extern "C"{
#endif

typedef struct{
    const int min;
    const int max;
}Range;

typedef struct{
    int top;
    const size_t size;
    int *const pBuf;
    const Range* const pRange;
}Stack;

bool push(Stack *p, int val);
bool pop(Stack *p,int *pRet);

#define newStack(buf){ \
    0, sizeof(buf)/sizeof(int), (buf), \
    NULL \
}

#define newStackWithRangeCheck(buf, pRange) { \
    0, sizeof(buf)/sizeof(int), (buf), \
    pRange \
}


#ifdef __cplusplus
}

stack.c

#include<stdio.h>                                                                                     
#include "stack.h"                                                                                    
static bool isRangeOk(const Range* p, int val){                                                       
    return p == NULL || (p->min <= val && val <= p->max);                                             
}                                                                                                     
                                                                                                      
static bool isStackFull(Stack *p) {                                                                   
    return p->top == p->size;                                                                         
}                                                                                                     
                                                                                                      
static bool isStackEmpty(Stack *p) {                                                                  
    return p->top == 0;                                                                               
}                                                                                                     
                                                                                                      
bool push(Stack *p, int val) {                                                                        
    if(! isRangeOk(p->pRange, val) || isStackFull(p)) return false;                                   
    p->pBuf[p->top++] = val;                                                                          
    return true;                                                                                      
}                                                                                                     
                                                                                                      
bool pop(Stack *p, int *pRet) {                                                                       
    if(isStackEmpty(p)) return false;                                                                 
    *pRet = p->pBuf[--p->top];                                                                        
    return true;                                                                                      
} 

체크기능을 범용화 한다
지금까지는 상하 한계 검사를 수행하는 구조로 만들었지만 값 검사의 대상은 한계말고도 많습니다.
예를 들어 직전에 Push한 값 이상의 값을 받아들이는 조건이 들어있는 스택을 만들려고 할때 현재 구조는 구현 할수 없지만 입력값 검사를 번용적으로 바꾸면 가능해 집니다.

먼저, Validator라는 구조체에 입력값을 검사하는 범용적인 역할을 구현하겠습니다.







VITIS Git + Doxygen Config

 Doxygen Configure 1. Vitis 메뉴의 Window->Preference의 C/C++ -> Editor의 Documentation tool comments 기본 설정값을 Doxygen으로 변경 설정 후 함수 바로 위에서 /...