STUDY

[1주차 TIL] KnockOn Bootcamp Pre.Rev : 헤더파일

Dalseung 2024. 12. 2. 22:43

헤더파일이란

헤더파일

C언어에서 함수는 먼저 함수의 원형에 대해 선언이 되어 있어야한다.

표준함수 역시 선언이 되어야하는데, 이런 표준 함수의 원형 및 관련 정보가 들어간 파일을 헤더파일이라한다.

헤더파일은 끝에 .h 확장자가 붙으며, 선언시에는 앞에 #include를 사용해야 적용된다.

리눅스 기준으로 헤더파일은 /usr/include 디렉터리 밑에 있다.

image

헤더파일 VS 라이브러리

그렇다면 라이브러리처럼 또는 Python 처럼 모듈과 같은 것이 아니냐? 할 수 있지만, 엄연히 차이가 있다.

구분 헤더파일 라이브러리
구성 사람이 알아들을 수 있는 C언어 문법으로 작성 기계어로 번역된 바이너리
역할 함수선언, 매크로 정의, 데이터 구조 선언
함수와 변수의 선언부만 포함, 구현부는 없음.
함수 구현부, 동작코드가 포함된 파일
프로그램 실행 시 링크되어 동작을 수행
형식 .h파일 정적 : .lib 또는 .a파일
동적 : .dll파일, .so 파일
사용목적 코드의 재사용성과 가독성을 높이기 위해 사용 코드 중복을 줄이고 효율적으로 재사용하기 위해 사용

정적라이브러리 VS 동적라이브러리

구분 정적라이브러리 동적라이브러리
컴파일 시 동작 라이브러리 전체가 실행파일에 복사 라이브러리에 대한 참조만 실행파일에 포함
실행 시 동작 실행파일이 독립적으로 동작(라이브러리 파일 필요X) 라이브러리를 실행 시점에 로드(라이브러리 파일 필요)
파일 크기 파일이 크다 파일이 작다
메모리 사용 각 실행파일이 라이브러리 복사본을 포함 여러 프로그램이 라이브러리를 공유
업데이트 용이성 어려움(수정 시 다시 컴파일) 쉽다
실행속도 빠름(실행 시점에 로드시간이 필요X) 약간 느림
운영체제 의존성 낮음 운영체제에 따라 경로 설정 등이 필요

헤더파일 작성 방법

헤더파일 작성은 크게 어렵지 않다.

앞서 설명한 것처럼 헤더파일은 프로그램에 필요한 함수 선언, 매크로 정의, 데이터 구조 선언 등의 정의, 선언을 해놓은 파일이기 때문에 보통의 코딩을 할때, 선언부만 따로 만든 파일이라 생각하면 된다.

헤더파일 구조

#ifndef HEADER_FILE_NAME // 헤더 가드 시작
#define HEADER_FILE_NAME // 중복해서 헤더를 포함하지 않게 막아주는 역할
.
.
.
#endif // 헤더 가드 종료
  • 조건부 컴파일 : 조건에 따라 필요한 문장만 골라서 컴파일하는 것.
  • ifndef(if not define) : 만약 정의(선언) 되어있지 않다면 → #ifndef ~ #endif 사이 코드를 컴파일해라
    • 만약, 이미 정의가 되어있으면 #define을 넘김 → 즉, 중복해서 헤더를 선언했을 경우 그냥 넘겨서 컴파일에러를 안 일으킨다.

코드 분석 : main.c

//main.c
#include "hacker.h"

int main(){
    hacker* qwertyou = new_hacker();
    printf("%s's age is %d\n",qwertyou->name, qwertyou->age);
    printf("%s's level is %d\n",qwertyou->name, qwertyou->level);


    printf("\n\n-----qwertyou's skill list-----\n");
    for(int i=0;i<qwertyou->skill_num;i++){
        printf("%s's skill%d : %s\n",qwertyou->name, i, qwertyou->skill_list[i]);
    }
}
  • hacker* qwertyou = new_hacker();
    • hacker의 자료형 qwertyou라는 이름의 새로운 구조체를 생성
  • printf("%s's age is %d\n",qwertyou->name, qwertyou->age); printf("%s's level is %d\n",qwertyou->name, qwertyou->level);
    • qwertyou의 멤버변수를 출력한다.
  • for(int i=0;i<qwertyou->skill_num;i++){
        printf("%s's skill%d : %s\n",qwertyou->name, i, qwertyou->skill_list[i]);
    • 동일하게 for 반복문을 써서 qwertyou의 멤버변수를 출력한다.

코드 분석 : hacker.h

//hacker.h
#pragma once
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

typedef struct hacker{       // hacker이라는 이름의 구조체 생성
    char name[50];           // 멤버변수 name 배열 생성
    unsigned int age;        // 멤버변수 age 생성
    char** skill_list;       // 이중 포인터 skill_list 생성
    int skill_num;           // 멤버변수 정수형 skill_num 생성
    int level;               // 멤버변수 정수형 level 생성
}hacker;                     // alias(별칭)를 지정 : hacker

int input_name(hacker*);     // input_name() 함수 선언
int input_age(hacker*);      // input_age() 함수 선언
int input_new_skill(hacker*);// input_new_skill() 함수 선언

hacker* new_hacker() {       //hacker 객체 생성
    hacker* new_ptr = (hacker*)malloc(sizeof(hacker)); //구조체 메모리를 할당(malloc)

    memset(new_ptr->name, '\0', sizeof(new_ptr->name)); //이름 초기화
    new_ptr->skill_num = 0; //skill_num 0으로 초기화
    new_ptr->level = 1;     // level 1로 초기화

    new_ptr->skill_list = (char**)malloc(sizeof(char*)); //skill_list 동적으로 메모리 할당(malloc)

    input_name(new_ptr);   // 이름 입력을 받음
    input_age(new_ptr);    // 나이 입력을 받음
    input_new_skill(new_ptr);  // 첫번째 스킬을 입력받음

    return new_ptr;
}


int input_name(hacker* ptr){
    write(1,"Input the name : ",17); // write함수로 표준출력을 사용해서 이름을 입력하라는 메세지 출력
    return scanf("%s",ptr->name);   // 이름을 입력받아 ptr->name에 저장
}

int input_age(hacker* ptr){
    printf("Input the age : ");
    return scanf("%u",&ptr->age);  // 나이를 입력받아 ptr->age에 저장
}

void increase_size(char***);     // increase_size 함수 선언
int input_new_skill(hacker* ptr){
    write(1, "Input new Skill : ", 18); // write함수로 표준출력을 이용해 새로운 스킬을 입력하라는 메세지 출력

    char buf[50] = {0,};       // buf배열을 0으로 초기화
    scanf("%s",buf);           // 입력 받아서 buf에 저장

    char* tmp = (char*)malloc(strlen(buf)+1); // buf의 크기+1한 사이즈의 동적 메모리를 할당. => strcpy는 \0까지 추가하기 때문에
    strcpy(tmp,buf); // buf 내용을 복사해서 tmp에 넣음

    ptr->skill_num++;       // ptr->skill_num 변수 증가
    increase_size(&(ptr->skill_list)); // increase_size 함수 실행


    ptr->skill_list[ptr->skill_num-1] = tmp;  // tmp변수에 저장된 새로운 스킬을 skill_list 마지막 위치에 저장
    return ptr->skill_num;
}

void increase_size(char*** skill_list) {
    if (*skill_list == NULL) {    // skill_list가 Null이면
        *skill_list = (char**)malloc(sizeof(char*)); // 크기 1인 배열을 할당
        (*skill_list)[0] = NULL; // skill_list 첫 배열에 Null 삽입
        return;
    }

    int org_size = sizeof(*skill_list) / sizeof(char*); // skill_list 배열의 사이즈 계산

    char** new_ptr = (char**)malloc(sizeof(char*) * (org_size + 1));   // skill_list 배열 사이즈에 +1을 한 것을 new_ptr로 동적 메모리할당

    for (int i = 0; i < org_size; i++) {
        new_ptr[i] = (*skill_list)[i]; // for 반복문을 통해 new_ptr에 기존 skill_list 내용을 복사
    }

    free(*skill_list);     // skill_list를 메모리 해제
    *skill_list = new_ptr; // skill_list에 new_ptr 넣기
}

정리하면 다음과 같다.

  • new_hacker()
    • 새로운 hacker 객체를 동적으로 생성하는 함수.skill_list는 동적으로 메모리 할당(malloc), 초기 크기는 1.
      • input_name: 이름을 입력받음.
      • input_age: 나이를 입력받음.
      • input_new_skill: 첫 번째 스킬을 입력받음.
    • 세 가지 입력 함수:
    • 구조체 메모리를 할당 (malloc)한 후, 이름을 초기화(memset), 스킬 개수는 0, 레벨은 1로 초기화.
  • input_new_skill
    • 새로운 스킬을 입력받아 skill_list에 추가하는 함수.
    1. 입력받은 문자열을 buf에 저장.
    2. 동적 메모리 할당을 통해 입력받은 문자열의 복사본(tmp) 생성.
    3. 스킬 개수(skill_num)를 증가시킨 뒤, increase_size를 호출해 skill_list의 크기를 확장.
    4. 새로운 스킬을 리스트의 마지막 위치에 추가.
  • increase_size()
    • 스킬 리스트의 크기를 확장하는 함수.
    1. 초기 리스트가 NULL인 경우 크기가 1인 배열을 할당.
    2. 기존 리스트 크기(org_size)를 계산.
    3. 새 크기로 메모리를 재할당한 배열 생성.
    4. 기존 배열의 내용을 새 배열로 복사한 뒤, 기존 배열을 해제하고 새 배열로 교체.

끝.