정글 TIL 33(03.30) "C - 헤더 중복 및 순환 참조 문제"

김동준·2024년 3월 31일
1

진심

목록 보기
2/15
post-thumbnail

여백

1. 에러

Project3. Virtual Memory에서 mmap을 구현하던 중 헤더 참조의 문제로 추정되는 에러가 발생했습니다.

filed 'spt' has incomplete type

Conflicting type for 'lazy_load_segment'

헤더를 추가하거나 코드의 위치를 변경하는 기존의 방식으로 해결할 수 없었습니다.

여백

문제 분석

gcc 컴파일링 과정
1. page_cache.c의 3번째 줄의 vm.h
2. vm.h의 32번째 줄의 file.h 참조
3. file.h의 5번째 줄의 process.h 참조
4. process.h의 5번째 줄의 thread.h 참조
5. thread.h에 있는 spt 참조 중 에러 발생(spt 구조체는 vm.h에 선언)

여백

2. 문제점

1. vm.h에서 spt 구조체는 완전하고, vm.h를 thread.h에 포함(include)했음에도 컴파일러가 불완전한 타입으로 인식하는 문제.
(이게 주된 원인이라 생각하여 코드를 추가하는 데에 집중했음)

// vm.h
struct supplemental_page_table { ... }
// thread.h
#ifdef VM
#include "vm/vm.h"
...
struct thread {
...
struct supplemental_page_table spt;

완전한 타입 : 해당 컴파일 시점에서 그 타입의 구현(Implement) 정보(enum, struct, union 등..)를 모두 알고 있는 타입

2. page_cache.c는 Project.4에 사용되는 코드임에도 현재(VM)에 영향을 미치는 문제.

여백

3. 기존의 문제 접근 방법

  • spt를 요소로 가지고 있는 쓰레드 위에 spt 구조체 선언
  • spt 구조체가 선언된 process.h를 include
  • spt 구조체의 원소들의 헤더 파일 포함
  • 작성한 코드 원점 재검토
  • EC2 재부팅, 재설치, 동료 학습, stack overflow, 조교님께 질문 등등..
  • 총 3일 소요

여백

조교님 답변(코치님 아이디)

해당 답변을 통해 컴파일 과정,순환 참조, 헤더 중복에 대해서 공부하다 #ifndef#pragma once에 대해 알게 됨

여백

4. 헤더 파일의 컴파일이란?

헤더 파일을 포함하면 헤더 파일이 필요한 각 소스 파일에 헤더 파일을 복사하는 것과 동일한 결과가 생성됩니다. - gcc.gnu

// header.h
char *test (void);
// program.c
#include "header.h"
int main (void)
{
  puts (test ());
}

컴파일러는 program.c를 읽을 때와 동일한 token stream을 보게 됩니다.

// program.c
char *test (void);
int main (void)
{
  puts (test ());
}

이러한 헤더의 컴파일은 PintOS와 같이 코드가 길어지고 구조가 복잡해질 때, 컴파일러가 헤더를 참조하는 과정에서 문제가 생길 수 있습니다.

여백

5. 순환 참조 및 헤더 중복

순환 참조 문제란?

순환 참조란 적게는 두 클래스가 서로를 참조하는 경우를 뜻합니다. 이 경우, 소스 코드에 클래스를 선언하고 있는 헤더를 include하여 해결합니다.
그러나 아래와 같이 여러 헤더가 순환 참조되는 경우도 발생할 수 있습니다.

여백

헤더 중복 문제란?

또한 분할 컴파일을 하다보면 사용자가 정의한 헤더 파일이 중복되는 경우가 발생할 수 있습니다. 헤더 파일의 중복은 헤더 파일 안에 정의한 것이 2번 중복 정의되어 에러를 발생시킵니다.


여백

6. 해결법

순환 참조 문제란?

  • 가장 좋은 방법은 헤더들이 단방향으로 참조하도록 설계하는 것입니다.
  • 아래와 같이 인터페이스를 추가하여 의존 관계를 역전시켜 헤더간의 의존 문제를 해결하는 방법도 있습니다.

여백

헤더 중복 문제

#ifndef HEADER_H
#define HEADER_H
// 내용
#endif
  1. 헤더 파일을 처음 include할 때는 HEADER_H가 정의되어 있지 않습니다. 그때만 HEADER_H를 정의하고 헤더 파일의 내용들도 컴파일하여 중복을 피하는 방법입니다.
    쉽게 말하자면 if(!userprog/syscall.h) { 내용 } 라고 볼 수 있습니다.
  1. #pragma once도 include guard로서 위 코드와 동일한 역할로 사용됩니다. 함께 사용될 수도 있습니다.

    '#pragma' 지시어는 언어 자체에서 전달되는 것 외에 컴파일러에 추가 정보를 제공하기 위해 C 표준에서 지정한 방법입니다. -gcc.gnu

여백

7. 에러 해결 과정

  1. page_cache.c는 Project.4에 사용되는 코드임에도 현재(VM)에 영향을 미친다. => page_cache.c를 #ifndef EFILESYS로 예외처리
  1. vm.h에서 spt 구조체는 완전하고, vm.h를 thread.h에 포함(include)했음에도 컴파일러가 불완전한 타입으로 인식한다.
    => 불필요한 process.h의 포함을 제거하여 및 단방향 참조로 코드 리팩토링

여백

해결

여백

8. 교훈

  • 소스 코드에는 필요한 헤더만, 의존성을 고려해서 설계하자.
  • 자신의 코드를 볼 때 시야가 좁아진다. 문제를 해결하는 시간이 일정 시간을 넘기면 주변 사람들에게 도움을 청하자.
  • "순환 참조"라는 개념을 알기 전까지 문제를 전혀 인식하지 못 했다. 문제가 발생하면 해당 문제에 대해서 공부해야 한다.
  • 트러블 슈팅하며 시도했던 방법들을 체계적으로 관리해야 한다. 같은 시도를 여러번 반복했다.
  • "STUDY HARD, BE PATIENT, STEP BY STEP" -리처드 파인만
  • 이러한 과정에서 극기(克己)가 중요하고, 내게 필요하다.

여백

출처

profile
고민하고 고뇌하는 개발자 (점심, 저녁 메뉴를)

0개의 댓글