본문 바로가기
언어/C언어

헤더파일?

by 배이즈 2025. 4. 4.
2025 KnockOn 부트캠프의 예시 코드를 토대로 작성 되었습니다

 

stdio.h, stdlib.h 등 여러 함수들이 담겨있는 헤더파일은 어떤 방식으로 만들어 질까? 

표준 헤더파일 (stdio.h, string.h, stdlib.h 등)은 컴파일러나 OS의 표준 라이브러리에 포함된다.

내부에는 보통 함수 원형 선언 + 매크로 + 구조체 등이 있다.

 

헤더파일에선 함수를 선언할 뿐, 구현(정의)은 보통 .c 또는 .lib 파일에 따로 존재한다.

예를 들어, int printf(const char *format, ...); 로 선언만 할 뿐 printf() 함수의 실제 구현은 libc 내부에 있다.


 

표준 헤더파일은 아니지만 하나의 예시 헤더파일을 분석해보자. 

다음은 hacker.h의 코드이다. 표준 헤더파일과 다르게 내부에 함수를 정의하였다.

//hacker.h
#pragma once //헤더파일이 한 번만 include되게 보장해주는 지시어
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

typedef struct hacker{
    char name[50];
    unsigned int age;
    char** skill_list;
    int skill_num;
    int level;
}hacker;

int input_name(hacker*);
int input_age(hacker*);
int input_new_skill(hacker*);

hacker* new_hacker() {
    hacker* new_ptr = (hacker*)malloc(sizeof(hacker));

    memset(new_ptr->name, '\0', sizeof(new_ptr->name));
    new_ptr->skill_num = 0;
    new_ptr->level = 1;

    new_ptr->skill_list = (char**)malloc(sizeof(char*));

    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);
    return scanf("%s",ptr->name);
}

int input_age(hacker* ptr){
    printf("Input the age : ");
    return scanf("%u",&ptr->age);
}

void increase_size(char***);

int input_new_skill(hacker* ptr){
    write(1, "Input new Skill : ", 18);
    
    char buf[50] = {0,};
    scanf("%s",buf);

    char* tmp = (char*)malloc(strlen(buf)+1);
    strcpy(tmp,buf);

    ptr->skill_num++;
    increase_size(&(ptr->skill_list));


    ptr->skill_list[ptr->skill_num-1] = tmp;
    return ptr->skill_num;
}

void increase_size(char*** skill_list) {
    if (*skill_list == NULL) {
        *skill_list = (char**)malloc(sizeof(char*));
        (*skill_list)[0] = NULL;
        return;
    }

    int org_size = sizeof(*skill_list) / sizeof(char*);

    char** new_ptr = (char**)malloc(sizeof(char*) * (org_size + 1));

    for (int i = 0; i < org_size; i++) {
        new_ptr[i] = (*skill_list)[i];
    }

    free(*skill_list);
    *skill_list = new_ptr;
}

 

이 코드는 [hacker]라는 구조체를 만들어서 name, age, level, skill_list 를 사용자로부터 입력받아 구조체에 저장하고, 나중에 출력하는 프로그램이다.

구조체 [hacker]

typedef struct hacker {
    char name[50];       
    unsigned int age;    
    char** skill_list;   
    int skill_num;      
    int level;           
} hacker;

 

  • char* 와 char**의 차이
    • char* : 1개의 문자열을 가리키는 포인터 : "hello"
    • char** : 문자열 포인터들의 배열을 가리키는 이중 포인터 : {"hello", "world"}
    • char* str = "hello";       // str ----> "hello"
      char** list = { "A", "B" };  // list ----> ["A" ----> "A", "B" ----> "B"]

skill_list는 여러 개의 스킬들을 저장하기 위한 문자열 리스트인 것이다.


함수 hacker*  new_hacker()

hacker* new_ptr = (hacker*)malloc(sizeof(hacker));

 

동적으로 구조체를 하나 생성하여 hacker 구조체의 크기만큼 초기화한다. 동적 메모리이므로 Heap에 할당된다.

memset(new_ptr->name, '\0', sizeof(new_ptr->name));
new_ptr->skill_num = 0;
new_ptr->level = 1;

new_ptr->skill_list = (char**)malloc(sizeof(char*));

 

name 배열을 \0 으로 초기화한 뒤, skill_list를 위한 공간을 확보한다. skill_num과 level도 초기값을 설정해준다.


함수 input_ 

1. input_name

int input_name(hacker* ptr){
    write(1,"Input the name : ",17);
    return scanf("%s",ptr->name);
}

 

scanf로 문자열을 받고 name[50]에 저장한다.

 

2. input_age

int input_age(hacker* ptr){
    printf("Input the age : ");
    return scanf("%u",&ptr->age);
}

 

나이를 입력받아 age에 저장한다.

 

왜 name함수에는 ptr로 받고 age함수에는 &ptr로 받을까?

  • scanf는 항상 '주소'가 필요하다.
  • name은 배열이므로 이미 '자기 자신의 주소'를 의미한다. 그래서 &ptr->name으로 받으면 오류가 뜬다.
  • age는 int형 변수이므로 &(주소)로 받아야 한다.
  • 배열의 이름은 '변하지 않는 포인터'와 같다.

 

 

포인터 배열과 2차원 배열의 차이

 

- 포인터 배열 : 문자열 포인터 10개를 저장하는 배열

   - 각각의 arr[i]는 문자열의 시작 주소를 가진다. 그 문자열은 어디든지 존재할 수 있다.(동적 할당, 상수 문자열 등)

   - 문자열은 따로따로 저장되므로 메모리가 불연속이다.(각 포인터가 따로 메모리를 가리킴)

      malloc() 등을 신경 써야한다. 하지만 문자열 길이가 자유롭다.

 

- 2차원 배열 : 문자열 n개를 저장할 수 있는 배열 

   - 연속적으로 저장되어 빠르지만 모든 줄이 고정된 길이를 가지므로 유연하지 않다.

 

 

3. input_new_skill

int input_new_skill(hacker* ptr){
    write(1, "Input new Skill : ", 18);
    
    char buf[50] = {0,};
    scanf("%s",buf);

    char* tmp = (char*)malloc(strlen(buf)+1);
    strcpy(tmp,buf);

    ptr->skill_num++;
    increase_size(&(ptr->skill_list));

    ptr->skill_list[ptr->skill_num-1] = tmp;
    return ptr->skill_num;
}

 

buf의 내용을 동적 메모리에 복사한 뒤에 skill_list에 추가하는 코드이다. 

왜 굳이 tmp라는 메모리를 만들까?

 

동적 메모리를 할당하는 이유

  • heap 메모리 (동적할당된 메모리 tmp) 는 프로그램 종료까지 유지가 된다. 
  • 하지만 stack 메모리(buf)는 함수가 끝나면 사라진다.

 

increase_size(리스트 크기를 동적으로 늘려주는 함수)를 이용해 skill_list의 크기를 증가시키고 입력받은 새 스킬을 추가해준다. 새로 추가된 공간의 맨 뒤에 tmp를 넣는다. 

skill_num은 현재 저장된 스킬의 개수이고 그 인덱스는 skill_num - 1 이라고 할 수 있겠다.

  • 문자열은 항상 맨 뒤에 \0 (null)이 있다는 것을 잊지 말자.
    • strlen(buf) + 1 을 하는 이유

함수 increase_size() 

void increase_size(char*** skill_list) {
    if (*skill_list == NULL) {
        *skill_list = (char**)malloc(sizeof(char*));
        (*skill_list)[0] = NULL;
        return;
    }

    int org_size = sizeof(*skill_list) / sizeof(char*);

    char** new_ptr = (char**)malloc(sizeof(char*) * (org_size + 1));

    for (int i = 0; i < org_size; i++) {
        new_ptr[i] = (*skill_list)[i];
    }

    free(*skill_list);
    *skill_list = new_ptr;
}

 

char** 타입 문자열 배열을 늘려서 저장 공간을 확장하는 함수이다.

 

입력 포인터가 NULL인 경우에는 새롭게 크기가 1인 char* 배열을 만들고,

그 값도 NULL로 초기화하여 빈 배열의 시작을 만든다. 

  • sizeof(skill_list)/sizeof(char)를 하면 둘 다 포인터니까 size가 같아서 org_size 값이 항상 1이 될 것 같은데?
    • skill_list는 malloc을 통한 메모리 할당으로 고정적인 char와 달리 크기가 가변적이다.
      따라서 두 요소의 크기가 달라질 수 있다.

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]);
    }
}

 

출력

Input the name : qwertyou
Input the age : 12
Input new Skill : SQLinjection
qwertyou's age is 12
qwertyou's level is 1

-----qwertyou's skill list-----
qwertyou's skill0 : SQLinjection

 

위 헤더파일은 표준 헤더와 다르게 함수 구현까지 포함한다고 하였다. 

그러면 왜 표준 헤더파일은 다른 라이브러리에 따로 구현을 할까?

 

헤더파일은 여러 파일에서 include될 수 있기 때문에 중복으로 정의될 오류를 방지하기 위함이다. 

또 컴파일 속도라던지 유지 보수를 위해 .h와 .c를 분리하기도 한다.  

최근댓글

최근글

skin by © 2024 ttuttak