[함수 기초]

 

별거 없음

 

[스택 프레임]

 

함수 호출시 내부적으로는 스택 프레임을 사용한다.

프로그램 실행 시 스택 프레임의 크기는 정해진다.

함수끼리 돌려 쓰는 메모장 같은 느낌이다.

 

레지스터를 추적해보면 매개변수-반환 주소값-지역변수 순으로 쌓이게 된다.

 

[지역 변수와 값 전달]

 

별거 없음

 

[호출 스택]

 

호출 스택을 보는 연습을 계속 해야한다.

프로그래밍 하면서 가장 많이 보게되는 것중 하나이다.

 

[함수 마무리]

 

오버로딩 : 중복 적재/정의. 함수 이름의 재사용

매개변수 갯수가 다르거나 타입이 다른경우에만 가능

 

스택 오버플로우: 스택 메모리가 꽉 차서 터지는 경우

 

[Text RPG #1, #2]

 

여기도 뭔가 딱히 없음

 

모든 함수 다 접는 단축키는 Ctrl+M+O

'C++ > Rookiss C++' 카테고리의 다른 글

[포인터] #2/2  (0) 2022.08.27
[포인터] #1/2  (0) 2022.08.26
[코드의 흐름 제어]  (0) 2022.08.26
[데이터 갖고 놀기]  (0) 2022.08.25
[어셈블리 언어 입문]  (0) 2022.08.25

[조건문]

 

별거 없음

 

switch-case는 정수 계열만 넣을 수 있다.

 

[반복문]

 

별거 없음

 

do-while은 쓰는걸 거의 본 적이 없음

for, while은 어셈블리 cmp, jmp로 이뤄져있다.

 

[열거형]

 

const를 이용한 상수화는 전역변수 선언시 VS 기준으로는 메모리에 잡히지 않고 상수로 대체가 되는 반면 주소값을 얻어오거나 스택영역에 선언될 경우 메모리에 잡히게 된다.

하지만 열거형은 완벽하게 상수로 대체되기 때문에 어디에 선언하던간에 메모리에 잡히지 않는다.

 

define보다는 enum을 사용하는게 낫다.

디버그 단계에서 define 내용이 날아가서 알아보기 어렵다.

 

정리하자면,

const 상수화는 컴파일러에 따라 메모리에 잡힐수도, 안잡힐수도 있다.

define 상수는 빌드시 전처리 단계에 하드코딩으로 박히기 때문에 디버그때 값의 추적이 어렵다.

enum은 메모리에 잡히지 않고 디버그에서 값의 추적도 잘 된다.

 

'C++ > Rookiss C++' 카테고리의 다른 글

[포인터] #2/2  (0) 2022.08.27
[포인터] #1/2  (0) 2022.08.26
[함수]  (0) 2022.08.26
[데이터 갖고 놀기]  (0) 2022.08.25
[어셈블리 언어 입문]  (0) 2022.08.25

거의 대부분은 아는 내용이라서 내용이 짧을것 같아 섹션을 묶어서 씀

 

[정수]

 

개인 프로젝트면 그냥 4바이트 8바이트 남발해도 별 문제가 없을수도 있지만 메모리가 늘 부족한 콘솔이나 모바일 또는 네트워크 통신이라면 아낄건 최대한 아끼는것이 좋다.

 

unsigned 사용 여부는 상황에 따라 다를 수 있다.

 

정수 오버플로우: 값의 범위를 초과하는 문제

 

[불리언과 부동소수점]

 

불리언 : 참/거짓만 갖고있는 값. 1바이트 정수에 불과함

어셈블리에서도 최소 단위인 al, ah도 1바이트이므로 표현값이 1비트여도 1바이트를 쓰게됨

 

부동 소수점

float는 뒤에 f를 붙여줘야함

float : 부호1비트 + 지수8비트 + 유효숫자23비트

double : 부호1비트 + 지수11비트 + 유효숫자 52비트

 

부동 소수점은 언제나 근사값이다. 수가 커질수록 오차 범위도 매우 커진다.

실수 두개를 == 으로 비교하는 것은 지양

 

더보기
// ex) -3.375라는 값을 저장
// 1) 2진수 변환 = (3) + (0.375) = 0b11 + 0b0.011 = 0b11.011
// 0.375 = 0.5*0 + 0.25*1 + 0.125*1 = 0b0.011
// 2) 정규화 0b1.1011 * 2^1
// 1(부호) 1(지수) 1011(유효숫자)
// 단, 지수는 unsigned byte라고 가정하고 숫자+127 만들어줌
// 예상 결과 : 0b 1 1000000 1011000..00
// 메모리를 열어보면 리틀 엔디안으로 저장되어 있는 것을 확인 할 수 있음

 

[문자와 문자열]

 

char: 알파벳 / 숫자 문자

wchar_t : UTF-16 유니코드 문자

 

유니코드는 표기 방식이 여러개다. 대표적으로 UTF-8, UTF-16

UTF-8 : 알파벳/숫자 1바이트, 유럽 지역의 문자는 2바이트, 한글/한자 등 3바이트

UTF-16 : 알파벳/숫자/한글/한자 등 거의 대부분 문자 2바이트

 

cout은 char 전용이라서 유니코드 표현이 안된다. wcout을 써야하는데 한국어라면 앞서 wcout.imbue(locale("kor")) 를 사용해야 한다.

 

Escape sequence: 표기하기 애매한 것들. tab, linefeed 등등

 

[산술 연산]

 

다른 얘기지만 #pragma region ~~ #pragma end region으로 범위의 내용을 접었다 폈다 가능

 

b = a++, b = ++a는 디스어셈블리로 열어보면 mov와 add의 순서가 다른것을 확인할 수 있다.

 

[비교 연산과 논리 연산]

 

비교 연산들은 값이 0 또는 1로 튀어나온다.

 

[비트 연산과 비트 플래그]

 

~ : bitwise not. 단일 숫자의 모든 비트를 뒤집음

& : bitwise and. 두 숫자의 모든 비트 쌍을 대상으로 and를 함

| : bitwise or. 두 숫자의 모든 비트 쌍을 대상으로 or를 함

^ : bitwise xor. 두 숫자의 모든 비트 쌍을 대상으로 xor를 함

<< : 비트열을 n만큼 왼쪽으로 이동. 넘치는 비트는 버림

>> : 비트열을 n만큼 오른쪽으로 이동. 넘치는 비트는 버림. 부호 비트가 존재할 경우 부호 비트를 따라감

 

비트단위로 뭔가 할때는 unsigned 타입으로 만들어 주는게 정신건강에 좋다.

 

bitmask: 원하는 비트 값만 추출. (ex: bool invincible = ((flag & (1 << 3) != 0);)

 

어떤 캐릭터의 상태이상 같은것을 검사할 때 유용하다.

 

[const와 메모리 구조]

 

하드코딩을 지양하라.

한번 정해지면 절대 바뀌지 않을 값들. const를 붙였으면 초기화가 반드시 이루어져야함.

 

데이터 영역에서도 초기값이 있는 경우 .data, 없는 경우 .bss, 읽기 전용은 .rodata에 들어간다.

상수라고 해도 const가 반드시 .rodata 영역에 들어가지는 않는다. C++ 표준은 아니고 컴파일러 마음이다.

하지만 웬만해서는 지역 변수로 사용하면 스택 영역에 들어간다.

 

[유의사항 & 팁]

 

변수의 유효범위

스택은 중괄호의 범위가 생존 범위이다.

 

연산 우선순위

연산자의 우선순위를 고려하지 않으면 원치 않는 결과가 나올 수 있다.

 

타입 변환

큰 타입에서 작은 타입으로 변환시 위쪽 비트 데이터가 잘린 상태로 저장된다.

정수->실수 변환 시 정밀도 차이가 있기 때문에 데이터가 손실될 수 있다.

signed->unsigned 변환 시 비트 단위로 보면 똑같은데, 분석하는 방법이 다르기 때문에 값이 다르게 나온다.

 

사칙 연산

곱셈: 오버플로우 조심

나눗셈: 0 나누기 조심. 정수끼리 나눈 결과를 신경 써야함

'C++ > Rookiss C++' 카테고리의 다른 글

[포인터] #2/2  (0) 2022.08.27
[포인터] #1/2  (0) 2022.08.26
[함수]  (0) 2022.08.26
[코드의 흐름 제어]  (0) 2022.08.26
[어셈블리 언어 입문]  (0) 2022.08.25

어셈블러? -> 번역기

 

[레지스터 기초]

 

주로 a,b,c,d를 자주 사용함

 

rax(64bit)-eax(32bit)-ax(16bit)-ah/al(8bit)

 

하위비트 범위의 값을 바꿔도 상위비트의 값엔 영향을 미치지 않는다.

 

[변수와 레지스터]

 

변수는 그냥 데이터를 저장하는 바구니

 

data 섹션: 초기화 된 데이터. db(1바이트)-dw(2바이트)-dd(4바이트)-dq(8바이트)

bss 섹션: 초기화 안 된 데이터. resb-resw-resd-resq

 

mov를 통해 데이터를 레지스터에 넣는것은 값이 아닌 주소를 넣는 것이다.

mov rax, a : a라는 바구니의 주소값을 rax에 복사

mov rax, [a] : a리는 바구니 안의 값을 rax에 복사

 

[문자와 엔디안]

 

저장된 데이터는 변하지 않는데 어떻게 분석하느냐의 차이이다.

 

리틀 엔디안: 숫자가 바이트 단위로 역순으로 저장(ex: 0x12345678 -> 0x78, 0x56, 0x34, 0x12)

장점 - 캐스팅에 유리함

 

빅 엔디안: 숫자가 정순으로 저장(ex: 0x12345678 -> 0x12, 0x34, 0x56, 0x78)

장점 - 숫자 비교에 유리함

 

우리가 사용하는 시스템은 보통 리틀 엔디안 방식이다.

 

[사칙 연산]

 

add a, b : a = a + b

a는 레지스터 or 메모리 / b는 레지스터 or 메모리 or 상수

a, b 모두 메모리는 안됨

 

곱셈과 나눗셈은 지정된 레지스터를 사용하고 조금 까다롭다

 

[시프트 연산과 논리 연산]

 

산술 시프트: 최상위 부호 비트는 유지된다

시프트로 밀려난 비트는 분실된다

 

시프트 연산은 왜 하는걸까?

곱셈, 나눗셈이 빠름

ex) 게임서버에서 ObjectID를 만들 때

 

응용 사례

bitflag: 예를들어 플레이어의 상태를 비트마다 의미를 부여함. and연산으로 해당 비트가 1인지 0인지 판단할 때 쓸 수 있음

 

동일한 값으로 xor을 두번 하면 원래 값으로 돌아오게 됨. 암호학에서 유용함. (value xor key)

자기 자신을 xor 하면 0이 나옴

 

[분기문]

 

특정 조건에 따라서 코드 흐름을 제어하는 것

 

cmp와 jmp를 이용한다.

더보기
    ; 분기문 (if)
    ; CMP dst, src (dst 기준)
    ; 비교를 한 결과물을 flag register 저장
    
    ; JMP [label] 시리즈
    
    ; 두 숫자가 같으면 1, 아니면 0을 출력하는 프로그램
    mov rax, 10
    mov rbx, 10
    
    cmp rax, rbx
    
    je LABEL_EQUAL
    
    mov rcx, 0
    jmp LABEL_EQUAL_END
    
LABEL_EQUAL:
    mov rcx, 1
LABEL_EQUAL_END:
    PRINT_HEX 1, rcx
    NEWLINE
    
    mov ax, 100
    mov bl, 2
    div bl
    cmp ah, 0
    je L1
    mov rcx, 0
    jmp L2
L1:
    mov rcx, 1
L2:
    PRINT_HEX 1, rcx
    NEWLINE

 

[반복문]

 

cmp, jmp를 이용해서 구현할 수도 있고 loop를 사용할 수도 있다

더보기
    mov ecx, 10

LABEL_LOOP:
    PRINT_STRING msg
    NEWLINE
    dec ecx ; sub ecx, 1과 동일
    cmp ecx, 0
    jne LABEL_LOOP
    
    ; 연습 문제) 1에서 100까지의 합을 구하는 프로그램
    
    mov ecx, 0
    mov eax, 0
    
LABEL_LOOP2:
    inc ecx
    add eax, ecx
    cmp ecx, 100
    jne LABEL_LOOP2

    PRINT_DEC 4, eax
    NEWLINE
    
    ; loop [라벨]
    ; - ecx에 반복 횟수
    ; - loop 할 때 마다 ecx 1 감소. 0이면 빠져나감. 아니면 라벨로 이동
    
    mov ecx, 100
    xor ebx, ebx
LABEL_LOOP_SUM:
    add ebx, ecx
    loop LABEL_LOOP_SUM
    
    PRINT_DEC 4, ebx
    NEWLINE

 

[배열과 주소]

 

더보기
    ; 배열과 주소
    ; 배열: 동일한 타입의 데이터 묶음
    ; - 배열을 구성하는 각 값을 배열 요소(element)라고 함
    ; - 배열의 위치를 가리키는 숫자를 인덱스(index)라고 함
    
    ; 주소
    ; [시작 주소 + 인덱스 * 크기]
    
    
    mov rax, a
    
    ; 연습문제: a배열의 모든 데이터 출력해보기
    
    xor ecx, ecx
    
LABEL_PRINT_A:
    ;PRINT_HEX 1, [a+ecx]
    ;NEWLINE
    inc ecx
    cmp ecx, 5
    jne LABEL_PRINT_A
    
    xor ecx, ecx
LABEL_PRINT_B:
    PRINT_HEX 2, [b+ecx*2]
    NEWLINE
    inc ecx
    cmp ecx, 5
    jne LABEL_PRINT_B
    
    xor rax, rax
    ret
    
section .data
    msg db 'Hello World', 0x00
    a db 0x01, 0x02, 0x03, 0x04, 0x05 ; 5*1 = 5바이트
    b times 5 dw 1 ; 5*2 = 10바이트

 

[함수 기초]

 

더보기
    ; 함수 (프로시저 procedure 서브루틴 subroutine)
    
    ;call PRINT_MSG
    
    mov eax, 10
    mov ebx, 15
    call MAX
    PRINT_DEC 4, ecx
    NEWLINE
    
    
    xor rax, rax
    ret
    
PRINT_MSG:
    PRINT_STRING msg
    NEWLINE
    ret
    
    ; ex) 두 값중 더 큰 값을 반환하는 max
    ; 근데 두 값을 어떻게 넘겨받지? 반환은 어떻게?
    ; eax와 ebx 입력값을 ecx에 반환하면 어떨까
MAX:
    cmp eax, ebx
    jg L1
    mov ecx, ebx
    jmp L2
    
L1:
    mov ecx, eax
L2:
    ret
    ; 만약 인자가 10개라면 어떻게 할까?
    ; eax, ebx에 이미 중요한 값이 있으면 어떻게 할까?
    ; .data .bss 사용하면?
    ; 인자를 도대체 몇개를 할당해야 하지?
    
    ; 다른 메모리 구조가 필요하다
    ; - 꿈이 유효한 동안에는 그 꿈을 유지시켜야 함(유효 범위의 개념)
    ; - 꿈이 끝나면 그 꿈을 부셔버려도 됨 (정리의 개념)
    ; - 꿈에서도 또 꿈을 꿀 수 있다는 것을 고려해야 한다 (유동적으로 유효 범위가 확장 가능)
    
    ; [!] 스택(stack)이라는 메모리 영역을 사용
    ; 함수가 사용하는 일종의 메모장
    ; - 매개 변수 전달
    ; - 돌아갈 주소 관리
    
section .data
    msg db 'Hello World', 0x00

 

[스택 메모리]

 

스택은 높은 주소에서 낮은 주소로 사용함.

call - ret 으로 실행 위치로 돌아갈 수 있는 이유는 스택에 다음에 실행해야 할 위치를 스택에 저장하기 때문이다.

 

BP는 SP를 잠시 고정시켜서 상대 주소를 계산하는데 유용하다.

BP를 관리하는 일련의 과정을 스택 프레임이라고 한다.

스택은 사용했으면 깔끔하게 정리도 해야 한다. 임의 조작으로 스택 위치가 어긋나면 크래시가 난다.

 

더보기
    ; 스택 메모리, 스택 프레임
    ; 레지스터는 다양한 용도로 사용
    ; -- ip (instruction pointer) : 다음 수행 명령어의 위치
    ; -- sp (stack pointer) : 현재 스택 top 위치 (일종의 cursor)
    ; -- bp (base pointer) : 스택 상대주소 계산용
    
    push rax
    push rbx
    push 5
    push 2
    call MAX
    PRINT_DEC 8, rax
    NEWLINE
    add rsp, 16 ; push를 두번 했으므로 8+8을 더해줘서 sp의 위치를 돌려놓는
    pop rbx
    pop rax
 
    
    xor rax, rax
    ret
    
MAX:
    push rbp
    mov rbp, rsp
    
    mov rax, [rbp+16]
    mov rbx, [rbp+24]
    cmp rax, rbx
    jg L1
    mov rax, rbx
L1:    
    pop rbp
    ret

'C++ > Rookiss C++' 카테고리의 다른 글

[포인터] #2/2  (0) 2022.08.27
[포인터] #1/2  (0) 2022.08.26
[함수]  (0) 2022.08.26
[코드의 흐름 제어]  (0) 2022.08.26
[데이터 갖고 놀기]  (0) 2022.08.25

+ Recent posts