코드를 느껴바라

[C/C++] C언어 프로젝트에서의 모듈화 본문

개발/임베디드(Embedded)

[C/C++] C언어 프로젝트에서의 모듈화

feelTheCode 2025. 12. 23. 14:45

대형 C 언어 프로그램과 모듈화의 필요성

대형 C 언어 프로그램을 개발할 때 모듈화는 선택이 아니라 필수다.
코드 규모가 커질수록 다음과 같은 문제가 발생합니다.

  • 하나의 파일에 로직이 집중되어 가독성 저하
  • 기능 수정 시 연쇄적인 버그 발생
  • 테스트와 유지보수가 어려워짐
  • 협업 시 코드 충돌 증가

이러한 문제를 해결하기 위한 핵심 개념이 바로 모듈화입니다.


OOP 언어에서는 어떻게 모듈화할까?

C++, Java와 같은 객체지향 언어에서는 보통 다음과 같은 방식으로 모듈화를 진행합니다.

  • Interface
  • Abstract Class
  • 다형성(Polymorphism)

이를 통해 구현과 사용을 분리하고, 의존성을 낮춘 구조를 만들 수 있습니다.

그렇다면 절차지향 언어의 대표 주자 C 언어는 어떨까요?


C 언어에는 Interface가 없다

C 언어에는 클래스도 없고, 인터페이스도 없습니다.
하지만 그렇다고 해서 모듈화를 할 수 없는 것은 아닙니다.

C 언어는 언어 차원에서 제공하는 가장 강력한 도구를 통해
객체지향적인 설계를 흉내 낼 수 있습니다.

그 도구가 바로 함수 포인터(Function Pointer) 입니다.


정답: 함수 포인터의 사용

C 언어에서의 모듈화 핵심은 다음 한 줄로 요약할 수 있습니다.

C 언어에서는 함수 포인터를 이용해 인터페이스와 유사한 구조를 만든다

함수 포인터를 사용하면 다음과 같은 구조가 가능합니다.

  • 기능 정의(인터페이스 역할)
  • 실제 구현 분리
  • 런타임에 구현 교체 가능
  • 의존성 감소

함수 포인터란?

함수 포인터는 함수를 가리키는 포인터입니다.

int add(int a, int b) {
    return a + b;
}

int (*func_ptr)(int, int);
func_ptr = add;

int result = func_ptr(3, 4);
  • func_ptrint(int, int) 형태의 함수를 가리킴
  • 함수 이름은 함수의 시작 주소
  • 함수처럼 호출 가능

함수 포인터로 인터페이스 흉내 내기

1. 인터페이스 역할의 구조체 정의

typedef struct {
    int (*open)(void);
    int (*close)(void);
    int (*read)(char *buf, int size);
} DeviceOps;

이 구조체는 인터페이스 역할을 합니다.


2. 실제 구현체 작성

int uart_open(void) {
    return 0;
}

int uart_close(void) {
    return 0;
}

int uart_read(char *buf, int size) {
    return size;
}

3. 구현체를 인터페이스에 연결

DeviceOps uart_ops = {
    .open = uart_open,
    .close = uart_close,
    .read = uart_read
};

4. 사용하는 쪽에서는 구현을 모름

void device_test(DeviceOps *ops) {
    ops->open();
    ops->read(NULL, 0);
    ops->close();
}
  • 실제 구현이 UART인지, SPI인지, File인지 알 필요 없음
  • 완벽한 구현과 사용의 분리

이런 구조의 장점

  • ✅ 모듈 간 결합도 감소
  • ✅ 테스트용 Mock 구현 가능
  • ✅ 유지보수 용이
  • ✅ 대형 프로젝트 구조에 적합
  • ✅ 객체지향 설계 개념 적용 가능

정리

  • 대형 C 언어 프로그램에서는 모듈화가 필수
  • C++/Java는 Interface로 해결
  • C 언어에는 Interface가 없음
  • 대신 함수 포인터를 활용
  • 구조체 + 함수 포인터 = C 스타일 인터페이스
  • 결과적으로 객체지향적인 설계가 가능

반응형