게시판 즐겨찾기
편집
드래그 앤 드롭으로
즐겨찾기 아이콘 위치 수정이 가능합니다.
C언어로 프로그램 짤 때의 몇가지 요령 2
게시물ID : programmer_4189짧은주소 복사하기
작성자 : 달빛연구자
추천 : 9
조회수 : 2530회
댓글수 : 13개
등록시간 : 2014/06/25 22:28:41
 
안녕하세요 달빛연구자 입니다.
 
지난 게시글에 이어서 이번에도 몇 가지 팁을 추가해 보았습니다.
이 내용들은 제가 작업할 때 사용하는 소소한 규칙들을 정리한 것 이라서요.
많은 분들이 아는 내용도 있고 꽤 신선한 내용도 있습니다. 재미있게 보시고 많은 피드백 주시면 감사하겠습니다.
 
1. 변수 은닉
 - 모듈에서 사용하는 변수들을 구조체로 만들어서 .c파일에 정의합니다.
 모듈에서 사용하는 변수들을 구조체로 만들면 편리합니다. 그런데 이걸 .c파일에 정의하면 이 구조체의 멤버가
파일 외부에서 접근되는 것을 어느정도 막아주는 효과가 생겨요. 대략 이런 식 이에요.
[module.h]
void *module_get_struct(void);
[module.c]
struct test_s
{
 int num;
 int num2;
};
struct test_s sss;
void *module_get_struct(void)
{
 return (void*)sss;
}
[main.c]
#include "module.h"
int main()
{
 void* test = module_get_struct();
 struct test_s *mod_struct; // 선언되지 않습니다.
 mod_struct = (struct test_s *)test; // 캐스팅도 되지 않습니다 ^^;
 return 0;
}
struct test_s의 정의가 module.c에 있기 때문에, main.c에서는 struct test_s가 어떤 타입의 구조체 인지 알 수 없기 때문에,
외부에서는 접근이 불가능 하게 됩니다. (물론 할려고 맘먹으면 합니다만.. ^^;)
따라서 변수가 파일 내부에 은닉 되는 효과가 있습니다.

2. 함수 은닉
 - .h에는 인터페이스로 사용할 함수만을 선언하고, 모든 함수는 static을 붙여 .c에 선언합니다.
아래와 같은 형식이 됩니다.
[module.h]
int module_ret_2_func_exter();
[module.c]
static int ret_2_func_inter()
{
 return 2;
}
int module_ret_2_func_exter()
{
 return ret_2_func_inter();
}
[main.c]
extern int ret_2_func_inter(); // extern이 적용되지 않습니다.
int main()
{
 ret_2_func_inter(); // 호출이 불가능합니다.
 module_ret_2_func_exter(); //호출할 수 있습니다.
 return 0;
}
main.c 에서는 module이 module.h에 선언된 함수만을 호출할 수 있기 때문에, 외부에 노출될 필요가 없는
함수들이 파일 내부에 은닉 되는 효과를 얻을 수 있습니다.
 
3. 이름공간
 - static을 이용하여 .c에서 선언한 함수명은 다른 .c파일에서 static을 이용하여 재사용할 수 있습니다.
아래와 같은 형식이 됩니다.
[module.h]
int module_ret_2_func_exter();
[module.c]
static int get_value()
{
 return 2;
}
int module_ret_2_func_exter()
{
 return get_value();
}
[main.c]
#include "module.h"
static int get_value() // module.c에 같은 이름의 함수가 있어도 선언이 가능합니다.
{
 return 4;
}
int main()
{
 get_value(); //main.c의 get_value함수가 호출됩니다.
 module_ret_2_func_exter(); //module.c의 get_value 함수도 잘 동작합니다.
 return 0;
}
C에서는 헤더에 공개되는 함수들은 이름의 중복을 피하기 위해 상당히 긴 접두어를 붙이는 경우가 많은데요,
이러한 성질을 이용하면, 파일 내부에서만 쓰이는 함수들은 이름이 중복될 걱정을 하지 않아도 됩니다.
 
4. typedef
 - 구조체를 정의할 때는 typedef 를 사용하여 별명을 지어주면 편리합니다. :)
ex)
typedef struct
{
 int num;
 int num2;
}TEST_T;

5. 생성/소멸자
 - void *module_open(void); 과 같은 함수로 구조체를 생성하고, void module_close(void* handle)과 같은 함수로
구조체를 메모리를 합니다. 아래와 같은 형태가 됩니다.
[module.h]
void* module_open(void);
void module_close(void *handle);
int do_work(void *handle);
[module.c]
typedef struct
{
 int num;
 int num2;
}TEST_T;
void* module_open(void)
{
 TEST_T *handle = (TEST_T *)malloc(sizeof(TEST_T));
 
 /* 구조체를 생성하자마자 해야 할 일들을 합시다 */ 
 handle->num = 1;
 handle->num2 = 2;
 return handle;
}
void module_close(void *handle)
{
 /* 구조체를 해제하기 전에 해야 할 일들을 합니다. */
 free(handle);
}
int do_work(void *handle)
{
 TEST_T *test = NULL;
 if(!handle)return -1;
 test = (TEST_T *)handle;
 printf("n1 : %d, n2 : %d",test->num,test->num1);
 return 0;
}

[main.c]
#include "module.h"
int main()
{
 void *md = module_open(); // 생성
 if(module_work(md) < 0)return -1; // 작업
 module_close(md); // 파괴
 return 0;
}
 
6. 동적 바인딩
 - 구조체 내부에 함수포인터를 넣어놓고, module_open시 할당합니다.
 아래와 같은 형태입니다.
[animal.h]
void* animal_open(void);
void animal_close(void *handle);
void set_bark(void *handle,void (*bark)(void));
void animal_bark(void *handle);
[animal.c]
typedef struct
{
 int num;
 void (*bark)(void);
}ANIMAL_T;
void ani_bark(void)
{
 printf("...");
}
void* animal_open(void)
{
 ANIMAL_T *ani = (ANIMAL_T *)malloc(sizeof(ANIMAL_T));
 ani->bark = ani_bark;
 return handle;
}
void set_bark(void *handle,void (*bark)(void))
{
 ANIMAL_T *ani = (ANIMAL_T*)handle;
 ani->bark = bark; 
}
void animal_bark(void *handle)
{
 ANIMAL_T *ani = (ANIMAL_T*)handle;
 ani->bark();
}
...생략...
[main.c]
void cat_bark(void)
{
 printf("야옹");
}
int main()
{
 void* handle = animal_open();
 animal_bark(handle); //ani_bark 실행
 set_bark(handle,cat_bark);
 animal_bark(handle); //cat_bark 실행
 return 0;
}
런타임에 함수의 동작을 바꾸는 동적 바인딩의 효과가 있습니다.
 
7. 상속
 - 그런거 없습니다. C는 객체지향 언어가 아니니까요 :)
위와 같이 만들어진 파일들의 헤더를 각각 include 하여 만들고자 하는 구조체에 void* 의 형태로 조립하면 됩니다.
이렇게요 :)
#include "man.h"
#include "animal.h"
#include "action.h"
#include "like.h"
typedef struct
{
 void* animal;
 void* action;
 void* like;
}MAN_T;
void *man_open(void)
{
 MAN_T *man = (MAN_T *)malloc(sizeof(MAN_T));
 man->animal = animal_open();
 man->action = action_open();
 man->like = like_open();
 return man;
}

지난 화에 이어서 이번에도 7가지 팁을 알아보았습니다.
사실 이게 무언가 공인된 내용은 아니구요.
제가 코드를 짤 때 사용하는 소소한 습관들을 모아 놓은 것이니, 반드시 맞아 떨어지리라는 보장은 없습니다. ^^;
 
 보통 C로 객체지향 코드를 짤 때에는 구조체 정의를 헤더에 올리고, 인터페이스 함수들을 함수포인터를 사용하여 구조체에 넣는 방식으로
마치 C++이나 JAVA에서 ani->bark(); 와 같은 형태로 메소드를 호출할 수 있게 만들어 놓은 경우가 많은데요.
 이것이 일단 만들어 놓으면 참 우아하고 강력한데, 그걸 만들기 위해 내부에서는 함수포인터가 변수처럼 날아다닙니다.--;
작업량도 많지만 함수포인터의 사용빈도가 높은만큼 코드 작성과 디버깅 할때 난이도가 높아요..
 
그래서 객체지향적인 특성을 최대한 잃지 않으면서 그 구조를 만들기 위한 작업량을 최소화 시키다 보니 대충 저런 규칙을 따르게 되었습니다.
이 방식의 장점은 헤더에 최소한의 함수만을 올려 놓기 때문에, 저 규칙을 따라 만든 소스들은 헤더를 인클루드하고 함수만 호출하면 아무데서나 잘 돌아갔었고 소스를 처음 보는 사람도 헤더만 보면 대충 사용법을 파악할 수 있기 때문에, 협업이나 유지보수에 유리한 면이 있었습니다.
파일의 내부와 외부가 잘 분리된 편이라 기능의 변경이나 디버깅도 상당히 편리한 편이구요.
 단점이 있다면...
다른 분이 저렇게 작업된 모듈을 수정할 때에는 저런 규칙을 따르지 않기 때문에, 여러사람이 작업하면 내부가 쉽게 지저분해지는 정도 일까요?...-_-

피드백이 있으면 댓글 달아주세요. 겸허히 받겠습니다. (__)
긴 글 읽어주셔서 감사합니다.
 
바로가기
C언어로 프로그램 짤 떄의 몇가지 요령 1편
전체 추천리스트 보기
새로운 댓글이 없습니다.
새로운 댓글 확인하기
글쓰기
◀뒤로가기
PC버전
맨위로▲
공지 운영 자료창고 청소년보호