csapp
겨우 하루? 안했는데 간만인 기분이다
할게 졸라게 많으니 가능한 간략하게 중요한 포인트만 기록하겠다
연산들은 보통 명령어 $Class$인데 그 이유는 명령아가 피연산자에 따라 여러가지 변형을 가질 수 있기 때문이다
근데 leaq
명령어는 그런거 없다 한다
예시)
add
명령어 $class$의 경우
addb
: 바이트(byte) 연산
addw
: 워드(word, 2바이트) 연산
addl
: 더블워드(double word, 4바이트) 연산
addq
: 쿼드워드(quad word, 8바이트) 연산
등 이렇게 가질 수 있다
보통 이렇게 네 가지 크기의 데이터에 대해 명령어를 갖는다 한다
나눠진 네그룹
유효 주소 계산(load effective address)
단항 연산(unary)
이항 연산(binary)
시프트 연산(shifts)
이항 연산은 두 개의 피연산자를 가지며,
덧셉, 곱셈 등
단항 연산은 한 개의 피연산자를 가진다
$|n| = \pm n$ 과 같이 절댓값 등
그리고 여기서 다룰 정수 산술 연산 명령어들에 대한
표를 여기에 넣어놓겠다
명령어 | 효과 | 설명 | |
---|---|---|---|
leaq S, D |
$D ← \&S$ | 유효 주소 적재(load effective address) | |
inc D |
$D ← D + 1$ | 증가(increment) | |
dec D |
$D ← D − 1$ | 감소(decrement) | |
neg D |
$D ← -D$ | 부호 반전(negate) | |
not D |
$D ← ~D$ | 비트 반전(complement) | |
add S, D |
$D ← D + S$ | 덧셈(add) | |
sub S, D |
$D ← D − S$ | 뺄셈(subtract) | |
imul S, D |
$D ← D × S$ | 곱셈(multiply) | |
xor S, D |
$D ← D ^ S$ | 배타적 논리합(Exclusive-or, XOR) | |
or S, D |
$D ← D | S$ | 논리합(OR) | |
and S, D |
$D ← D \& S$ | 논리곱(AND) | |
sal k, D |
$D ← D « k$ | 왼쪽 시프트(left shift, sal과 shl은 동일) | |
shl k, D |
$D ← D « k$ | 왼쪽 시프트(left shift) | |
sar k, D |
$D ← D »A k$ | 산술적 오른쪽 시프트(arithmetic right shift) | |
shr k, D |
$D ← D »L k$ | 논리적 오른쪽 시프트(logical right shift) |
leaq
명령어가 왜 변형없는지 알려준다
이유는 메모리의 값을 참조하는게 아닌
단순히 그 주소만 갖고 오기 떄문이다
movq
와 비교하자면
movq는 메모리 주소에 있는 값을 레지스터에 가져온다
leaq는 메모리 주소 계산식의 결과(즉, 주소 자체) 만 레지스터에 넣어준다
(실제로 그 주소의 값에는 접근하지 않음)
메모리 참조 형식의 주소 계산식 결과만을 레지스터에 저장하는 leaq 명령어는 포인터 연산, 배열 인덱스 산술 등 복잡한 주소 산술을 효율적으로 수행한다
단항연산의 예시와 이항연산 알려준다
incq (%rsp)
이 경우 단항 연산으로 값을 1만큼 증가시킨다
이항 연산은
두 번째 피연산자가 소스이자 목적지 역할이다
x -= y
랑 비슷하단다 (C에서)
단, 중요한건 첫번째 오는게 소스, 두번째가 목적지다
subq %rax, %rdx
위의 경우 %rdx
레지스터 값을 %rax
만큼 감소시킨다는 뜻이다
주의할 점은 둘다 메모리나 레지스터 위치가 될 수 있지만
둘 다 메모리 위치이면 안된다는거다
그리고 두번째가 메모리 위치일 경우엔 저장 위치가 바뀐 것이기에
메모리 읽기 뿐 아니라 메모리 쓰기까지 작동해서
속도와 비용측면에서 차이가 발생한다
단항 연산(unary)은 하나의 피연산자에, 이항 연산(binary)은 두 번째 피연산자에 결과를 저장하며, 이 순서와 ATT 어셈블리의 피연산자 순서를 주의해야 한다
처음은 시프트 양 그다음 시프트할 값이 지정된다
시프트 연산이 뭔지는 알테니 스킵하겠다
연산뒤에 붙는 단위에 따라 시프트 단위도 바뀐다
sal
과 shl
은 ㄹㅇ 똑같다
sar
과 shr
하고 라임 맞추려고 2개인거 뿐임
sal
shl
모두 오른쪽에서 0으로만 채운다
그치만 sar
shr
은 다르다
sar
는 산술적 시프트 (부호 비트 복사로 채움)
Shift Arithmetic Right
의 약자shr
는 논리적 시프트 (0으로 채움)
Shift Right
의 약자Logical Shift Right
을 걍 관례적으로 Shift Right
으로 부른다고 한다)로 차이점이 존재한다
왼쪽/오른쪽 시프트 연산은 즉시값이나 %cl
레지스터로 시프트 양을 지정하며, 산술/논리적 오른쪽 시프트는 부호 비트 처리에서 차이가 있다. sal
과 shl
은 동작이 완전히 같다
부호 없는 산술 연산이나 2의 보수 산술 연산이나 대부분 걍 쓰면 된다
shr
, sar
과 같은 오른쪽 시프트 연산자만 조심해주면 되는데
2의 보수 방식이기에 이도 큰 차이 없이 연산 가능하다
이것이 2의 보수방식이 선호되는 이유다
2의 보수 방식이 뭔지 모른다면 아래로
🔽🔽🔽🔽🔽
대부분의 산술·논리 명령은 부호/무부호 구분 없이 동작하며, 오른쪽 시프트만 부호에 따라 명령이 달라지고, 컴파일러는 여러 연산을 위해 레지스터 재활용 및 값을 이동시킨다
128비트 곱셈·나눗셈은 한 번에 처리할 수 없기 때문에,
x86-64에서는 %rdx
:%rax
레지스터 쌍을 하나의 128비트 값처럼 취급해서 연산을 수행한다
imulq
, mulq
: %rax
와 소스(레지스터/메모리/즉시값)의 곱을 계산해서
결과 하위 64비트는 %rax
,
상위 64비트는 %rdx
에 저장한다.
idivq
, divq
: 128비트 피제수(%rdx
:%rax
)를 소스(레지스터/메모리/즉시값)로 나눔
몫은 %rax
,
나머지는 %rdx
에 저장
cqto
: %rax
의 부호 비트를 %rdx
로 확장해서,
이 방식 덕분에 64비트 CPU에서도
128비트 곱셈·나눗셈(부호/무부호 모두)을 기본 명령으로 안전하게 처리할 수 있다.
128비트 곱셈/나눗셈은 %rdx
:%rax
쌍으로 고정 처리하며, 곱셈은 mulq
/imulq
, 나눗셈은 divq
/idivq
, 부호 확장은 cqto
로 준비한다
(부호/무부호 연산 모두 지원됨)