C언어 - 프로그래밍 언어의 문법

이미지
1. 언어의 문법 인간이 사용하는 언어에는 매우 다양한 문법 이 존재하며, 이를 익히는 데에는 많은 시간이 필요하다. 명사, 동사, 시제, 어순 등 매우 많은 문법을 익혀야 한다. 프로그래밍 언어는 컴퓨터에게 무엇을 어떻게 할지 지시하기 위한 명확한 목적을 가진 언어이다. 비교적 적은 수의 문법 요소만으로도 의미의 표현과 실행이 가능하다. 사람이 쓰는 언어와 달리 프로그래밍 언어는 몇 가지 핵심 문법 만 이해하면 바로 사용이 가능하다. 2. C언어 문법 프로그래밍 언어는 다음과 같이 다섯 가지로 구성되어 있다. 변수 - 값을 저장한다. 연산 - 값을 계산한다. 문장 - 내용을 실행한다. 제어 - 실행 순서를 변경한다. 함수 - 여러 문장을 하나로 묶는다. 이 다섯 가지만의 문법만 이해해도 프로그램의 기본 구조를 이해할 수 있다. 2.1. 변수 (Variable) 변수는 값(value)을 저장하는 메모리 공간에 이름 을 부여한 것이다. 프로그램은 변수를 통해 데이터를 기억하고, 참조하고, 변경한다. 2.1.1. 변수 타입(Type) 변수 타입은 변수가 어떤 종류의 값을 저장 하며, 그 값을 어떻게 해석할지를 결정하는 규칙이다. 변수 타입에 의하여 저장 가능한 값의 형태, 메모리 사용 방식, 그리고 허용되는 연산의 종류가 결정된다. int count; count = count + 1 ; // int type stores integer values.   char grade = 'A' ; // char type stores a single character or small integer values.   float ratio = 0.75f ; ratio = ratio * 100.0f ; // float type stores floating-point (real) values. 2.1.2. 상수 상수는 값이 바뀌지 않는 변수 이다. C언어에...

실행 파일은 파일은 어떻게 동작하는가

1. Assembly

  • CPU가 이해하는 기계어는 2진(Binary) 코드이다. Assembly 는 기계어를 사람이 읽을 수 있도록 문자 형태의 명령어(mnemonic) 로 표현한 것이다. 
  • 따라서, 기계어(machine code)와 가장 유사한 프로그래밍 언어는 Assembly 이라고 할 수 있다. 
  • 기계어가 어떤 과정으로 실행되는지 확인하기 위하여 간단한 Assembly 프로그램을 살펴보고자 한다. 
다음 Assembly 코드는 "2 + 3"의 결과를 출력하는 동작을 한다.

; Ubuntu 24.04 x86-64 (NASM, ELF64)

section .data
    msg     db "result: "
    msg_len equ $ - msg
    newline db 0x0a

section .bss
    buf     resb 1

section .text
    global _start

_start:
    mov     al, 2
    add     al, 3
    mov     bl, al

    mov     rax, 1
    mov     rdi, 1
    mov     rsi, msg
    mov     rdx, msg_len
    syscall

    mov     al, bl
    add     al, '0'
    mov     [buf], al

    mov     rax, 1
    mov     rdi, 1
    mov     rsi, buf
    mov     rdx, 1
    syscall

    mov     rax, 1
    mov     rdi, 1
    mov     rsi, newline
    mov     rdx, 1
    syscall

    mov     rax, 60
    xor     rdi, rdi
    syscall
add.asm
  
; ============================================================
; Ubuntu 24.04 x86-64 (NASM, ELF64)
; ============================================================
 
section .data               ; Initialized data
    msg     db "result: "
    msg_len equ $ - msg
    newline db 0x0a
 
section .bss                ; Uninitialized data
    buf      resb 1         ; buffer for converting a single-digit number to ASCII
 
section .text               ; Executable code section
global _start
 
_start:
    ;----------------------------------------------------------
    ; [1] Calculate 2 + 3 (use 8-bit AL)
    ;----------------------------------------------------------
    mov     al, 2           ; al = 2
    add     al, 3           ; al = 2 + 3 = 5
    mov     bl, al          ; bl = 5 (preserve in BL because syscalls may overwrite RAX)
 
    ;----------------------------------------------------------
    ; [2] Print "result: " string (sys_write)
    ;----------------------------------------------------------
    mov     rax, 1          ; syscall: sys_write (rax, rcx, r11 may be clobbered)
    mov     rdi, 1          ; fd: stdout
    mov     rsi, msg
    mov     rdx, msg_len
    syscall
 
    ;----------------------------------------------------------
    ; [3] Convert result to ASCII and print
    ;----------------------------------------------------------
    mov     al, bl          ; al = 5 (restore result from BL)
    add     al, '0'         ; 5 + 48 = 53 '5'
    mov     [buf], al       ; store ASCII character in buf
 
    mov     rax, 1          ; syscall: sys_write
    mov     rdi, 1
    mov     rsi, buf
    mov     rdx, 1
    syscall
 
    ;----------------------------------------------------------
    ; [4] Print newline
    ;----------------------------------------------------------
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, newline
    mov     rdx, 1
    syscall
 
    ;----------------------------------------------------------
    ; [5] Exit program (sys_exit, code=0)
    ;----------------------------------------------------------
    mov     rax, 60         ; syscall: sys_exit
    xor     rdi, rdi
    syscall

1.1. Build

  • 이 Assembly 프로그램을 실행파일로 만들기 위해서는 다음과 같은 과정이 필요하다.


$ nasm -f elf64 -o add.o add.asm
$ ld -o add add.o

$ ./add
result: 5

1.2. Executable File

  • Linux (Ubuntu 24.04) 환경에서 동작하는 실행 파일은 ELF64(Executable and Linkable Format 64-bit) 형식을 가진다.


$ objdump -h add
Sections:
Idx Name           Size      VMA               LMA                    File off   Algn
  0 .text          0000006c 0000000000401000  0000000000401000         00001000   2**4
                   CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data          00000009 0000000000402000  0000000000402000         00002000   2**2
                   CONTENTS, ALLOC, LOAD, DATA
  2 .bss           00000004 000000000040200c  000000000040200c         00002009   2**2
                   ALLOC

$ objdump -s -j .text add
Contents of section .text:
 401000 b0020403 88c3b801 000000bf 01000000   ................
 401010 48be0020 40000000 0000ba08 0000000f   H.. @...........
 401020 0588d804 30880425 0c204000 b8010000   ....0..%. @.....
 401030 00bf0100 000048be 0c204000 00000000   ......H.. @.....
 401040 ba010000 000f05b8 01000000 bf010000   ................
 401050 0048be08 20400000 000000ba 01000000   .H.. @..........
 401060 0f05b83c 00000048 31ff0f05            ...<...H1...

$ objdump -s -j .data add
Contents of section .data:
 402000 72657375 6c743a20 0a                  result: .

$ objdump -s -j .bss add
Contents of section .bss:
 40200c 00000000

1.3. Executable Code

  • 코드 섹션(.text)은 일반적으로 Header 다음의 4KB 페이지 경계(0x1000 = 4096)인 0x401000에 배치된다. 
    • 이는 x86-64 Linux의 기본 배치 방식이다. 컴파일러 옵션 또는 보안 기능(ASLR) 등이 적용되면 이 주소는 달라질 수 있다.


$ objdump -d -M intel add
Disassembly of section .text:
0000000000401000 <_start>:
  401000:       b0 02                   mov    al,0x2
  401002:       04 03                   add    al,0x3
  401004:       88 c3                   mov    bl,al
  401006:       b8 01 00 00 00          mov    eax,0x1
  40100b:       bf 01 00 00 00          mov    edi,0x1
  401010:       48 be 00 20 40 00 00    movabs rsi,0x402000
  401017:       00 00 00
  40101a:       ba 08 00 00 00          mov     edx,0x8
  40101f:       0f 05                   syscall
  401021:       88 d8                   mov     al,bl
  401023:       04 30                   add     al,0x30
  401025:       88 04 25 0c 20 40 00    mov     BYTE PTR ds:0x40200c,al
  40102c:       b8 01 00 00 00          mov     eax,0x1
  401031:       bf 01 00 00 00          mov     edi,0x1
  401036:       48 be 0c 20 40 00 00    movabs rsi,0x40200c
  40103d:       00 00 00
  401040:       ba 01 00 00 00          mov     edx,0x1
  401045:       0f 05                   syscall
  401047:       b8 01 00 00 00          mov     eax,0x1
  40104c:       bf 01 00 00 00          mov     edi,0x1
  401051:       48 be 08 20 40 00 00    movabs rsi,0x402008
  401058:       00 00 00
  40105b:       ba 01 00 00 00          mov     edx,0x1
  401060:       0f 05                   syscall
  401062:       b8 3c 00 00 00          mov     eax,0x3c
  401067:       48 31 ff                xor     rdi,rdi
  40106a:       0f 05                   syscall

이 주소의 2진 코드 b0020403 88C3...는  다음과 같이 해석된다.


b0 02                   mov    al,0x2
04 03                   add    al,0x3
88 C3 mov bl,al ...

이것은 현재의 CPU가 실행하는 Instruction Code를 Assembly로 해석된 결과를 보여준다.

2. Execute

2.1. CPU Registers

  • CPU 레지스터는 CPU가 연산을 수행하기 위해 직접 사용하는 초고속 저장공간이며, 프로그램 실행의 핵심 동작이 모두 레지스터 기반으로 이루어진다.
x86 32bit CPU(IA-32) Register

2.2. OP Code to CPU Resisters

  • CPU는 명령어를 메모리에서 가져와(Fetch → Decode) μOp로 해석한다.
  • 연산을 수행한 뒤(Execute → Memory), 그 결과를 레지스터에 기록한다(WriteBack). 
  • 그 후 RIP를 다음 명령어로 증가시키며 이런 과정을 반복 수행한다. 

  • RIP(Register Instruction Pointer) 
    •  x86-64 CPU에서 다음에 실행할 명령어의 주소를 가리키는 레지스터
5-Stage Pipeline (Simplifiled x86-64 Model)

2.3. Execution Process

  • 빌드 단계에서 소스 코드는 기계어로 변환되어 ELF 실행 파일이 만들어진다. 
  • OS의 커널은 execve로 ELF를 로드해 세그먼트를 가상 메모리에 배치하고, RIP를 Entry Point로 설정해 실행을 시작한다. 
  • CPU는 이후 IF → ID → EX → MEM → WB 파이프라인을 반복하며 명령어를 수행한다. 

이 블로그의 인기 게시물

게이트 회로로 이해하는 컴퓨터 연산

C언어 - 프로그래밍 언어의 문법