
2025.04.06
WEEK04 :
동적 프로그래밍, 그리디 알고리즘
CSAPP 3장. 프로그램의 기계 수준 표현 (특히 3.4, 3.7, 3.8)
3.6 ,3.7 공부
| 이름 | 의미 | 용도 |
|---|---|---|
| CF (Carry Flag) | 가장 상위 비트에서 캐리(자리 올림)가 발생했는가? | Unsigned overflow 판별 |
| ZF (Zero Flag) | 연산 결과가 0인가? | 결과가 0인지 확인 |
| SF (Sign Flag) | 결과가 음수인가? | 결과 부호 판단 |
| OF (Overflow Flag) | 부호 있는 2의 보수 오버플로우 발생 여부 | Signed overflow 판별 |
leaq: 주소 계산용이므로 condition code 변경 안 함xor: 논리 연산 → CF, OF는 0으로 설정shl, shr: CF는 밀려나간 비트, OF는 0inc, dec: ZF, OF 설정하지만 CF는 유지됨
cmp: sub처럼 작동하지만 결과 저장 안 함, 조건 코드만 설정ZF가 1이면 두 피연산자가 같음test: and처럼 작동하지만 결과 저장 안 함, 비트가 설정되었는지 검사testq %rax, %rax → %rax가 0인지, 음수인지 확인 가능set 명령어로 0 또는 1을 저장
→ 조건 만족 여부를 바이트로 저장 (0 or 1)
조건부 jump
→ 조건에 따라 코드의 흐름을 다른 위치로 분기
조건부 데이터 이동 (CMOV 등)
→ 조건 만족 시 특정 레지스터나 메모리로 값 이동
setl (signed less), setb (unsigned below)
cmpq a, b는 같지만, setl(signed) vs setb(unsigned)는 다르게 동작| 명령어 | 형태 | 설명 |
|---|---|---|
jmp Label | Direct(직접) | 해당 라벨로 바로 이동 |
jmp *Operand | Indirect(간접) | 레지스터나 메모리에 저장된 주소로 이동 |
jmp .L1 // .L1 위치로 이동jmp *%rax // %rax에 저장된 주소로 이동
jmp *(%rax) // %rax가 가리키는 메모리 주소로 jump0x03: eb 03 // jmp +3 (→ 0x08)
0x0b: 7f f8 // jg -8 (→ 0x05)
`jmp`는 현재 위치(0x03) 다음(0x05) 기준 +3 → `0x08`에 jump
`jg`는 현재 위치(0x0b) 다음(0x0d) 기준 -8 → `0x05`에 jump
C의 **goto 스타일 코드**로 표현 가능 → 실제 어셈블리 흐름과 구조가 비슷해짐void cond(short a, short *p) {
if (a && *p < a)
*p
testq %rdi, %rdi ; if (a == 0) → je .L1
je .L1
cmpq %rsi, (%rdi) ; if (*p >= a) → jle .L1
jle .L1
movq %rdi, (%rsi) ; *p = a
.L1:
rep; ret
void cond_goto(short a, short *p) {
if (!a)
goto done;
if (*p >= a)
goto done;
*p = a;
done:
return;
}
if ((a) && (*p < a))는 사실 두 조건을 순차적으로 검사해야 함:a != 0 확인p < a 확인test와 cmp 두 개의 조건 분기가 생긴 것if문은 보통 jcc 명령어로 분기함cmov)cmov는 분기 없이 데이터만 선택적으로 이동result = (x < y) ? (y - x) : (x - y); 은 다음처럼 구현:subq %rdi, %rsi ; y - x → %rax
subq %rsi, %rdi ; x - y → %rdx
cmpq %rsi, %rdi
cmovge %rdx, %rax ; if x >= y → result = x - y| 항목 | 조건부 분기 (jcc) | 조건부 이동 (cmov) |
|---|---|---|
| 분기 예측 | 필요함 | 필요 없음 |
| 성능 | 예측 실패 시 느림 | 일정함 |
| 사용 조건 | 제한 없음 | 부작용 없고 가벼운 계산만 가능 |
| 대표 명령 | je, jg, jl 등 | cmove, cmovg, cmovl 등 |
cmov는 양쪽 표현식 모두 실행됨 → 예: 포인터 역참조, 함수 호출, 부작용 있는 연산은 사용 불가C 코드:
do {
body;
} while (cond);
goto 변환:
loop:
body;
if (cond)
goto loop;
방식 1: **Jump-to-Middle (JTM)**
goto test;
loop:
body;
test:
if (cond)
goto loop;
Og 수준에서 이 방식 사용방식 2: **Guarded-Do (GD)**
if (!cond)
goto done;
loop:
body;
if (cond)
goto loop;
done:
O1 이상에서 이 방식 사용switch는 다중 분기(multiway branching) 구조여러 개의 if-else보다 간결하고 빠름
Jump table을 사용하면 분기 시간이 일정 (O(1))
Jump Table 방식
점프 테이블 크기 = (최댓값 - 최솟값 + 1)
switch 인덱스 값으로 점프 테이블을 인덱싱하여 해당 주소로 jump
n이 100~106 사이일 때 → index = n - 100
index = switch_expr - base_case범위 초과하면 default case로 이동
if (index > max) goto default수백 개 case도 일정한 시간에 처리 가능
중복 case label(같은 label에 매핑), 누락된 case, fall-through(goto 없이 다음 label 실행) 등도 처리 가능
| 상황 | 점프 테이블 쓰나? | |
|---|---|---|
| case 값 연속적, 개수 많음 | switch (n) { |
case 1: ...
case 2: ...
case 3: ...
case 4: ...
} | ✅ 사용함 |
| case 값 띄엄띄엄, 개수 적음 | switch (n) {
case 1: ...
case 1000: ...
} | ❌ 조건문 사용 |
| 범위 너무 넓어서 테이블 낭비 심함 | | ❌ 조건문 사용 |
call 명령어: 현재 명령어 다음 주소(=복귀 주소)를 스택에 push하고, 호출 함수의 주소로 점프ret 명령어: 스택에서 복귀 주소를 pop해서, 그 주소로 점프| 인자 번호 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 64비트 | %rdi | %rsi | %rdx | %rcx | %r8 | %r9 |
| 32비트 | %edi | %esi | %edx | %ecx | %r8d | %r9d |
| 16비트 | %di | %si | %dx | %cx | %r8w | %r9w |
| 8비트 | %dil | %sil | %dl | %cl | %r8b | %r9b |
%rax를 사용| 자료형 | 크기 (바이트) | 크기 (비트) | 레지스터 사용 예 |
|---|---|---|---|
char | 1바이트 | 8비트 | %al, %sil 등 |
short | 2바이트 | 16비트 | %ax, %dx 등 |
int | 4바이트 | 32비트 | %eax, %edi 등 |
long | 8바이트 | 64비트 | %rax, %rdi 등 |
pointer | 8바이트 | 64비트 | %rsi, %rdx 등 |
레지스터가 부족할 때
→ 많은 지역 변수를 저장해야 할 경우
주소 연산자(&)를 쓸 때
→ &x처럼 변수의 주소를 요구하면 메모리에 있어야 함
배열이나 구조체를 사용할 때
→ 메모리에 저장되어야 접근 가능
| 분류 | 레지스터 | 특징 |
|---|---|---|
| callee-saved | %rbx, %rbp, %r12–%r15 | 호출된 함수(Q)가 책임지고 복구해야 함 |
| caller-saved | 나머지 (예: %rax, %rcx, %rdx, %rsi, %rdi, %r8–%r11) | 호출하는 함수(P)가 저장하고 복구해야 함 |
| 스택 포인터 | %rsp | 항상 예외, 조작 시 매우 주의 필요 |
재귀 호출에서도 스택 프레임이 각각 따로 생기기 때문에, 각 호출은 자기만의 지역 저장 공간을 갖고 충돌하지 않음.