C++의 컴파일이 느린 이유 (a.k.a 헤더시스템)

JellyPower·2023년 4월 25일
0

나만 몰랐던 C++

목록 보기
5/11
post-thumbnail

C++의 컴파일이 느린 이유

  • C++은 하나의 프로젝트를 여러개의 파일로 관리 가능하게 하기 위한 시스템으로헤더 파일시스템을 사용한다. (C++ 발표일: 1985년, C 발표일: 1972년)
  • 비교적 모던한 언어들인 Java, C#과 같은 언어들은 하나의 프로젝트를 여러개의 파일로 관리 가능하게 하기 위한 시스템으로 모듈시스템을 사용한다.(Java 발표일: 1995년, C#발표일: 2000년)
  • 헤더파일시스템은 내가 사용하고자 하는 기능이 들어있는 파일을 그대로 복사 붙여넣기 하는 방식으로 작동하기에 내가 원하지 않는 기능까지 같이 컴파일된다.
  • 모듈시스템은 내가 사용하고자 하는 기능만 그대로 가져오기에 내가 원하지 않는 기능까지 같이 컴파일되지 않는다.
  • 이것이 C++의 컴파일이 느린 이유이다.

C++의 헤더파일 시스템

  • 위에 말했듯 헤더파일시스템은 기본적으로 복붙하는 방식으로 작동한다.

  • 실제 C++에서 #include "헤더파일.h” 구문의 주된 역할은 내가 지정한 헤더파일(사실 헤더파일이 아니어도 상관 없음)을 복붙해 오라는 뜻이다.

  • 이를 도식화해보자면 내가 파일과 코드 구성을 다음과 같이 했을 때,

  • 실제 전처리를 진행하고 나면 실제 컴파일해야 하는 소스코드의 양은 다음과 같다.

(사실 C++에선 전처리 이후 실제 컴파일되는 파일들은 .cpp파일들밖에 없어서 A.h, B.h, C.h파일들은 컴파일되지 않는다. 위 도식은 이해를 쉽게 하기 위해 임의로 만들어낸 것임을 이해해 주길 바란다. 만약 해당 파일들도 A.cpp, B.cpp, C.cpp처럼 구현부가 나눠져 있다면 모두 컴파일 해야한다)

  • 헤더파일의 inlcude 계층이 깊어질수록 실질적으로 컴파일해야 할 코드의 양은 O(n^2) 만큼 늘어난다고 볼 수 있다.

  • 코드가 간단하면 상관 없겠지만 만약 사용하려는 코드가 매우 길다면 이는 큰 부담이 된다.

  • 우리가 흔히 사용하는 stdio.h만 하더라도 헤더파일만 2000줄이 넘는다(Visual Studio 기준). 만약 여기에 템플릿까지 포함된다면 컴파일시간은 종잡을 수 없이 길어질 것이다.

  • 특히 위처럼 include계층이 깊은 상황에서 C.h파일이 수정된다면 어떻게 될까? C.h가 아주 살짝만 바뀌어도 이를 포함하는 모든 파일들에 대해 컴파일을 다시 해야한다. 그러면 당연히 컴파일이 오래 걸릴 수밖에 없을 것이다.

헤더파일 예제

  • 실제 헤더파일 시스템이 작동하는 방식에 대한 이해를 위해 예제를 만들어봤다.
// C.h 파일

#pragma once
#include<cstdio> // 입출력 헤더

void funcInC() {
	printf("Function in C\n");

	printf("\n");
}
// B.h 파일

#pragma once
#include<cstdio>
#include "C.h" // C 헤더 추가

void funcInB() {
	printf("Function in B\n");

	printf("\n");
}
// A.h 파일

#pragma once
#include<cstdio>
#include "B.h" // B헤더 추가

void funcInA() {
	printf("Function in A\n");

	printf("\n");
}
// main.cpp 파일

#include "A.h" // A헤더만 추가해도 B, C헤더의 기능들을 모두 활용 가능하다.

int main() {

	funcInA(); // A.h의 함수
	funcInB(); // B.h의 함수
	funcInC(); // C.h의 함수
	// main.cpp에서 직접 추가하지 않은 파일들까지 전부 실행 가능하다.

	printf("cstdio is also included\n");
	// cstdio 헤더의 함수도 직접 includ되지 않았지만 쓸 수 있다.

	return 0;
}

C#의 모듈 시스템

  • 이에 반해, 비교적 모던한 C#, Java와 같은 언어들은 모듈시스템을 활용한다.
  • 모듈시스템은 파일의 모든 내용을 가져오는 것이 아닌 내가 활용하고자 하는 기능만을 해당 언어의 컴파일러가 알아서 분석해 가져오기에 C++처럼 헤더파일 하나 수정했다고 해당 헤더에 줄줄이 소세지로 엮여있는 모든 파일들을 다시 컴파일해야하는 상황을 막고있다.

모듈시스템 예제

// C.cs
using namespaceC;

namespace namespaceC
{
    internal class C
    {
        internal static void print()
        {
            Console.WriteLine("function in C class\n");
        }
    }
}
// B.cs
using namespaceC; // 사용하고자 하는 C.cs의 네임스페이스 추가

namespace namespaceB
{
    internal class B
    {
        internal static void print()
        {
            Console.WriteLine("function in B class\n");

            C.print(); // => 잘 된다.
        }
    }
}
// A.cs
using namespaceB; // 사용하고자 하는 B.cs의 네임스페이스 추가

namespace namespaceA
{
    internal class A
    {
        internal static void print()
        {
            Console.WriteLine("function in A class\n");

            B.print(); //=> 잘 된다.
            //C.print(); //=> 에러가 난다.
        }
    }
}
// Program.cs
using namespaceA; // 사용하고자 하는 A.cs의 네임스페이스 추가
// 헤더파일 시스템과는 다르게 B.cs, C.cs의 기능을 사용하려면 직접 using 해줘야 한다.

namespace Main
{
    class Program
    {
        static void Main(string[] args)
        {
            A.print();
            //B.print(); //=> 에러가 난다.
            //C.print(); //=> 에러가 난다.
        }
    }
}

C++의 모듈 시스템

  • C++에서도 이러한 헤더시스템의 컴파일 문제를 알고 이를 완화하기 위해 C++20부터 모듈시스템을 도입했다!
  • 그러나, 해당 글은 C++의 모듈시스템을 설명하기 위한 글이 아니기에 만약 궁금하다면 아래 글들을 참조하길 바란다.

C++17 모듈시스템!

[C++20] 모듈(module)

레퍼런스

씹어먹는 C++ - <20 - 1. 코드 부터 실행 파일 까지 - 전체적인 개요>

선언과 정의의 차이점

C++ 헤더파일 cpp파일로 왜 나눌까? 강의 강좌 프로그래밍 10강

profile
게임엔진코드싸개(진)

0개의 댓글