C/C++ 코드는 시스템에서 어떻게 컴파일됩니까?

우리 모두는 제품(예: Unreal Engine을 사용하여 만든 게임) 또는 생성/개발 프로젝트로 C 또는 C++ 언어를 사용했을 수 있습니다. 음, 가장 인기 있고 오늘날에도 Python, Golang과 같은 많은 언어의 기반으로 존재합니다. 그러나 C 또는 C++ 코드가 시스템에서 어떻게 컴파일되는지 궁금한 적이 있습니까? 뭐 사실 저도 처음에는 몰랐는데 나중에 선배님들의 좋은 팁을 알게 되었어요. 그럼 들어가 볼까요😁



필요한 도구?



작동 원리를 알기 전에 널리 사용되는 GNU의 GCC Compiler를 사용하는 것이 좋습니다. LLVM Clang(GCC보다 낫다)을 사용하여 시도해 볼 수도 있습니다!

행동 중!



자, 시작하려면 일반 C++ Hello World 코드를 입력해 보겠습니다. 결국 내가 간단한 것으로 시작한 이유를 이해하게 될 것입니다.

// Hello.cpp
#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World! \n";
    return 0;
}



자, 이제 g++ Hello.cpp 또는 clang++ Hello.cpp를 사용하여 코드를 컴파일하겠습니다.
a.out (Windows의 경우 a.exe)라는 출력 파일을 실행하면 꽤 직선적인 출력을 얻을 수 있습니다. 의심의 여지가 없습니다.

Hello World!



이제 흥미로운 점을 만들기 위해 코드를 다시 컴파일하되 이번에는 -S 플래그를 사용합니다.

// For GCC
gcc -S Hello.c (for C)
g++ -S Hello.cpp (for C++)

// For Clang
clang++ -S Hello.cpp

Hello.s라는 파일이 나오게 되는데 내용을 읽어보면 일반적인 구문과는 조금 다르고 조금 더 읽어보면... 다 어셈블리 형식으로 인코딩되어 있습니다!

다음은 예입니다(컴파일러, 계산 속도, 사용된 프로세서 등에 따라 다름).

// From GCC (arrows are presented for better view)
    .file   "Hello.cpp"
    .text
    .section    .rodata
    .type   _ZStL19piecewise_construct, @object
    .size   _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:   <--- Initialize step
    .zero   1
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
.LC0:                          <--- Which function to execute?
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:                          <--- Main operation
.LFB1522:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rsi
    leaq    _ZSt4cout(%rip), %rdi
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1522:
    .size   main, .-main
    .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2006:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    cmpl    $1, -4(%rbp)
    jne .L5
    cmpl    $65535, -8(%rbp)
    jne .L5
    leaq    _ZStL8__ioinit(%rip), %rdi
    call    _ZNSt8ios_base4InitC1Ev@PLT
    leaq    __dso_handle(%rip), %rdx
    leaq    _ZStL8__ioinit(%rip), %rsi
    movq    _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
    movq    %rax, %rdi
    call    __cxa_atexit@PLT
.L5:
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2006:
    .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
    .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2007:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $65535, %esi
    movl    $1, %edi
    call    _Z41__static_initialization_and_destruction_0ii
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2007:
    .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I_main
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long    1f - 0f
    .long    4f - 1f
    .long    5
0:
    .string  "GNU"
1:
    .align 8
    .long    0xc0000002
    .long    3f - 2f
2:
    .long    0x3
3:
    .align 8
4:


Hello World 프로그램에 대한 많은 코드를 볼 수 있습니다! (조금 충격적인 것 같죠?! 네, 저도 이해했습니다) 이해를 돕기 위해 조금 설명하겠습니다...

    .file   "Hello.cpp"
    .text
    .section    .rodata
    .type   _ZStL19piecewise_construct, @object
    .size   _ZStL19piecewise_construct, 1


이 섹션에는 C++ 파일에 대한 메타데이터와 파일에 대한 몇 가지 정보가 포함되어 있습니다.

_ZStL19piecewise_construct:
    .zero   1
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
.LC0:
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:
.LFB1522:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rsi
    leaq    _ZSt4cout(%rip), %rdi
    call    


위의 코드에서 _ZSt... 섹션은 포인터의 초기화를 나타내며 코드의 주요 부분은 .LC0 및 메인 섹션에서 수행됩니다. LC0에는 함수에 대한 정보와 실행 위치(이 경우 주 함수)가 있습니다. 기본 섹션에서 데이터 및 포인터 처리에 대해 조금 볼 수 있습니다. 흥미로운 사실은 모두 어셈블리로 인코딩된다는 것입니다.

이제 약간 복잡한 것을 살펴보겠습니다(당황하지 마세요 xD). 숫자 2개 추가!

#include <iostream>

using namespace std;

int main()
{
    int a=5, b=10;
    cout << a+b << "\n";
    return 0;
}


-S 플래그로 프로그램을 컴파일하면 어셈블리 파일을 얻을 수 있습니다. 그것에 대한 중요한 세부 사항을 살펴 보겠습니다.

main:
.LFB1522:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $5, -8(%rbp)  <-- NOTE HERE
    movl    $10, -4(%rbp) <-- NOTE HERE
    movl    -8(%rbp), %edx
    movl    -4(%rbp), %eax
    addl    %edx, %eax
    movl    %eax, %esi
    leaq    _ZSt4cout(%rip), %rdi
    call    _ZNSolsEi@PLT
    leaq    .LC0(%rip), %rsi
    movq    %rax, %rdi
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc


위의 주요 섹션에서 레지스터가 값 5와 10을 저장하는 데 사용되고 있음을 알 수 있습니다. 자세히 살펴보겠습니다...

movl    $5, -8(%rbp)
movl    $10, -4(%rbp)


따라서 값은 대상 레지스터로 이동하고 결국 어셈블리 코드의 다른 부분에서 작업을 수행하도록 만들어집니다.

이것은 일반적으로 C 또는 C++가 어셈블리 모드로 컴파일되는 방법이며 빙하의 상위 레이어만 표시했습니다(예, 약간 깊습니다!). 또한 어셈블리 모드 등에서 실행하는 동안 액세스하기 위해 포인터를 사용하는 방법에 대한 블로그 게시물도 곧 공유할 예정입니다. 그때까지, 나중에 보자! ;)

좋은 웹페이지 즐겨찾기