programming language/C++

[C++ 기초] Java와 비교한 C++의 특징 5가지 정리

공대키메라 2026. 5. 18. 21:30

해당 글에서는 필자가 Java를 평소에 많이 쓰지만 

 

Java와 비교해서 C++에서는 어떤 특징이 있는지 알기 위해서 정리한다.

 

필자가 알아보고자 하는 핵심 특징은 다음과 같다.

 

1. 포인터와 참조자
2. 스택, 힙 메모리 직접 관리
3. 소멸자와 RAII
4. 복사 생성자, 이동 생성자
5. 컴파일 과정

 

정보를 찾으면서 최대한 영어 문서를 보려고 했다.

 

또한, 어느정도 공식력 있어보이는(?) 사이트에서 긁어서 정리를 했으니 참고 바란다.


목표

C++의 경우 상위 5개의 항목이 어떻게 Java와 다른지 이해한다.


 

1. 포인터와 참조자

Java에는 포인터 개념이 없지만 참조자는 있다.

 

Java에서는 참조자의 경우 개발자가 직접 지정하기보다는, 이미 사용하면서 이것은 Call By Value인지, Call By Refernce인지를 알고 알맞게 사용하면 된다.

 

Java의 경우 데이터형이 기본형과 참조형으로 나뉜다. 

 

1.1 Java - 기본형 (Primitive Type)

실제 값(Data) 자체를 스택(Stack) 메모리에 직접 저장하는 타입입니다. 자바에서 기본적으로 제공하는 8가지 자료형

 

  • 논리형: boolean (1바이트)
  • 문자형: char (2바이트)
  • 정수형: byte (1바이트), short (2바이트), int (4바이트), long (8바이트)
  • 실수형: float (4바이트), double (8바이트)

 

1.2 Java - 참조형 (Reference Type)

객체가 저장된 메모리의 주소값을 스택(Stack) 메모리에 저장하고, 실제 객체 데이터는 힙(Heap) 메모리에 저장하는 타입
  • 기본형을 제외한 모든 타입 (클래스, 인터페이스, 배열 등)
  • 예시: String, System, 개발자가 직접 만든 클래스(객체), 배열(int[]) 등

여기서 주목해야 할 점은 Java 의 경우 JVM에서 참조형들은 Heap메모리에서 관리한다는 점이다. 

 

그렇기 때문에 Java에서는 코드를 실행한다고 하면 Stack영역에 참조형 객체가 있다면 이를 Heap에서 Stack 영역에 적절하게 push하고 사용한다. 

 

그런데 이를 C++에 비교해서 보면 우리가 직접 메모리를 조정하는 일은 없다. 

 

개인적인 생각으로는 그래서 단순히 기본형이라서 값을 불러서 stack에 저장하고 사용하는것과, 참조형의 주소를 불러서 stack에 저장하고 사용하는것이 구분을 될 지 언정 사실 개발자는 이를 직접 손댈일은 없다.

 

그러면 C++에서는 포인터와 Reference를 어떻게 하고 있을까?

 

1.3 C++ - 포인터와 Reference

포인터(Pointer)

포인터(Pointer)는 다른 변수의 메모리 주소를 가지고 있는 변수다.

포인터는 메모리가 가리키는 위치에 접근하기 위해서 * 연산자로 역참조(dereferenced)해야 한다.

 

참조 혹은 레퍼런스(Reference)

레퍼런스(reference) 변수는 이미 존재하는 변수의 다른 이름 혹은 alias이다.

포인터 처럼 reference는 객체의 주소를 저장한다. 

 

int i = 3; 

// A pointer to variable i or "stores the address of i"
int *ptr = &i; 

// A reference (or alias) for i.
int &ref = i;

 

GeekForGeeks에서 이에 대해 잘 정리해줘서 표를 가져왔다.

 

레퍼런스와 포인터의 차이점 정리

관점 레퍼런스 포인터
Definition A reference is an alias for an existing variable. A pointer is a variable that stores the memory address of another variable.
Initialization Must be initialized when declared and cannot be reassigned. Can be initialized later and can be reassigned to point to different objects.
Nullability Cannot be null; must always refer to an object. Can be null, pointing to no object (e.g., nullptr).
Syntax Uses & for declaration and accessing. Uses * for declaration and dereferencing, & for address-of.
Dereferencing No dereferencing required, ca be used directly like a normal variable. Must be dereferenced using * to access the value it points to.
Void Type A reference can never be void. A pointer can be declared as void
Nesting Reference variable has only one/single level of indirection.   A pointer variable has n-levels/multiple levels of indirection i.e. single-pointer, double-pointer, triple-pointer
Pointer Arithmetic Cannot perform arithmetic operations (e.g., increment or decrement). Can perform arithmetic operations (e.g., increment or decrement).
Use Case Primarily used for simpler, more readable references to variables. Used for more complex memory management, dynamic memory allocation, and handling arrays.
Example int& ref = x; int* ptr = &x;

 

 

그리고 이 둘 중에서 뭘 써야 하는지 친절하게 설명해주고 있다.

 

 

Reference는 function 파라미터 와 리턴 타입에 사용!

 

Pointer는 포인터 산술이 필요할 혹은 NULL 포인터를 넘겨야 한다면 사용!


이게 무슨 말인지 예시를 통해 정리하자.

 

// 포인터 산술(Pointer Arithmetic)이 필요할 때
// 포인터: 못 찾으면 nullptr 반환 가능
int* findValue(int arr[], int size, int target) {
    for (int i = 0; i < size; i++) {
        if (arr[i] == target) return &arr[i];
    }
    return nullptr; // 없음을 표현
}


// -- 레퍼런스를 함수 파라미터에 쓰는 경우
// 값 복사 (비효율적 - 객체가 크면 복사 비용 큼)
void printName(string name) { ... }

// 레퍼런스 (원본을 직접 참조 - 복사 없음)
void printName(string& name) { ... }

// 읽기만 할 거면 const 레퍼런스가 관용적
void printName(const string& name) { ... }

// -- 레퍼런스를 리턴 타입에 쓰는 경우
// 값 리턴 (복사본 반환)
string getName() { return this->name; }

// 레퍼런스 리턴 (원본 자체를 반환)
string& getName() { return this->name; }

 

예시 코드를 Gemini에게 뽑아달라고 요청했고, 이를 참고하면 될 듯 하다.

 

2. 스택, 힙 메모리 직접 관리

스택과 힙!

 

Java에서는 이것을 우리가 관리할 필요가 없다. JVM이 알아서 관리해 주기 때문이다.

 

Java에서 stack과 heap을 공부하려면 JVM의 구조부터 알아야 한다.

 

내가 그린 JVM. 막 퍼가도 됌

 

우리가 알아갈 것은 Runtime Data Area에 위치한 JVM Stack과 Heap이다. 

 

Java 에서 Heap은 객체 인스턴스와 배열을 저장하며 GC가 이를 관리한다.

 

Stack은 JVM Stack이 Stack Frame 메소드를 호출한다.  Stack Frame은 메서드를 호출할 때 마다 Local Variacle, Operand Stack 그리고 Frame Data를 만든다.

 

다음 간단한 예시를 보자.

void method() {
    int a = 10;          // Stack에 저장 (Stack Frame의 Local Variable)
    String s = "hello";  // 참조값만 Stack, 실제 객체는 Heap
    Person p = new Person(); // p(참조)는 Stack, Person 객체는 Heap
}

 

C++에서는 이것을 어떻게 관리할까?

 

우리가 직접! 할당하고 해체해야 한다!

 

Java와 C++ 메모리 할당,해제 구분

구분 Java C++
할당 new malloc / new
해제 GC가 자동 free / delete 직접
누수 위험 거의 없음 해제 안 하면 메모리 누수
댕글링 포인터 없음 해제 후 접근 시 발생

 

여기서 댕글링 포인터(dangling pointer)는 이미 해제(free 또는 delete)되어 유효하지 않은 메모리 영역을 여전히 가리키고 있는 포인터를 말하는 것으로 dangle 의 단어 뜻을 알아보면 참 재미있다.

 

 

무언가 달려있다는 것이다. 쓰지 않는데 어딘가에 달려있기만 한 상황을 재치있게 표현한 것 같다.

 

하여간 C++에서 직접 메모리를 할당하고 해제하는것이 하도 문제가 되니, modern c++에서는 이러한 문제를 해결하고자 새로운 방식을 도입했다.

 

C++11 부터 도입된 스마트 포인터를 사용하면 된다! 

 

스마트 포인터를 사용하면 메모리 할당과 해제를 똑똑하게 자동화주기에 메모리 누수나 다른 위험들을 극적으로 줄인다!

 

어느정도는 C++도 이러한 신기능을 최대한 도입함으로 이를 해결할 수 있다고 한다.

 

스마트 포인터 vs GC 차이

구분 C++ 스마트 포인터 JVM GC
해제 시점 스코프 끝나는 즉시 (예측 가능) GC가 판단할 때 (예측 불가)
순환 참조 shared_ptr 끼리 순환 시 누수 발생 GC가 알아서 처리
런타임 오버헤드 거의 없음 Stop-the-World 발생
추적 방식 참조 카운팅 (count=0이면 해제) 살아있는 객체를 전부 추적

 

이 스마트 포인터에 대해서는 나중에 정리하도록 하겠다.

 

3. 소멸자와 RAII

소멸자? Java에서는 그런건 없다. 다만 자원을 할당하면 해제하는것 정도는 해줘야 한다. 

 

물론 메모리는 GC가 자동처리하지만, 그 외에도 Connection연결이라던가 그러한 경우에 Java도 직접 해줘야 한다.

 

 

C++ 에서는 객체가 스코프를 벗어날때 자동으로 호출되는 함수를 설정할 수 있다.

 

class Connection {
public:
    Connection()  { cout << "연결 열었다"; }  // 생성자
    ~Connection() { cout << "연결 닫았다"; }  // 소멸자 (~가 붙음)
};

void method() {
    Connection conn;  // 생성자 호출
    // ... 작업 ...
}   // 스코프 끝 → 소멸자 자동 호출 → 연결 자동 닫힘

 

Java로 치자면 AOP의 @After? 이거는 객체가 사라지는게 아니라 메서드가 끝날 때 실행되는거라 많이 다르고 try-with-resources랑 유사하다고 한다. 

 

RAII (Resource Acquisition Is Initialization)는 자원 확득이 초기화라는데 이게 무슨 말이지?

 

Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the lifetime of an object.

중략...

출처 : https://en.cppreference.com/cpp/language/raii

 

cppreference.com의 글을 읽어보니

RAII는 사용전에 획득해야 하는 리소스의 수명 주기를 객체의 수명에 연결하는 C++ 프로그래밍 기법이라고 한다.

 

그런데 이러한 흥미로운 글을 찾았는데, 이게 이름을 잘못 지어서 개발자들한테 바로 와닿지 않는다는 것이다. 

 

https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii

 

예시를 보자.

 

// Source - https://stackoverflow.com/a/18054738
// Posted by the_mandrill, modified by community. See post 'Timeline' for change history
// Retrieved 2026-05-17, License - CC BY-SA 3.0

RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation();  // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks

 

// Source - https://stackoverflow.com/a/18054738
// Posted by the_mandrill, modified by community. See post 'Timeline' for change history
// Retrieved 2026-05-17, License - CC BY-SA 3.0

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

 

이렇게 읽어보니 메모리 누수가 생기지 않게 그냥 생성자에서 자원을 열고, 소멸자에서 닫으라는 것이다.

 

그래서 뭔가 와닿지 않는다는 Stack Overflow 글이 보이는것으로 이해한다.

 

4. 복사 생성자, 이동 생성자

복사 생성자? 이동 생성자?

 

아니 생성자면 그냥 생성자 아닌가? 무슨 종류가 또 이렇게 있어?

 

Java에서는 생성자란 무엇인가?

 

w3schools에서 생성자는 객체를 초기화하기 위해 사용하는 특별한 메소드 라고 소개한다.

 

// Create a Main class
public class Main {
  int x;  // Create a class attribute

  // Create a class constructor for the Main class
  public Main() {
    x = 5;  // Set the initial value for the class attribute x
  }

  public static void main(String[] args) {
    Main myObj = new Main(); // Create an object of class Main (This will call the constructor)
    System.out.println(myObj.x); // Print the value of x
  }
}

 

물론 매개변수 생성자도 있다.

 

C++에서도 당연 기본 생성자, 매개변수 생성자가 있고 복사 생성자, 이동 생성자라는것도 있다.

 

4.1 기본 생성자 (Default Constructor)

기본 생성자는 어떠한 인자도 가지지 않는 생성자다. 파라미터가 없다. 또 제로-인자 생성자(zero-argument constructor)라고도 부른다.

 

4.2 매개변수 생성자 (Parameterized Constructor)

생성자한테 인자를 넘길 수 있게 한다. 

 

4.3 복사 생성자 (Copy Constructor)

복사 생성자는 동일한 클래스의 기존 객체를 사용하여 새 객체를 생성하는 데 사용되는 특별한 유형의 생성자다.

 

// 출처 : https://www.geeksforgeeks.org/cpp/copy-constructor-in-cpp/

#include <iostream>
using namespace std;

// Create a demo class
class A
{
  public:
    int x;
};

int main()
{

    // Creating an a1 object
    A a1;
    a1.x = 10;
    cout << "a1's x = " << a1.x << endl;

    // Creating another object using a1
    A a2(a1);
    cout << "a2's x = " << a2.x;

    return 0;
}

 

위 코드는 굳이 우리가 복사 생성자를 선언하지 않아도 이미 존재하는 객체를 복사할 수 있지만...

우리가 별도로 복사 생성자를 선언할 수 도 있다.

 

복사 생성자를 직접 정의하지 않으면 얕은 복사(shallow copy)가 일어난다.

 

그렇게 되면 댕글링 포인터(dangling pointer)가 생길 수 있으니 조심하자!

 

다음은 geekforgeeks에서 가져온 코드이다. 

 

// 출처 : https://www.geeksforgeeks.org/cpp/copy-constructor-in-cpp/

#include <bits/stdc++.h>
using namespace std;

class String
{
  private:
    char *s;
    int size;

  public:
    String(const char *str)
    {
        size = strlen(str);
        s = new char[size + 1];
        strcpy(s, str);
    }
    ~String()
    {
        delete[] s;
    }
    String(const String &old_str)
    {
        size = old_str.size;
        s = new char[size + 1];
        strcpy(s, old_str.s);
    }
    void print()
    {
        cout << s << endl;
    }

    void change(const char *str)
    {
        delete[] s;
        size = strlen(str);
        s = new char[size + 1];
        strcpy(s, str);
    }
};

int main()
{
    String str1("GeeksQuiz");

    // Create str2 from str1
    String str2 = str1;
    str1.print();
    str2.print();

    // Update the str2 object
    str2.change("GeeksforGeeks");

    str1.print();
    str2.print();
    return 0;
}

 

4.4 이동 생성자(Move Constructor)

이동 생성자는 데이터를 복사하는것 없이 하나의 객체를 다른 곳으로 옮기도록 도와주는 특별한 생성자다.

 

복사하는것 보다는 빠르다고 한다. 

 

왜 이동 생성자이지? 코드를 봐보자.

 

#include <iostream>
using namespace std;

class Geeks {
private:
    int* ptr;

public:
    // Constructor
    Geeks(int value) {
        // Dynamically allocate memory
        ptr = new int(value); 
        cout << "Constructor called\n";
    }

    // Move Constructor
    Geeks(Geeks&& obj) {
        cout << "Move Constructor called\n";
        // Steal the pointer
        ptr = obj.ptr;        
        obj.ptr = nullptr;    
    }

    // Destructor
    ~Geeks() {
        if (ptr != nullptr) {
            cout << "Destructor deleting data: " << *ptr << endl;
        } else {
            cout << "Destructor called on nullptr\n";
        }
        delete ptr;
    }

    // Display function
    void display() {
        if (ptr)
            cout << "Value: " << *ptr << endl;
        else
            cout << "No data\n";
    }
};

int main() {
    // Constructor is called
    Geeks obj1(42);              
    // Move constructor is called
    Geeks obj2 = std::move(obj1); 

    cout << "\nAfter move:\n";
    cout << "obj1: ";
    // Should show "No data"
    obj1.display();              
    cout << "obj2: ";
    // Should show "Value: 42"
    obj2.display();              

    return 0;
}

 

여기서 Java와는 다르게 그냥 std::move 해버린다. 

 

이동생성자는 copy없이 객체를 다른곳으로 옮기기 위해 사용하므로 효율적임을 기억하자!

 

이거는 좀 다른 말이긴 한데, 다음 코드를 좀 더 봐보자. 다음 코드는 복사 생성자를 사용하는 경우의 예시이다.

 

#include <iostream>

class MyBuffer {
private:
    int* data;
    size_t size;

public:
    // 1. 일반 생성자
    MyBuffer(size_t s) : size(s) {
        data = new int[size];
        std::cout << "[일반 생성자] " << size << " 크기의 메모리 동적 할당\n";
    }

    // 2. 복사 생성자 (Deep Copy)
    MyBuffer(const MyBuffer& other) : size(other.size) {
        std::cout << "[복사 생성자] " << size << " 크기의 새로운 메모리 할당 및 데이터 복사 (O(N) 비용 발생)\n";
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }

    // 3. 소멸자
    ~MyBuffer() {
        if (data != nullptr) {
            std::cout << "[소멸자] 메모리 정상 해제\n";
            delete[] data;
        } else {
            std::cout << "[소멸자] 빈 객체 소멸 (해제할 메모리 없음)\n";
        }
    }
};

int main() {
    std::cout << "--- 1. 원본 객체 생성 ---\n";
    MyBuffer bufferA(100); // 100 크기 배열 할당

    std::cout << "\n--- 2. 객체 대입 (임시 객체 취급 상황 가정) ---\n";
    // 이동을 시도하지만 이동 생성자가 없으므로 '복사 생성자'가 강제 호출됨
    MyBuffer bufferB = std::move(bufferA); 

    std::cout << "\n--- 3. 프로그램 종료 및 메모리 해제 ---\n";
    return 0;
}

 

 

다음의 예시는 이동 생성자를 추가한 예시이다.

 

#include <iostream>

class MyBuffer {
private:
    int* data;
    size_t size;

public:
    // 1. 일반 생성자 (기존과 동일)
    MyBuffer(size_t s) : size(s) {
        data = new int[size];
        std::cout << "[일반 생성자] " << size << " 크기의 메모리 동적 할당\n";
    }

    // 2. 복사 생성자 (기존과 동일)
    MyBuffer(const MyBuffer& other) : size(other.size) {
        std::cout << "[복사 생성자] 무거운 새로운 메모리 할당 및 복사 발생 (O(N))\n";
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }

    // ★ 3. 이동 생성자 (Move Constructor) - 새롭게 추가된 부분
    // 다른 임시 객체(other)의 필드 값을 내 필드로 그대로 가져오겠다고 콜론(:) 뒤에 선언합니다.
    MyBuffer(MyBuffer&& other) noexcept : size(other.size), data(other.data) {
        std::cout << "[이동 생성자] 새로운 메모리 할당 없음! 포인터 주소만 훔쳐옴 (O(1))\n";
        
        // [중요] 내 필드로 주소를 무사히 옮겨 담았으니, 원본(other)의 필드는 끊어버립니다.
        other.size = 0;
        other.data = nullptr; 
    }

    // 4. 소멸자 (기존과 동일)
    ~MyBuffer() {
        if (data != nullptr) {
            std::cout << "[소멸자] 메모리 정상 해제\n";
            delete[] data;
        } else {
            std::cout << "[소멸자] 빈 껍데기 소멸 (해제할 메모리 없음)\n";
        }
    }
};

int main() {
    std::cout << "--- 1. 원본 객체 생성 ---\n";
    MyBuffer bufferA(100); 

    std::cout << "\n--- 2. 객체 대입 (이동 생성자 호출) ---\n";
    // 이제 클래스에 이동 생성자(&&)가 존재하므로, 
    // 컴파일러는 복사 생성자 대신 이동 생성자를 맵핑하여 호출합니다.
    MyBuffer bufferB = std::move(bufferA); 

    std::cout << "\n--- 3. 프로그램 종료 및 메모리 해제 ---\n";
    return 0;
}

 

5. 컴파일 과정

C++에서의 컴파일 과정을 살펴보려고 한다. 

 

Java 는 컴파일과 인터프레터를 같이 사용하는 하이브리드 언어다.

 

내가 그린 JVM. 막 퍼가도 됌

 

C++은 컴파일언어이다. 그말은 C++은 실행 전에 기계레벨 언어로 변환해야 한다는 말이다. 

 

C++ 코드를 실행 가능한 이진파일로 만들기 위해서 C++ 컴파일러는 컴파일 모델을 사용한다. 

 

1. 전처리(preprocessing)

2. 컴파일링(compiling)

3. 어셈블링(assembling)

4. 링킹(linking)

 

내가 그런 구조

 

사실 이걸 일일히 내가 찾고 하는것보다는 AI에게 이를 정리해주기를 부탁했다.

 

C나 C++ 같은 컴파일 언어는 사람이 작성한 텍스트 코드(`.cpp`, `.c`)를 컴퓨터가 이해할 수 있는 이진수(0과 1) 형태의 기계어로 변환해야 합니다. 이 전체 과정을 통틀어 '빌드(Build) 과정'이라고 부르며, 해당 글에서는 이를 4가지 단계로 나누어 설명하고 있습니다.

각 단계가 왜 필요하고 무슨 일을 하는지 원리와 구조를 중심으로 쉽게 정리해 드리겠습니다.

---

### 전체 프로세스 요약

전체 흐름은 **[ 소스 코드 ] ➔ 전처리 ➔ 컴파일 ➔ 어셈블리 ➔ 링크 ➔ [ 실행 파일(.exe) ]** 구조로 진행됩니다.

---

### 1. 전처리 (Preprocessing)

* **쉽게 말하면:** 본격적인 컴파일을 시작하기 전에, 소스 코드의 텍스트를 정리하고 덧붙이는 '문서 편집 단계'입니다.
* **주요 역할:**
* **헤더 파일 결합:** `#include <iostream>` 같은 지시어를 만나면, 해당 라이브러리 파일의 소스 코드를 그대로 복사해서 우리 코드 맨 위에 붙여넣습니다.
* **매크로 치환:** `#define PI 3.14`가 있다면 코드 내의 모든 `PI`를 `3.14`라는 숫자로 바꿉니다.
* **주석 제거:** 컴퓨터에게 필요 없는 인간용 설명문인 주석(`//`, `/* */`)을 전부 지우고 공백으로 대체합니다.


* **결과물:** 텍스트가 모두 확장된 형태의 중간 소스 파일이 생성됩니다. (GCC 기준 `.i` 확장자)

### 2. 컴파일 (Compiling)

* **쉽게 말하면:** 전처리가 끝난 C++ 코드를 컴퓨터 하드웨어에 한 단계 더 가까운 **'어셈블리 언어(Assembly Language)'로 번역하는 단계**입니다. 일반적으로 말하는 컴파일러의 핵심 두뇌가 여기서 작동합니다.
* **주요 역할 (내부 구조):**
* **토큰화(Tokenization):** 코드를 의미 있는 최소 단위(키워드, 변수명, 연산자 등)로 쪼갭니다.
* **구문 분석(Parsing):** 쪼갠 토큰들이 언어의 문법에 맞게 작성되었는지 검사하고 트리에 담습니다. 문법 에러(`;` 누락 등)가 여기서 잡힙니다.
* **의미 분석 및 최적화:** 불필요한 연산을 줄이거나 코드를 더 효율적으로 바꿉니다. 예를 들어 코드에 `int a = 10 + 15;`가 있다면 실행할 때 계산하지 않도록 컴파일 시점에 `int a = 25;`로 미리 계산해 둡니다.


* **결과물:** CPU의 명령어로 이루어진 어셈블리 코드 파일이 생성됩니다. (GCC 기준 `.s` 확장자)

### 3. 어셈블리 (Assembling)

* **쉽게 말하면:** 어셈블리 언어로 된 텍스트 파일을 완전히 **컴퓨터가 이해할 수 있는 0과 1의 이진수(바이너리) 기계어로 바꾸는 단계**입니다.
* **주요 역할:** 어셈블러(Assembler)라는 도구가 하드웨어(CPU) 아키텍처에 맞는 기계어 명령어로 1:1 매핑하여 변환합니다.
* **결과물:** 오브젝트 파일(Object File)이 생성됩니다. (Windows는 `.obj`, Linux/GCC는 `.o` 확장자)
* *주의:* 이 단계의 파일은 기계어이긴 하지만 아직 독립적으로 실행할 수 없습니다. 왜냐하면 다른 파일에 있는 함수(예: `std::cout`)의 실제 메모리 주소가 어디인지 아직 모르기 때문입니다. 주소 자리가 비어 있는 '미완성 기계어' 상태입니다.



### 4. 링크 (Linking)

* **쉽게 말하면:** 조각조각 나 있는 여러 오브젝트 파일들과 라이브러리들을 **하나로 엮어서 최종 실행 파일(.exe)로 완성하는 단계**입니다.
* **주요 역할:** 링커(Linker)가 호출된 함수들의 실제 구현부가 어디 있는지 찾아서 연결해 줍니다.
* 예를 들어, 우리 코드에서 `std::cout`을 썼다면 표준 라이브러리 바이너리 파일(`.lib` 또는 `.dll`)에서 `std::cout`이 구현된 실제 메모리 주소를 찾아 우리 오브젝트 파일의 비어 있던 주소 창에 연결(Link)해 줍니다.


* **결과물:** 사용자가 최종적으로 실행할 수 있는 완성된 **실행 파일**이 생성됩니다. (`.exe` 또는 Linux의 바이너리 실행 파일)

---

### 요약 구조도

```
[ helloworld.cpp ] (내가 작성한 C++ 코드)
       │
       ▼  1. 전처리 (텍스트 치환 및 헤더 복사)
[ helloworld.i ]   (전처리 완료된 소스)
       │
       ▼  2. 컴파일 (상위 언어 -> 어셈블리 언어 번역)
[ helloworld.s ]   (어셈블리 명령어 코드)
       │
       ▼  3. 어셈블리 (어셈블리 -> 기계어 번역)
[ helloworld.o ]   (미완성 기계어 오브젝트 파일)
       │
       ▼  4. 링크 (라이브러리 및 타 오브젝트 결합)
[ helloworld.exe ] (최종 실행 파일)

```

각 단계는 소스 코드라는 고수준 언어적 표현을 하드웨어가 읽을 수 있는 저수준 물리 신호(바이너리)로 안전하고 효율적으로 변환하기 위해 철저히 분업화된 구조를 가지고 있습니다.

 


이번 글에서는 간단하게 C++에서의 특징들을 좀 찾아보았다.

 

해당 글의 주제는 사실 AI에게 C++을 학습하는데 있어서 내가 어떻게 하면 좋을까? 물어봤는데 Java와 차이점을 떠올리면서

 

글을 정리하면 좋을 것 같다는 추천을 받았다.

 

물론 이것보다 훨씬 많고 해당 글은 요약이 된 부분이 많지만 살짝 감이 잡히긴 했다.

 

가장 중요한 점은 무엇보다도 C++에서는 메모리를 직접 관리하다보니 이 메모리와 관련된 기법들이 필요하다는 점이다.

 

그래서 생성자에도 단순하게 쓸것인지 아닌지(이동생성자, 복사생성자) 그리고 포인터는 어떻게 사용할 것인지를 정의한다. 

 

 

요즘에는 언어에 대한 장벽이 낮아지고 있다고 본다. 

 

다만 컨셉을 명확하게 알고 이를 잘 설계하는것은 또... AI에게만 일임하기에는 문제가 많기 때문에 큰 그림에서 이를 하나하나 정복해나갈 예정이다.

 

다음에는 공부하면서 좀 더 자세한 C++ 내용들을 정리하고자 한다.

 

 

참조:

https://www.geeksforgeeks.org/cpp/pointers-vs-references-cpp/

https://medium.com/@naseefcse/mastering-memory-management-in-modern-c-essential-techniques-for-high-performance-applications-b3dc1b66b9f0

https://en.cppreference.com/cpp/language/raii

https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii

https://stackoverflow.com/questions/64378721/what-is-the-difference-between-the-copy-constructor-and-move-constructor-in-c

https://www.w3schools.com/JAVA/java_constructors.asp

https://www.geeksforgeeks.org/cpp/types-of-constructors-in-cpp/

https://www.geeksforgeeks.org/cpp/copy-constructor-in-cpp/

https://www.geeksforgeeks.org/cpp/move-constructors-in-c-with-examples/

https://dev.to/shreyosghosh/behind-the-scenes-of-c-compiling-and-linking-j0n