3.2 Program Encodings의 시작
책은 먼저
“컴퓨터는 기계어를 실행한다”는 말로 출발해요.
여기서 제일 먼저 잡아야 하는 건
우리가 직접 보는 C 코드는
컴퓨터가 직접 실행하는 형태가 아니라는 점이에요.
컴퓨터가 실제로 실행하는 것은
machine code, 즉 기계어예요.
기계어는
낮은 수준의 연산들을 표현하는
바이트들의 나열이에요.
이 바이트들은
- 데이터를 조작하고
- 메모리를 관리하고
- 저장장치와 입출력을 다루고
- 네트워크와 통신하는
아주 기본적인 동작들을 나타내요.
즉 책은 처음부터
“우리가 보고 있는 프로그램의 진짜 모습은 기계어다”
이걸 깔고 시작해요.
그다음: 컴파일러는 이 기계어를 바로 만들지 않고, 중간에 어셈블리를 만든다
책은 이어서
컴파일러가 기계어를 한 번에 뚝 만드는 게 아니라,
중간 단계들을 거친다고 설명해요.
gcc 같은 C 컴파일러는
프로그래밍 언어 규칙,
목표 기계의 명령어 집합,
운영체제의 관례를 바탕으로
기계 코드를 만들어내요.
그런데 이 과정에서
먼저 assembly code를 생성해요.
어셈블리 코드는
기계어를 사람이 읽을 수 있게 적은
텍스트 표현이에요.
그리고 나서
- assembler가
어셈블리 코드를 실제 기계어로 바꾸고 - linker가
필요한 코드들을 연결해서
최종 실행 파일을 만들어요.
여기서 책 흐름상 중요한 건
아직 세부 명령어 설명으로 바로 안 들어간다는 점이에요.
먼저
“C 프로그램이 실행 파일이 되기까지의 경로”를 보여주고,
그다음
“우리가 앞으로 볼 것은 그중 어셈블리다”
이렇게 자연스럽게 독자를 끌고 가요.
즉 3.2 초반은
어셈블리가 왜 중요한지를 납득시키는 부분이에요.
그다음: 고급 언어에서는 많은 것이 숨겨져 있다
책은 이어서
C나 Java 같은 고급 언어를 쓸 때
우리는 기계 수준의 세부 구현으로부터
많이 보호받고 있다고 말해요.
이건 아주 중요한 연결이에요.
왜냐하면 독자가
“굳이 기계어까지 알아야 하나?”
이렇게 생각할 수 있으니까요.
책의 논리는 이래요.
- 고급 언어는 생산성과 안정성을 높여 준다
- 컴파일러는 타입 검사도 해주고
- 추상화를 제공해서 사람이 쉽게 프로그래밍하게 해준다
- 현대 컴파일러는 성능도 꽤 좋다
여기까지만 보면
어셈블리를 알 필요가 없어 보여요.
그런데 책은 여기서 방향을 틀어요.
“그래도 기계 수준을 이해하면 프로그램이 실제로 어떻게 표현되는지 알 수 있다”
“특히 포인터, 메모리, 함수 호출, 성능, 버그 분석에 도움이 된다”
즉 3.2는 단순히
“어셈블리 문법 소개”가 아니라,
왜 이 장을 공부해야 하는지를 설득하는 흐름으로 먼저 가요.
비유하면
자동차 운전자가 굳이 엔진 구조까지 몰라도 차는 탈 수 있어요.
하지만 엔진 구조를 알면
- 이상 소리를 더 빨리 알아차리고
- 문제 원인을 더 잘 추정하고
- 성능 차이도 이해할 수 있죠.
책이 3.2에서 말하는 것도 비슷해요.
3.2.1 Machine-Level Code
이제 책이 본격적으로
기계 수준 코드의 특징을 이야기해요.
여기서 흐름은
“고급 언어와 기계 수준은 무엇이 다른가”로 갑니다.
책이 강조하는 첫 포인트는
기계 수준 코드는
고급 언어보다 훨씬 구체적이고 단순하다는 거예요.
고급 언어에서는
- 변수 이름이 있고
- 자료형 이름이 있고
- 반복문과 조건문이 있고
- 함수 호출이 자연스럽게 보이죠
그런데 기계 수준에서는
이런 구조들이 그대로 남아 있지 않아요.
대신 프로그램은
- 레지스터 값
- 메모리 주소
- 산술 연산
- 분기
- 호출과 복귀
이런 기본 동작으로 표현돼요.
책 흐름상 여기서 중요한 건
“추상화가 벗겨진다”는 느낌이에요.
비유하면
C 코드는 완성된 가구 설명서예요.
- 의자 하나 조립하세요
- 다리 네 개를 연결하세요
- 나사를 조이세요
반면 기계 수준은
진짜 작업 지시예요.
- 왼손으로 부품 A를 집어라
- 오른손으로 드라이버를 잡아라
- 구멍 3번에 나사를 넣어라
- 시계방향으로 5번 돌려라
즉
기계 수준 코드는 훨씬 직접적이고 세부적이에요.
책은 이어서 기계 수준에서 중요한 프로그램 상태를 암시해요
3.2.1은 뒤 절들을 위한 준비 역할도 해요.
이 부분에서 독자가 감을 잡아야 하는 건
기계 수준 프로그램은 결국
몇 가지 핵심 저장 위치를 중심으로 움직인다는 거예요.
구체적으로는 뒤에서 더 자세히 다루지만,
지금 단계에서의 감각은 이거예요.
- 어떤 값은 레지스터에 있고
- 어떤 값은 메모리에 있고
- 다음 실행 위치는 프로그램 카운터가 정하고
- 직전 비교 결과는 조건 코드가 들고 있다
즉 프로그램은
“의미 있는 문장들의 집합”이라기보다
“상태를 조금씩 바꾸는 명령들의 연속”이에요.
책은 이걸 장황하게 철학적으로 말하진 않지만,
이런 시각을 독자에게 심어주고 다음으로 넘어가요.
그리고 x86-64라는 구체적 대상에 초점을 맞춘다
3.2.1은 일반론만 하고 끝나지 않아요.
책은 우리가 앞으로 볼 대상이
x86-64 machine code라고 분명히 해요.
즉 모든 기계어가 아니라
이 장에서는 x86-64를 본다는 거예요.
이게 중요한 이유는
기계 수준 표현은 아키텍처마다 다르기 때문이에요.
마치 같은 문장을
한국어, 영어, 일본어로 표현하는 방식이 다르듯이,
기계 수준 코드도 CPU 계열마다 달라요.
책은
“이제부터 우리는 x86-64 언어를 배운다”
이런 느낌으로 독자를 데려가는 거예요.
3.2.2 Code Examples
이제 책은
추상적인 설명만 하지 않고
실제 예제로 들어가요.
이 흐름이 자연스러워요.
- 앞에서는 “어셈블리가 중요하다”고 말했고
- 이제는 “그럼 실제로 생긴 건 어떻게 생겼나?”를 보여주는 거죠
책은 작은 C 함수 하나를 예로 들고,
그 함수가 어셈블리로 어떻게 바뀌는지 보여줘요.
여기서 책이 하려는 일은
어셈블리 전체를 한 번에 가르치는 게 아니에요.
오히려 독자에게
“아, C 코드가 이렇게 바뀌는구나”
하는 첫 인상을 주는 거예요.
예제에서 자연스럽게 보게 되는 것들은 이거예요.
- 함수 이름이 어셈블리 라벨로 나타난다
- 인자들은 특정 레지스터로 들어온다
- call로 함수 호출을 한다
- 결과는 %rax 같은 정해진 곳에 놓인다
- ret로 복귀한다
즉 C에서 하나의 함수는
어셈블리에서는
명령어 몇 줄의 흐름으로 드러나요.
비유하면
영화 대본에서 한 장면을 읽다가
실제 촬영 콘티를 보는 느낌이에요.
대본에서는
“철수가 문을 열고 들어온다”
이 한 줄이지만,
촬영 콘티에서는
- 카메라 위치
- 배우 동선
- 문 여는 타이밍
- 대사 전 호흡
이런 게 다 보이죠.
어셈블리도 그래요.
C에서는 한 줄처럼 보이는 게
기계 수준에서는 여러 단계로 풀어져요.
여기서 책은 실제 어셈블리 출력이 꽤 지저분하다는 걸 보여준다
예제 코드를 보여주면서
책은 실제 .s 파일의 내용을 같이 보여줘요.
그런데 그 안에는
우리가 당장 이해하고 싶은 연산 명령 말고도
여러 줄이 더 들어 있어요.
예를 들면
- .file
- .text
- .globl
- .type
- .size
이런 것들이요.
책은 여기서 중요한 구분을 시켜줘요.
이런 줄들은
CPU가 실행하는 명령어가 아니라
어셈블러와 링커를 위한 지시어라는 거예요.
이건 책 흐름상 되게 중요해요.
왜냐하면 초보자는 .s 파일을 처음 보면
모든 줄을 다 같은 급의 정보로 보게 되거든요.
책은 여기서 말해요.
“우리가 지금 집중해야 할 것은
실행 의미가 있는 명령어들이다.”
즉 실제 파일 전체를 그대로 외우는 게 아니라,
그 안에서 핵심을 추려 읽는 법을 가르치고 있어요.
비유하면
논문 첫 페이지를 봤을 때
- 제목
- 저자
- 소속
- 저작권 문구
- 본문
이 다 섞여 있잖아요.
그런데 우리가 진짜 읽고 싶은 건 본문이죠.
책은
어셈블리에서도 그런 식으로
“본문과 부가정보를 구분해서 읽어라”
이걸 알려주는 거예요.
3.2.3 Notes on Formatting
이제 책은
“그래서 앞으로 우리는 어셈블리를 이런 형식으로 보여주겠다”
라고 말해요.
이 흐름이 아주 자연스러워요.
- 실제 .s 파일을 보니 좀 지저분하다
- 그대로 보면 초보자에게 부담이 크다
- 그래서 책은 학습용 형식으로 정리해서 보여주겠다
책에서 앞으로 쓰는 형식은 대체로 이래요.
- 불필요한 지시어는 생략하고
- 함수 시그니처 비슷한 설명을 붙이고
- 인자가 어느 레지스터에 있는지 써주고
- 줄 번호를 붙이고
- 각 줄 오른쪽에 짧은 설명을 단다
이건 단순히 예쁘게 편집한 게 아니라
독자가 어셈블리를 읽는 시선을 훈련시키는 장치예요.
즉 책은 이제부터
어셈블리를 그냥 던져주지 않고,
읽는 법이 드러나는 형태로 보여주겠다는 거예요.
비유하면
원문 영어 문장을 바로 주는 대신
- 문장 번호 붙이고
- 핵심 단어 밑줄 치고
- 옆에 해설을 다는
그런 학습용 교재 형식과 같아요.
이 덕분에 독자는
명령어를 볼 때마다
- 이 줄이 무슨 역할인지
- 원래 C 코드의 어느 부분과 대응되는지
- 어느 레지스터가 어떤 값인지
조금씩 감을 잡게 돼요.
3.2 전체를 책 흐름으로 다시 한 번 묶으면
책은 3.2에서 이런 순서로 가요.
- 컴퓨터는 기계어를 실행한다고 말한다.
즉 우리가 봐야 할 진짜 실행 단위는 기계 수준 코드다. - C 코드는 바로 실행되지 않고
컴파일러, 어셈블러, 링커를 거쳐 실행 파일이 된다고 설명한다.
이 과정에서 어셈블리가 중요한 중간 표현으로 등장한다. - 고급 언어는 편리하지만
기계 수준을 이해하면 프로그램의 실제 동작을 더 잘 이해할 수 있다고 말한다.
그래서 3장을 공부할 이유를 만들어 준다. - 기계 수준 코드는
고급 언어의 추상화가 벗겨진 형태라고 설명한다.
변수명이나 제어문보다는
레지스터, 메모리, 연산, 점프로 표현된다는 감각을 준다. - 실제 C 함수와 어셈블리 예제를 보여주며
함수 인자, 호출, 반환, 레지스터 사용이 어떻게 나타나는지 맛보기로 보여준다. - 실제 .s 파일에는
실행 명령어 외에도 여러 지시어가 포함된다고 설명한다.
즉 “보이는 걸 다 똑같이 읽으면 안 된다”는 걸 알려준다. - 그래서 앞으로 책에서는
어셈블리를 학습용 형식으로 정리해서 보여주겠다고 말한다.
이게 이후 절들을 읽는 기본 틀이 된다.
책 흐름 기준 핵심 한 줄 요약
3.2는 컴퓨터가 실제로 실행하는 기계어와 그것의 사람이 읽기 쉬운 표현인 어셈블리를 소개하고, C 코드가 그 형태로 바뀌는 과정과 앞으로 어셈블리 코드를 어떻게 읽을지를 준비시키는 절이다.
3.3의 시작점
책은 여기서
x86-64가 여러 크기의 데이터를 다룬다고 설명해요.
왜 이 얘기가 중요하냐면,
기계 수준에서는 데이터의 “의미”보다
몇 바이트인가가 훨씬 중요하기 때문이에요.
C에서는 우리가
- char
- short
- int
- long
- pointer
- float
- double
이런 이름으로 타입을 보죠.
그런데 어셈블리에서는
이 타입들이 우선
몇 바이트를 차지하는가로 나타나요.
즉 기계 입장에서는
“이게 변수 이름이 뭐냐”보다
“1바이트냐, 2바이트냐, 4바이트냐, 8바이트냐”가 더 중요해요.
비유하면
택배 상자 안에 뭐가 들었는지도 중요하지만,
물류센터에서는 먼저
소형, 중형, 대형처럼 크기 규격으로 분류하는 것과 비슷해요.
책이 여기서 정리하는 핵심: C 타입과 크기 대응
3.3에서는
x86-64 환경에서
C의 기본 데이터 타입들이 보통 어떤 크기를 가지는지 보여줘요.
대표적으로 이런 감각을 잡으면 돼요.
- char : 1바이트
- short : 2바이트
- int : 4바이트
- long : 8바이트
- char *, 각종 포인터 : 8바이트
- float : 4바이트
- double : 8바이트
책 흐름상 여기서 중요한 건
“타입 이름” 자체보다
“이 타입이 기계 수준에서 몇 바이트인가”를 연결하는 거예요.
왜냐하면 뒤에서 movb, movw, movl, movq 같은 명령을 볼 때
이 크기 감각이 바로 필요하기 때문이에요.
여기서 책이 강조하는 x86-64의 데이터 크기 이름
책은 단순히 바이트 수만 말하지 않고
x86-64에서 쓰는 전통적인 이름도 연결해줘요.
- 1바이트 : byte
- 2바이트 : word
- 4바이트 : double word
- 8바이트 : quad word
이 부분은 처음 보면 좀 이상해요.
특히 word가 2바이트라는 점,
double word가 4바이트라는 점이
직관적이지 않을 수 있어요.
이건 역사적인 이름이라서 그래요.
비유하면
옛날 제도에서 쓰던 단위를
지금도 관성적으로 쓰는 느낌이에요.
예를 들어 이름만 보면 헷갈리지만,
그 세계 안에서는 다들 그렇게 부르는 거죠.
책은 이걸 통해
우리가 뒤에서 볼 명령어 suffix와 연결할 준비를 시켜요.
명령어 이름과 데이터 크기의 연결
여기서부터 책은
아주 중요한 연결을 하나 해줘요.
x86-64 명령어들은
데이터 크기에 따라 이름 끝이 달라진다고 설명해요.
예를 들면 mov 계열은
- movb : 1바이트 이동
- movw : 2바이트 이동
- movl : 4바이트 이동
- movq : 8바이트 이동
이렇게 돼요.
이 흐름이 중요한 이유는
앞 절에서 어셈블리를 보여줬지만,
아직은 “저 뒤에 붙은 b, w, l, q가 뭔데?”가 남아 있기 때문이에요.
3.3은 바로 그 의문을 풀어주는 절이에요.
비유하면
3.2에서 공구함을 열어 보여줬고,
3.3에서는
“드라이버 소형, 중형, 대형이 각각 어디에 쓰이는지”를 설명하는 느낌이에요.
즉 명령어는 같아 보여도
다루는 데이터 크기에 따라 다른 버전이 있다는 거예요.
왜 데이터 크기가 그렇게 중요할까
책 흐름상 이 절은 짧지만
의미는 꽤 커요.
왜냐하면 기계 수준에서는
같은 메모리 위치라도
몇 바이트를 읽느냐에 따라 전혀 다른 결과가 나오기 때문이에요.
예를 들어 메모리에 값이 들어 있어도
- 1바이트만 읽을지
- 4바이트를 읽을지
- 8바이트를 읽을지
에 따라 해석이 달라져요.
즉 기계 수준에서는
데이터를 다룰 때
“무엇을 읽느냐” 못지않게
“얼마나 읽느냐”가 중요해요.
비유하면
책 한 권이 있어도
- 첫 글자만 읽을지
- 한 문장만 읽을지
- 한 페이지를 읽을지
에 따라 얻는 정보가 달라지는 것과 비슷해요.
컴퓨터도 메모리에서
딱 정해진 크기만큼 읽고 쓰기 때문에
데이터 크기가 핵심이에요.
정수와 포인터가 왜 8바이트로 자주 보이는가
3.3에서는 x86-64 환경답게
long과 포인터가 8바이트라는 점이 중요하게 깔려 있어요.
이건 64비트 구조이기 때문이에요.
특히 포인터가 8바이트라는 점은
뒤에서 메모리 주소 계산, 배열 접근, 함수 인자 전달, 스택 프레임을 볼 때 계속 중요해져요.
책 흐름상 지금은 그냥 데이터 크기 표처럼 보여도,
실제로는 뒤 절들의 기반이에요.
비유하면
건물 도면을 보기 전에
기둥 간격 단위가 몇 미터인지 먼저 알아두는 것과 같아요.
지금은 숫자만 보이지만,
나중에 구조 전체를 읽을 때 계속 쓰여요.
부동소수점도 크기로 본다
이 절에서는
부동소수점도 결국 크기 관점으로 정리돼요.
- float : 4바이트
- double : 8바이트
즉 기계 수준에서
정수와 부동소수점은 의미상 다르지만,
우선 저장 크기라는 측면에서는 똑같이 다뤄져요.
물론 뒤의 3.11 Floating-Point Code에 가면
레지스터와 명령 체계가 달라진다는 걸 더 배우게 되지만,
3.3에서는 우선
“크기” 기준으로 먼저 감을 잡게 해줘요.
책 흐름상 3.3의 역할
3.3은 혼자 보면 단순한 표 정리처럼 보여요.
그런데 책 흐름 안에서는 역할이 명확해요.
- 3.2에서 어셈블리를 소개했다
- 이제 3.3에서 데이터 크기 규칙을 정리한다
- 그리고 3.4부터 본격적으로 레지스터와 데이터 이동을 다룬다
즉 3.3은
본격적인 명령어 학습 전에 필요한 단위 정리예요.
이 절이 필요한 이유는
앞으로 나오는 명령어를 읽을 때
계속 “몇 바이트짜리 연산인가?”를 알아야 하기 때문이에요.
비유로 3.3 전체를 보면
3.2가
“이 공장은 작업 지시서로 움직인다”를 보여준 절이라면,
3.3은
“이 공장에서는 부품 크기를 이렇게 구분한다”를 설명하는 절이에요.
- 작은 부품은 1바이트
- 조금 큰 부품은 2바이트
- 중간 부품은 4바이트
- 큰 부품은 8바이트
그리고 작업 지시서 끝의 표시가
그 부품 크기를 나타내는 거예요.
즉 movb, movw, movl, movq는
같은 “옮긴다”라는 작업이지만
옮기는 상자 크기가 다른 거예요.
책 흐름대로 짧게 다시 묶으면
3.3에서는
x86-64에서 사용하는 데이터 타입들의 크기를 정리한다.
기계 수준에서는
데이터의 의미보다
몇 바이트짜리 데이터인가가 중요하다.
그래서 C의 기본 타입들이
1, 2, 4, 8바이트 중 어느 크기로 표현되는지 보여주고,
이 크기가 어셈블리 명령어 이름의 suffix와 연결된다고 설명한다.
이 절은 뒤에서 나오는
레지스터 사용과 데이터 이동 명령을 이해하기 위한 준비 단계다.
한 줄 핵심
3.3은 x86-64에서 C 데이터 타입이 몇 바이트로 표현되는지 정리하고, 그 크기 정보가 이후 어셈블리 명령어 해석의 기준이 된다는 점을 설명하는 절이다.
질문
그럼 비트는 무엇인가?
비트는 말 그대로 컴퓨토가 표현할수있는 가장 작은 정보 단위
쉽게 생각해 0과 1 둘중 하나만 가질수있다
그럼 16비트는 뭘까?
16개의 비트로 값 하나를 표현한다.
- 0000000000000001 은 1
- 0000000000000010 는 2
- 0000000000001010 는 10
이런식으로 정보를 표현할수있다
하지만 모양이 같아도 다른 정보를 표현할수있다.
예를 들어 0100000101000010 이라는 16비트가 있으면
그걸 정수로 볼 수도 있고,
두 개의 문자로 쪼개 볼 수도 있고,
어떤 기계 명령의 일부로 볼 수도 있다.
정수로는 16706
문자열로는
8비트씩 나누면
- 01000001 = 0x41
- 01000010 = 0x42
ASCII 기준으로
- 0x41 = A
- 0x42 = B
그래서 문자 두 개로 보면
"AB"
기계명령으로는 이게 cpu종류와 주변 문맥에 따라 해석법이 다르지만 굳이굳이 x86-64 기준으로 보면 “다음 명령은 확장 레지스터를 써” 같은 꼬리표같은거다.
그럼 32비트면 속도가 더 빨라지는걸까?
무조건 그렇지는 않다 왜냐하면 32비트는 처리할수있는 데이터 종류가 많아진거지 이게 속도랑 관련은 없다 오히려 더 많은 데이터 종류를 한번에 보낼수있으니 효율적일수는 있지만 하나의 데이터 처리는 늦어질수있다.
그럼 왜 64비트까지밖에 없는걸까?
우리가 주로 64비트를 쓰는것이다. 실제로는 128비트 또는 그 이상도 쓰지만 cpu기본 구조로 64비트가 주로 쓰일뿐이다.
하지만 이유는 있다
1. 이미 충분히 크다
위에서 말한것 처럼 보낼수있는 데이터 종류가 더 많아지는것이라 64비트로 이미 보낼 데이터 종류는 다 표현할수있다
2. 더 크게 만들면 비용도 늘어난다
비트 수가 커지면.
- 레지스터가 더 커지고
- 데이터 이동량이 늘고
- 메모리 사용량이 늘 수 있고
- 회로도 더 복잡해질 수 있다
3. 필요하면 일부 기능만 더 넗게 만들수있다
이미 똑똑한 사람들이 CPU 전체를 128비트 기본 구조로 바꾸지 않더라도,
필요한 연산만 128비트 이상으로 처리할 수 있게 만들었다.
예를 들어
- SIMD 벡터 연산
- 암호 연산
- 큰 정수 연산
그럼 3.2에나온 예시 코드는 무슨뜻일까?
long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
- mult2(x, y)를 호출해서
- 그 결과를 t에 저장하고
- *dest = t로 결과를 메모리에 저장하는 함수
즉 쉽게 말하면
x와 y를 곱한 결과를 dest가 가리키는 곳에 저장해라`는 뜻
그래서 이걸 어셈블리어로 바꿔 보면
.file "mstore.c"
.text
.globl multstore
.type multstore, @function
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
.size multstore, .-multstore
하지만 책은 더 쉽게 보여주기 위해
void multstore(long x, long y, long *dest)
x in %rdi, y in %rsi, dest in %rdx
1 multstore:
2 pushq %rbx Save %rbx
3 movq %rdx, %rbx Copy dest to %rbx
4 call mult2 Call mult2(x, y)
5 movq %rax, (%rbx) Store result at *dest
6 popq %rbx Restore %rbx
7 ret Return
이걸 한줄씩 해석하면
x in %rdi, y in %rsi, dest in %rdx
함수 인자가 처음부터 이렇게 들어온다는 뜻이야.
- 첫 번째 인자 x -> %rdi
- 두 번째 인자 y -> %rsi
- 세 번째 인자 dest -> %rdx
pushq %rbx
이건 %rbx 값을 스택에 잠깐 저장하는 거야.
왜 저장하냐면
이 함수 안에서 %rbx를 쓸 건데,
원래 있던 값을 망가뜨리면 안 되기 때문이야.
movq %rdx, %rbx
이건 dest 값을 %rbx로 옮기는 거야.
원래 dest는 %rdx에 들어 있었지.
그런데 곧 call mult2를 할 거라서
나중에도 안전하게 가지고 있으려고 %rbx에 복사해 두는 거야.
call mult2
이건 mult2(x, y)를 호출하는 거야.
여기서 중요한 건
x와 y는 이미 %rdi, %rsi에 들어 있기 때문에
따로 준비 안 하고 바로 호출할 수 있다는 점이야.
즉 C의 이 줄:
long t = mult2(x, y);
가 어셈블리에서는
실제로 call mult2로 나타나는 거야.
그리고 함수가 끝나면
반환값은 %rax에 들어와.
즉 t의 값이 사실상 %rax에 들어온다고 보면 돼.
movq %rax, (%rbx)
이건 %rax에 있는 결과를
%rbx가 가리키는 메모리 위치에 저장하는 거야.
아까 %rbx에 뭐가 들어 있었지?
dest 주소가 들어 있었어.
그래서 이 명령은 결국:
*dest = t;
를 의미해.
즉
- %rax = mult2의 반환값
- (%rbx) = dest가 가리키는 메모리 위치
- 그래서 결과를 그 위치에 저장
popq %rbx
처음에 저장해뒀던 %rbx 값을 다시 꺼내서 복구하는 거야.
즉 작업 끝났으니까
서랍에 넣어둔 메모를 다시 책상 위에 올리는 거야.
ret
함수를 끝내고 호출한 곳으로 돌아가는 명령이야.
그럼 %rdi는 뭘까?
%는 이제부터 위에 오는게 레지스터이름이다
r은 이 레지스터가 64비트 버전이다
di는 엤날 x86에서 온 이름 뜻은 원래는 메모리 복사나 문자열 처리에서 목적지 쪽 위치를 가리키는 용도로 많이 썼어.
그럼 32비트는 똑같이 r인가?
정답은 e이다.
그럼 한 어셈블리어에서 r과 e를 동시에 쓸수있을까?
정답은 실제로 자주 같이 나온다
그럼 실제로 왜 같이 쓰냐?
프로그램 안에는
- 64비트 long
- 32비트 int
가 같이 있을 수 있잖아.
그러면 어셈블리에서도
- long 다룰 때는 %rax, %rdi 같은 64비트 이름
- int 다룰 때는 %eax, %edi 같은 32비트 이름
을 같이 쓸 수 있어.
즉 자료형 크기가 다르면
같은 코드 안에서도 r, e가 섞이는 게 자연스러워.
마지막 정리
3.2 Program Encodings는
C 프로그램이 바로 실행되는 게 아니라
어셈블리와 기계어로 바뀌어서 실행된다는 내용을 다뤄요.
컴퓨터는 결국 바이트로 된 기계어를 실행하고,
어셈블리는 그걸 사람이 읽기 쉽게 적어 놓은 형태예요.
여기서는 C 함수가 어셈블리에서
레지스터, call, ret, mov 같은 명령으로 어떻게 보이는지 예제로 보여줘요.
3.3 Data Formats는
기계 수준에서는 데이터가 “이게 int냐 long이냐”보다
몇 바이트냐가 중요하다는 내용을 다뤄요.
그래서 char, short, int, long, 포인터, float, double이
각각 몇 바이트인지 정리하고,
이 크기가 movb, movw, movl, movq처럼
어셈블리 명령어 이름과 연결된다는 걸 보여줘요.
한 줄씩 더 줄이면:
- 3.2 : C 코드가 기계어로 바뀌는 과정과 어셈블리 형태를 배운다.
- 3.3 : 기계 수준에서 데이터는 주로 바이트 크기로 구분된다는 걸 배운다.
'개념들' 카테고리의 다른 글
| CSA 1.8~1.10 (1) | 2026.03.31 |
|---|---|
| 트리 (0) | 2026.03.30 |
| CSAPP(쌉) 1장들 (0) | 2026.03.28 |