<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>헤맨 만큼 내 땅이다</title>
    <link>https://notes51162.tistory.com/</link>
    <description>문과생 비전공자의 정글 생존기!</description>
    <language>ko</language>
    <pubDate>Tue, 20 Oct 2026 21:50:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>신속다람</managingEditor>
    <image>
      <title>헤맨 만큼 내 땅이다</title>
      <url>https://tistory1.daumcdn.net/tistory/8995569/attach/bb94cacb1b39439f98a6d91fb2fef05d</url>
      <link>https://notes51162.tistory.com</link>
    </image>
    <item>
      <title>9.6~</title>
      <link>https://notes51162.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;: 디스크는 지하 창고, RAM은 열람실 책상이에요. 책 한 권이 &lt;b&gt;페이지&lt;/b&gt;이고, 책이 어디 있는지 적어 둔 &lt;b&gt;목록표&lt;/b&gt;가 페이지 테이블이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 페이지 (9.3)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상메모리를 같은 크기의 덩어리로 자른 게 &lt;b&gt;가상페이지(VP)&lt;/b&gt;, RAM을 같은 크기로 자른 게 **물리페이지(PP)**예요.&lt;/li&gt;
&lt;li&gt;전체 데이터는 디스크에 있고, 지금 쓰는 페이지만 RAM에 올려 둬요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 페이지 테이블 (9.3.2)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상페이지마다 칸이 하나씩 있고, 그 칸을 &lt;b&gt;PTE&lt;/b&gt;라고 해요. PTE에는 두 가지가 적혀 있어요.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유효비트&lt;/b&gt;: 1이면 지금 RAM에 있다는 뜻이고, 0이면 RAM에 없다는 뜻이에요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주소&lt;/b&gt;: 1이면 RAM의 어느 물리페이지인지, 0이면 디스크 어디인지 적혀 있어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;페이지 테이블은 RAM에 있고, 번역할 때마다 MMU가 이걸 읽어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 적중과 폴트 (9.3.3~9.3.4)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;페이지 적중&lt;/b&gt;: 유효비트가 1이면 RAM에서 바로 읽어요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지 폴트&lt;/b&gt;: 유효비트가 0이면 예외가 발생해요. 커널이 RAM의 페이지 하나를 내보내고(희생자), 필요한 페이지를 디스크에서 가져와서 PTE를 고쳐요. 그다음 &lt;b&gt;같은 명령을 다시 실행&lt;/b&gt;하면 이번엔 적중이에요.&lt;/li&gt;
&lt;li&gt;필요할 때만 가져오는 방식을 &lt;b&gt;요구 페이징&lt;/b&gt;이라고 해요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 프로세스마다 페이지 테이블이 따로 있어요 (9.4)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스마다 자기만의 주소공간과 페이지 테이블을 가져요. 그래서 서로 다른 프로세스가 같은 가상주소를 써도 다른 물리페이지로 가요.&lt;/li&gt;
&lt;li&gt;서로 다른 가상페이지가 같은 물리페이지를 가리킬 수도 있어요. printf 코드를 공유하는 게 그 예예요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 보호 (9.5)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PTE에 &lt;b&gt;권한 비트&lt;/b&gt;(SUP, READ, WRITE)가 더 붙어 있어요. 번역하면서 접근 권한도 같이 검사해요.&lt;/li&gt;
&lt;li&gt;권한을 어기면 세그멘테이션 오류(SIGSEGV)가 나요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9.6 주소의 번역&lt;/b&gt; (783쪽)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① &quot;이번 절은 기본 주소 번역을 다룬다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 주소가 물리 주소로 바뀌는 과정을 이번 절에서 다룬다는 뜻이에요. 앞에서 &quot;MMU가 번역한다&quot;고만 했던 부분을 이제 실제로 계산해 봐요.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;MMU = Memory Management Unit (메모리 관리 장치)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 칩 안에 들어 있는 &lt;b&gt;전용 하드웨어&lt;/b&gt;예요. 하는 일은 두 가지예요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;주소 번역&lt;/b&gt;: CPU가 내보낸 가상 주소를 물리 주소로 바꿔요. 이때 RAM에 있는 페이지 테이블을 읽어요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 검사&lt;/b&gt;: 번역하면서 PTE의 권한 비트도 확인해요(9.5에서 본 내용).&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;: CPU는 &quot;이 책 주세요&quot; 하고 책 번호(가상 주소)를 말해요. MMU는 사서예요. 목록표(페이지 테이블)를 보고 실제 책장 위치(물리 주소)를 찾아서 RAM에 전달해요. CPU는 진짜 위치를 몰라도 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;발표에서 짚을 포인트&lt;/b&gt;는 번역이 소프트웨어가 아니라 &lt;b&gt;하드웨어(MMU)가 하고, 페이지 테이블 내용은 운영체제가 관리한다&lt;/b&gt;는 점이에요. 번역은 속도가 중요해서 하드웨어가 맡고, 어느 페이지를 RAM에 둘지 같은 판단은 운영체제가 맡아요. 둘의 협업이에요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ &quot;참고를 위해 그림 9.11은 이 절에서 사용할 심볼들을 요약한 것이다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림 9.11(784쪽)은 이 절에서 쓰는 약어를 모아 둔 표예요. 이 표는 &lt;b&gt;발표 중에 계속 돌아와서 볼 중요한 표&lt;/b&gt;예요. 한 번에 외우지 말고, 각 약어가 본문에 처음 나올 때 하나씩 풀어 드릴게요. 지금 알아 둘 건 세 가지예요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;N, M&lt;/b&gt;: 가상 주소의 개수(N)와 물리 주소의 개수(M)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;P&lt;/b&gt;: 페이지 크기(바이트)&lt;/li&gt;
&lt;li&gt;표가 &lt;b&gt;가상 주소(VA)용&lt;/b&gt;과 &lt;b&gt;물리 주소(PA)용&lt;/b&gt; 두 묶음으로 나뉘어 있다는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑤ &quot;주소 번역은 N-원소 가상 주소공간(VAS)과 M-원소 물리 주소공간(PAS)의 원소들 간의 매핑이다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역은 &lt;b&gt;가상 주소 하나를 넣으면 물리 주소 하나가 나오는 대응표&lt;/b&gt;라는 뜻이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;VAS&lt;/b&gt;: 가상 주소공간 (주소 N개)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PAS&lt;/b&gt;: 물리 주소공간 (주소 M개)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매핑&lt;/b&gt;: &quot;이 가상 주소는 저 물리 주소&quot;라고 짝지어 주는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MAP: VAS &amp;rarr; PAS &amp;cup; &amp;empty;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAP은 함수 이름이에요. 가상 주소를 넣으면 &lt;b&gt;물리 주소 또는 &amp;empty;(없음)&lt;/b&gt; 가 나온다는 뜻이에요. &amp;empty;가 나올 수 있다는 점이 핵심이에요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MAP(A) = A&amp;prime; (A의 데이터가 물리 주소 A&amp;prime;에 있다면) / &amp;empty; (A의 데이터가 물리 메모리에 없다면)&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우가 두 가지예요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RAM에 있다&lt;/b&gt;: 물리 주소 A&amp;prime;를 돌려줘요. 예를 들면 가상 주소 4100이 물리 주소 4로 바뀌어요(그림 9.2의 예).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RAM에 없다&lt;/b&gt;: &amp;empty;를 돌려줘요. 앞에서 본 &lt;b&gt;유효비트 0, 즉 페이지 폴트&lt;/b&gt; 상황이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그림 9.12&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어는 이렇게만 잡으면 돼요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MMU&lt;/b&gt;: 번역하는 CPU 안의 하드웨어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PTBR&lt;/b&gt;: 지금 쓰는 페이지 테이블 위치를 적어 둔 레지스터&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PTE&lt;/b&gt;: 페이지 테이블의 한 칸 (유효비트 + 물리페이지 번호)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;번역 결과가 &amp;empty;&lt;/b&gt;: RAM에 없다는 뜻이고, 페이지 폴트예요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역 한 번의 흐름은 이래요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU가 &lt;b&gt;가상 주소&lt;/b&gt;를 내보내요.&lt;/li&gt;
&lt;li&gt;MMU가 가상 주소를 둘로 나눠서 읽어요.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;VPN&lt;/b&gt;: 몇 번째 페이지인가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VPO&lt;/b&gt;: 그 페이지 안에서 몇 번째 바이트인가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MMU가 &lt;b&gt;PTBR&lt;/b&gt;로 페이지 테이블 위치를 찾고, &lt;b&gt;VPN으로 PTE를 골라요.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;PTE에서 &lt;b&gt;PPN&lt;/b&gt;(물리페이지 번호)을 꺼내요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PPN + VPO&lt;/b&gt;를 이어 붙이면 물리 주소가 돼요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 줄 핵심&lt;/b&gt;: 번역에서 바뀌는 건 &lt;b&gt;페이지 번호(VPN &amp;rarr; PPN)&lt;/b&gt; 뿐이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑥ &quot;그림 9.12는 MMU가 매핑을 하기 위해 페이지 테이블을 어떻게 사용하는지를 보여준다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;785쪽 위쪽 그림이에요. 이 줄은 안내 문장이고, 그림은 뒤 문장들을 읽으면서 같이 풀게요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑦ &quot;CPU 내에 있는 제어 레지스터인 페이지 테이블 베이스 레지스터(PTBR)는 현재 페이지 테이블을 가리킨다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMU가 번역하려면 먼저 &lt;b&gt;페이지 테이블이 RAM 어디에 있는지&lt;/b&gt;를 알아야 해요. 그 위치를 적어 두는 곳이 PTBR이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;레지스터&lt;/b&gt;: CPU 안에 있는 아주 작은 서랍이에요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PTBR&lt;/b&gt;: 그 서랍 중에 &quot;지금 쓰는 페이지 테이블의 시작 주소&quot;를 넣어 두는 서랍이에요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;현재&quot;라는 말이 붙은 이유&lt;/b&gt;: 프로세스마다 페이지 테이블이 따로 있어서(9.4), 지금 실행 중인 프로세스의 테이블을 가리켜야 하기 때문이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑧ &quot;n 비트 가상주소는 두 개의 컴포넌트를 가진다: p 비트 가상페이지 오프셋(VPO)과 (n&amp;minus;p) 비트 가상페이지 번호(VPN).&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요한 줄이에요.&lt;/b&gt; 가상 주소(n비트)를 둘로 쪼개서 읽는다는 뜻이에요. 그림 9.12 위쪽 칸이 이 모양이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽 (n&amp;minus;p 비트) 뒤쪽 (p 비트)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;VPN&lt;/b&gt; (가상페이지 번호)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;VPO&lt;/b&gt; (가상페이지 오프셋)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;몇 번째 페이지인가&lt;/td&gt;
&lt;td&gt;그 페이지 안에서 몇 번째 바이트인가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하면 VPN은 &lt;b&gt;몇 번 책&lt;/b&gt;이고, VPO는 &lt;b&gt;그 책의 몇 번째 줄&lt;/b&gt;이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤쪽이 p비트인 이유는 페이지 크기가 P = 2ᵖ 바이트이기 때문이에요. 페이지 안의 위치를 구분하려면 p비트가 필요해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;숫자로 확인해 볼게요.&lt;/b&gt; n = 8, p = 4라고 하면 페이지 크기는 16바이트예요. 가상 주소가 0011 1010이면 이렇게 나뉘어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VPN = 0011 = 3 &amp;rarr; 3번 페이지&lt;/li&gt;
&lt;li&gt;VPO = 1010 = 10 &amp;rarr; 그 페이지의 10번째 바이트&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑨ &quot;MMU는 VPN을 사용해서 적합한 PTE를 선택한다. 예를 들어, VPN 0은 PTE 0을 선택하고, VPN 1은 PTE 1을 선택하는 식으로 진행된다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 테이블에서 &lt;b&gt;몇 번째 칸(PTE)을 볼지는 VPN이 정해요.&lt;/b&gt; VPN이 3이면 3번 PTE를 봐요. 그림 9.12에서 &quot;The VPN acts as an index into the page table&quot;이라고 적힌 부분이 이 내용이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도서관 비유로는 책 번호(VPN)가 3번이면 목록표의 3번째 줄을 보는 거예요. VPO는 이 단계에서는 쓰지 않고 따로 가져가요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑩ &quot;대응되는 물리 주소는 페이지 테이블 엔트리에서 가져온 물리페이지 번호 PPN과 가상주소에서 온 VPO를 연결한 것이다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리 주소는 &lt;b&gt;PPN과 VPO를 이어 붙여서&lt;/b&gt; 만들어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PPN&lt;/b&gt;: 물리페이지 번호예요. 몇 번째 &lt;b&gt;물리&lt;/b&gt; 페이지인지를 나타내고, PTE에 적혀 있어요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VPO&lt;/b&gt;: 가상 주소에서 그대로 가져와요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 58번 주소(3번 페이지의 10번 칸)로 해 볼게요. 3번 PTE를 봤더니 PPN이 5라고 적혀 있다고 가정해요. 그러면 물리 주소는 &lt;b&gt;5번 물리페이지의 10번 칸&lt;/b&gt;이에요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑪ &quot;물리와 가상페이지가 모두 P 바이트이므로 물리페이지 오프셋 PPO은 VPO와 동일하다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상페이지와 물리페이지는 &lt;b&gt;크기가 같아요.&lt;/b&gt; 그래서 페이지 안의 칸 번호는 번역해도 안 바뀌어요. 위 예시에서 10번 칸이 번역 전에도 10번이고 번역 후에도 10번인 거예요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PPO&lt;/b&gt;: 물리페이지 안에서 몇 번째 바이트인지를 나타내요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PPO = VPO&lt;/b&gt;: 번역에서 바뀌는 건 &lt;b&gt;페이지 번호(VPN &amp;rarr; PPN)&lt;/b&gt; 뿐이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그림 9.13&lt;br /&gt;&lt;br /&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑫ &quot;그림 9.13(a)는 CPU 하드웨어가 페이지 적중이 있을 때 수행하는 단계들을 보여준다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림은 786쪽 위쪽에 있어요. 방금 정리한 번역 흐름을 &lt;b&gt;①~⑤ 다섯 단계 화살표&lt;/b&gt;로 그린 거예요. 페이지 적중은 PTE의 유효비트가 1이라서 RAM에서 바로 읽는 경우예요. 단계는 하나씩 읽을게요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑬ &quot;1단계: 프로세서는 가상주소를 생성하고 이것을 MMU로 보낸다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 ①번 화살표예요. CPU(프로세서)가 가상 주소(VA)를 만들어서 MMU에 넘겨요. 여기까지는 번역 흐름의 1번(CPU가 가상 주소를 내보낸다)과 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑭ &quot;2단계: MMU는 PTE 주소를 생성하고 이것을 캐시/메인 메모리에 요청한다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 ②번 화살표(PTEA)예요. &lt;b&gt;PTEA&lt;/b&gt;는 PTE의 주소(Page Table Entry Address)예요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 테이블은 RAM에 있어요.&lt;/li&gt;
&lt;li&gt;MMU는 PTBR로 &lt;b&gt;테이블이 어디서 시작하는지&lt;/b&gt;, VPN으로 &lt;b&gt;몇 번째 칸인지&lt;/b&gt;를 알아내서 PTE의 주소를 만들어요.&lt;/li&gt;
&lt;li&gt;그 주소를 메모리에 보내서 &quot;이 칸 내용 주세요&quot; 하고 요청해요.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑮ &quot;3단계: 캐시/메인 메모리는 PTE를 MMU로 리턴한다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 ③번 화살표(PTE)예요. 메모리가 요청받은 칸의 내용(PTE)을 MMU에 돌려줘요. PTE 안에는 유효비트와 PPN이 들어 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;캐시/메인 메모리&quot;라고 쓴 건 PTE가 캐시에 있을 수도 있어서예요. 지금은 RAM이라고 생각하면 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑯ &quot;4단계: MMU는 물리 주소를 구성하고 이것을 캐시/메인 메모리로 보낸다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 ④번이에요. MMU가 PTE에서 꺼낸 PPN과 가상 주소의 VPO를 이어 붙여서 &lt;b&gt;물리 주소(PA)&lt;/b&gt; 를 만들고, 메모리에 보내요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑰ &quot;5단계: 캐시/메인 메모리는 요청한 데이터 워드를 프로세서로 보낸다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림의 ⑤번이에요. 메모리가 그 물리 주소의 데이터를 CPU에 돌려줘요. 번역이 끝나고 원하던 데이터를 받았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;페이지 적중 한 줄 요약&lt;/b&gt;: 메모리를 두 번 가요. 첫 번째는 PTE를 읽으러(②③), 두 번째는 진짜 데이터를 읽으러(④⑤)예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9.6.1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ &quot;물리 주소를 사용하면, 다중 프로세스들이 캐시에서 블록을 갖는 것과 마찬가지로 가상페이지로부터 블록을 공유하는 것이 단순해진다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 서로 다른 프로세스의 가상페이지가 &lt;b&gt;같은 물리페이지&lt;/b&gt;를 가리킬 수 있다고 했죠(9.4, printf 코드 공유). 캐시도 &lt;b&gt;물리 주소&lt;/b&gt;로 찾으면, 프로세스마다 가상 주소가 달라도 같은 물리 주소는 캐시에서도 하나로 취급돼요. 그래서 공유가 쉬워요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ &quot;게다가 캐시는 보호 이슈를 다룰 필요가 없어지는데, 접근 권한이 주소 번역 과정의 일부로 체크되기 때문에 그렇다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 권한 검사(9.5의 SUP, READ, WRITE)는 &lt;b&gt;번역할 때 MMU가 이미 끝내요.&lt;/b&gt; 그래서 캐시는 권한을 신경 쓰지 않아도 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이유 두 줄 요약&lt;/b&gt;: 물리 주소로 캐시를 쓰면 공유가 쉽고, 권한 검사도 캐시가 안 해도 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑤ &quot;그림 9.14는 물리 주소를 사용하는 캐시가 어떻게 가상메모리와 통합될 수 있는지를 보여준다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;787쪽 위쪽 그림이에요. 그림 9.13에서 &quot;캐시/메모리&quot;로 뭉뚱그려 그렸던 부분을 &lt;b&gt;L1 캐시와 메모리로 나눠서&lt;/b&gt; 그린 거예요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑥ &quot;메인 아이디어는 주소 번역이 캐시 참조 이전에 일어난다는 것이다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요한 줄이에요.&lt;/b&gt; 순서는 이래요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 MMU가 가상 주소를 &lt;b&gt;물리 주소로 번역&lt;/b&gt;해요.&lt;/li&gt;
&lt;li&gt;그 물리 주소로 &lt;b&gt;캐시를 확인&lt;/b&gt;해요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시가 물리 주소로 찾기 때문에 번역이 먼저 끝나야 해요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⑦ &quot;페이지 테이블 엔트리들이 다른 데이터 워드와 마찬가지로 캐싱될 수 있다는 점에 주목해야 한다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PTE도 일반 데이터처럼 &lt;b&gt;캐시에 들어갈 수 있어요.&lt;/b&gt; 그림에서 PTEA를 보내면 캐시에 있을 때(hit)는 캐시가 PTE를 바로 주고, 없으면(miss) 메모리까지 가요. 아까 9.13에서 &quot;캐시/메인 메모리&quot;라고 쓴 이유가 이거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/32</guid>
      <comments>https://notes51162.tistory.com/32#entry32comment</comments>
      <pubDate>Fri, 2 Oct 2026 20:57:21 +0900</pubDate>
    </item>
    <item>
      <title>트러블슈팅2</title>
      <link>https://notes51162.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 ㅇㅇㅇ입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 퀴즈 5번, 어셈블리를 C 코드로 복원하는 문제를 풀면서 막혔던 지점들을 정리해왔습니다.&lt;br /&gt;&lt;br /&gt;우선 어셈블리를 읽을 때는 레지스터가 어떤 C 변수에 대응하는지부터 먼저 표시해두고 시작해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 %edi는 x, %edx는 y가 됩니다.&lt;br /&gt;추가로 복기를하며 `sub`가 아닌 `cmp`를 쓴 이유를 확인해봤습니다.&lt;br /&gt;- `sub`는 계산 결과로 레지스터를 덮어써버려서 원래 값을 잃어버리지만&lt;br /&gt;- `cmp`는 계산만 하고 결과는 버린 채, 부호 정보만 플래그에 기록합니다. 여기선 x - 0이었죠.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음으로 `jle`는 jump if less or equal, 계산한 x-0이 0 이하면 점프하라는 뜻이었습니다.&lt;br /&gt;그래서 조건부 점프를 만나면 &quot;어디로, 무슨 조건으로 가는지&quot;가 핵심이었습니다.&lt;br /&gt;&lt;br /&gt;마지막으로 점프 조건을 충족하면 else로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if 조건이 맞다면 then을 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서는 점프 후 then이 아닌 else 도착하는 것이 중요 포인트입니다.&lt;br /&gt;그래서 if문 조건을 쓸 땐 점프 조건을 반대로 뒤집어야 했습니다.&lt;br /&gt;&lt;br /&gt;즉, 점프 조건이 x&amp;le;0이었으니, 반대로 뒤집으면 `if (x &amp;gt; 0)`이 되는 거죠.&lt;br /&gt;&lt;br /&gt;이 세단계를 통해 최종적으로&amp;nbsp;`if&amp;nbsp;(x&amp;nbsp;&amp;gt;&amp;nbsp;0)&amp;nbsp;y&amp;nbsp;=&amp;nbsp;1;&amp;nbsp;else&amp;nbsp;y&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;return&amp;nbsp;y;`라는&amp;nbsp;정답을&amp;nbsp;도출할&amp;nbsp;수&amp;nbsp;있었습니다.&amp;nbsp;감사합니다.&lt;/p&gt;</description>
      <category>week5</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/31</guid>
      <comments>https://notes51162.tistory.com/31#entry31comment</comments>
      <pubDate>Wed, 30 Sep 2026 16:45:54 +0900</pubDate>
    </item>
    <item>
      <title>02 Stack Buffer Overflow (off-by-one)</title>
      <link>https://notes51162.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파스칼의 삼각형을 1차원 배열에 삼각 인덱싱으로 채우는 코드다. 실행하면 정상적으로 14개 행을 다 출력한 뒤, 마지막에 갑자기 다음 에러와 함께 종료된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;*** stack smashing detected ***: terminated
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 끝까지 정상인데 프로그램만 비정상 종료되는 것이 특징이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 코드&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static void build_pascal(int *tri, int rows) {
    for (int i = 0; i &amp;lt;= rows; i++) {   // 문제 지점
        for (int j = 0; j &amp;lt;= i; j++) {
            ...
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROWS가 14일 때, 유효한 행은 0번부터 13번까지 총 14개다. 그런데 i &amp;lt;= rows 조건 때문에 i가 0부터 14까지, 즉 15번 돈다. 존재하지 않는 15번째 행(i = 14)까지 만들려고 하는 것이 근본 원인이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열 밖을 넘어 쓰는 지점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 계산 함수는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static int tri_index(int i, int j) {
    return i * (i + 1) / 2 + j;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i = 14, j = 0을 대입하면 idx = 14 * 15 / 2 = 105가 나온다. 배열은 enum { SIZE = ROWS * (ROWS + 1) / 2 }, 즉 SIZE = 105로 선언되어 있다. 유효한 인덱스는 0 ~ 104뿐이므로, tri[105]는 배열 밖의 메모리를 건드리는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 하필 return 0;에서 죽는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버플로가 일어나는 시점(105번 인덱스에 쓰는 순간)과 에러가 발생하는 시점(main의 return 직전)이 서로 다르다는 것이 이 문제의 핵심이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴파일러는 지역 배열 뒤에 &lt;b&gt;스택 카나리(canary)&lt;/b&gt; 라는 감시용 값을 몰래 심어둔다.&lt;/li&gt;
&lt;li&gt;tri[105]에 쓰는 순간 그 카나리 값이 조용히 덮어써진다. 이때는 아무 에러도 나지 않는다.&lt;/li&gt;
&lt;li&gt;build_pascal과 이후 출력문들은 모두 정상적으로 끝까지 실행된다.&lt;/li&gt;
&lt;li&gt;main이 함수를 끝내고 돌아가기(return) 직전, 컴파일러가 심어둔 코드가 &quot;카나리 값이 그대로인지&quot; 검사한다.&lt;/li&gt;
&lt;li&gt;값이 바뀌어 있으므로 침입을 감지하고 abort를 호출해 프로그램을 강제 종료시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 범행(배열 밖 쓰기)은 함수 실행 중 조용히 일어나고, 경보(스택 스매싱 감지)는 함수가 끝나는 시점에야 울리는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gdb로 직접 확인하는 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 &amp;mdash; gdb 실행&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;make gdb NAME=02_stack_buffer_overflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 &amp;mdash; run으로 실제 크래시 재현&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;(gdb) run
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;row  0: 1   (sum=1)
...
row 13: 1 13 78 286 ... 13 1   (sum=8192)
SIZE = 105
*** stack smashing detected ***: terminated

Program received signal SIGABRT, Aborted.
0x0000fffff7e774d8 in ?? () from /lib/aarch64-linux-gnu/libc.so.6
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14개 행과 SIZE까지는 전부 정상 출력된 뒤에 크래시가 난 것을 확인할 수 있다. 즉 로직상 계산 자체는 문제없이 끝났고, 뭔가 &quot;뒷정리&quot; 단계에서 문제가 생겼다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계 &amp;mdash; bt(backtrace)로 호출 스택 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;(gdb) bt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;#0  0x0000fffff7e774d8 in ?? () from libc.so.6
#1  0x0000fffff7e2cb3c in raise () from libc.so.6
#2  0x0000fffff7e17e00 in abort () from libc.so.6
#3  0x0000fffff7e6aac4 in ?? () from libc.so.6
#4  0x0000fffff7ee82a8 in __fortify_fail () from libc.so.6
#5  0x0000fffff7ee9264 in __stack_chk_fail () from libc.so.6
#6  0x0000aaaaaaaa0b58 in main () at bug.c:112
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bt는 번호가 &lt;b&gt;낮을수록 가장 최근에 실행된(안쪽) 함수&lt;/b&gt;, &lt;b&gt;높을수록 그걸 호출한(바깥쪽) 함수&lt;/b&gt;를 의미한다. 그래서 아래에서 위로 읽으면 사건의 흐름이 보인다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;#6  main () at bug.c:112      &amp;larr; 우리 코드. return 하려던 참이었다
#5  __stack_chk_fail ()       &amp;larr; &quot;카나리 값이 깨졌다!&quot;를 감지한 함수
#4  __fortify_fail ()         &amp;larr; &quot;그럼 강제 종료 처리한다&quot;
#3~#0  abort, raise 등        &amp;larr; 실제로 프로세스를 죽이는 내부 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#0~#3이 함수 이름 없이 ??로 나오는 것은 그 부분이 glibc의 디버그 심볼이 없는 내부 구현이기 때문이며, 우리 코드와는 무관한 부분이다. 신경 쓸 필요가 있는 건 #5, #6뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계 &amp;mdash; frame과 list로 정확한 소스 위치 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#6이 정확히 어느 줄인지 직접 소스로 확인하기 위해 해당 프레임으로 이동한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;(gdb) frame 6
(gdb) list
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;    return 0;
}   // 112라인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main()이 return 0; 문 자체(112라인)에서 멈춰 있는 것을 직접 확인했다. 이걸로 앞서 추론한 &quot;오버플로는 조용히 일어나고, 검사는 return 직전에 이뤄진다&quot;는 가설이 실제 gdb 출력으로 증명된 셈이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; rows; i++) {   // &amp;lt;= 를 &amp;lt; 로
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유효한 행 번호는 0 ~ rows-1이므로 &amp;lt;로 바꿔주면 정확히 필요한 개수만큼만 반복된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정 후 재확인&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;make run NAME=02_stack_buffer_overflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack smashing 에러 없이 14개 행이 모두 출력되고, SIZE = 105가 찍힌 뒤 정상 종료(exit code 0)되는 것을 확인했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱싱 계산식이 복잡할수록 &quot;마지막으로 접근하는 인덱스&quot;를 직접 손으로 계산해서 배열 크기와 비교해보는 습관이 필요하다.&lt;/li&gt;
&lt;li&gt;스택 버퍼 오버플로는 &lt;b&gt;오버플로가 발생하는 지점&lt;/b&gt;과 &lt;b&gt;크래시가 감지되는 지점&lt;/b&gt;이 다를 수 있다. bt로 크래시 지점만 보고 끝내면 안 되고, frame/list로 실제 소스 위치까지 내려가서 확인해야 한다.&lt;/li&gt;
&lt;li&gt;bt 결과에서 ??로 나오는 프레임은 라이브러리 내부 구현이라 무시해도 되고, 내 코드가 보이는 프레임(main () at bug.c:112)에 집중하면 된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>week5</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/30</guid>
      <comments>https://notes51162.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 22 Sep 2026 20:50:23 +0900</pubDate>
    </item>
    <item>
      <title>퀴즈 5번 : 어셈블리 &amp;rarr; C 코드 복원</title>
      <link>https://notes51162.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;cmpl / jle 조건부 분기를 읽다가 막혔던 지점들 정리&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;cmpl $0, %edi
jle  .L1
movl $1, %edx
jmp  .L2
.L1:
movl $0, %edx
.L2:
movl %edx, %eax
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int function(int x) {
    int y;
    if ( ____(1) )
        _____(2);
    else
        y = 0;
    return ____(3);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블 1 &amp;mdash; %edi가 갑자기 왜 나오지? x는 어디 있는 거야?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;: 코드 어디에도 x라는 글자가 없는데 갑자기 %edi가 등장해서 당황함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;: 함수 인자는 코드에 명시적으로 &quot;담기는&quot; 과정이 안 보여요. &lt;b&gt;함수가 시작되는 순간, 이미 첫 번째 인자가 %edi(4바이트 정수 인자 규칙)에 담긴 채로 시작&lt;/b&gt;하기 때문이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;: 어셈블리에서 %edi를 보면 자동으로 **&quot;이건 x다&quot;**라고 치환해서 읽는 습관을 들이기. 별도의 &quot;대입&quot; 코드가 있어야 하는 게 아니라, 함수 진입 시점부터 이미 그렇게 세팅되어 있다는 걸 전제로 깔고 읽어야 함.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블 2 &amp;mdash; cmp가 뭔지 몰라서 첫 줄부터 막힘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;: cmpl $0, %edi를 보고 &quot;이게 뭘 하는 명령어인지&quot; 전혀 감이 안 옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;: cmp를 배운 적이 없거나, 배웠어도 &quot;빼기랑 뭐가 다른지&quot;가 명확하지 않았음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 &amp;mdash; 핵심 비유&lt;/b&gt;: 저울에 물건 두 개를 올려서 &quot;몇 그램 차이 나는지&quot;는 필요 없고 &quot;어느 쪽이 무거운지&quot;만 알고 싶은 상황과 같음.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;cmpl $0, %edi
   &amp;darr;
x - 0을 계산은 하되, 그 결과값은 버림
대신 &quot;결과가 양수/음수/0이었는지&quot;만 CPU의 플래그(표시등)에 기록
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 sub(진짜 빼기)를 안 쓰는지&lt;/b&gt;: sub를 쓰면 계산 결과가 레지스터에 그대로 덮어써져서, x의 원래 값을 잃어버림. cmp는 &quot;비교만 하고 원본은 보존&quot;하기 위한 전용 명령어.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블 3 &amp;mdash; jle가 정확히 뭘 비교하는 건지 모르겠음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;: jle .L1이 &quot;무엇을&quot; &quot;무엇과&quot; 비교해서 점프하는 건지 연결이 안 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;: jle는 그 자체로는 아무것도 비교하지 않음 &amp;mdash; &lt;b&gt;바로 앞줄 cmp가 세팅해놓은 플래그를 읽기만 하는 명령어&lt;/b&gt;라는 연결고리를 놓침.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;jle = jump if less or equal (작거나 같으면 점프)

바로 앞 cmp에서 계산한 게 &quot;x - 0&quot; 이었으므로:
jle의 조건 = &quot;x - 0 &amp;le; 0&quot; = &quot;x &amp;le; 0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정리&lt;/b&gt;: cmp가 &quot;재료(플래그)&quot;를 만들고, 바로 다음에 오는 조건부 점프가 그 재료를 &quot;소비&quot;한다는 2단계 구조로 항상 짝지어 읽기.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블 4 (가장 헷갈렸던 지점) &amp;mdash; 왜 x &amp;le; 0이 아니라 x &amp;gt; 0으로 뒤집혀서 나오는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;: 분명 jle의 점프 조건은 x &amp;le; 0인데, 정답 C 코드에는 **if (x &amp;gt; 0)**이라고 반대로 적혀있음. &quot;그냥 조건 그대로 쓰면 안 되나?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인 &amp;mdash; 질문 자체가 다르다는 걸 놓침&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;점프 조건이 답하는 질문 = &quot;언제 else로 도망갈까?&quot;&lt;/li&gt;
&lt;li&gt;if 조건이 답하는 질문 = &quot;언제 then을 실행할까?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘은 &lt;b&gt;같은 게 아니라 서로 반대되는 상황&lt;/b&gt;을 가리킴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 구조로 확인&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cmpl $0, %edi
jle  .L1              &amp;larr; &quot;조건 맞으면(x&amp;le;0) else로 도망가라&quot;
movl $1, %edx           &amp;larr; 도망 안 갔을 때만 실행 = then 절 (y=1)
jmp  .L2
.L1:
movl $0, %edx              &amp;larr; 도망가서 도착 = else 절 (y=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 &amp;mdash; 뒤집기 공식&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;점프 조건(jle):  x &amp;le; 0     &amp;rarr;  이건 &quot;else로 가는 조건&quot;
        &amp;darr; 반대로 뒤집기
then 조건:      x &amp;gt; 0     &amp;rarr;  이게 실제 if문에 들어가는 조건
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;: &quot;비가 오면 우산을 챙겨라&quot;는 규칙만 있을 때, &quot;우산을 안 챙기는 상황&quot;이 궁금하면 그 규칙을 뒤집어야(=비가 안 오면) 답이 나오는 것과 같은 원리.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 정답&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int function(int x) {
    int y;
    if (x &amp;gt; 0)          // (1)
        y = 1;            // (2)
    else
        y = 0;
    return y;                // (3)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;재발 방지 체크리스트 &amp;mdash; 다음에 비슷한 문제 만나면&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;인자 &amp;rarr; 레지스터 매핑부터 확인&lt;/b&gt;: %edi, %esi 등이 어떤 C 변수에 대응하는지 먼저 표시해두기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cmp 줄 발견 시&lt;/b&gt;: &quot;오퍼랜드2 - 오퍼랜드1&quot;이 뭔지부터 적어보기 (여기선 x - 0)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건부 점프(jle, je, jg...) 발견 시&lt;/b&gt;: 그 점프가 &quot;어디로, 무슨 조건으로&quot; 가는지 적기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;★ 가장 중요&lt;/b&gt;: 조건부 점프는 대부분 **&quot;else로 가는 티켓&quot;**이지 &quot;then으로 가는 티켓&quot;이 아니므로, &lt;b&gt;if문 조건을 쓸 땐 점프 조건을 반드시 반대로 뒤집는다&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>week5</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/29</guid>
      <comments>https://notes51162.tistory.com/29#entry29comment</comments>
      <pubDate>Tue, 22 Sep 2026 15:37:22 +0900</pubDate>
    </item>
    <item>
      <title>발표 자료 &amp;mdash; CS:APP 3.8.4~3.8.5 (249~253쪽) 정리</title>
      <link>https://notes51162.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도입 (30초)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 발표는 **&quot;컴파일러가 다차원 배열 인덱싱을 어떻게 최적화하는가&quot;**에 대한 내용입니다. 핵심 질문은 하나예요 &amp;mdash; &lt;b&gt;배열 원소에 접근할 때마다 매번 곱셈을 해야 할까?&lt;/b&gt; 결론부터 말하면, 컴파일러는 최대한 이 곱셈을 피하려고 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 고정크기 배열 (3.8.4절) &amp;mdash; 2분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;#define N 16
typedef int fix_matrix[N][N];

int fix_prod_ele(fix_matrix A, fix_matrix B, long i, long k) {
    long j;
    int result = 0;
    for (j = 0; j &amp;lt; N; j++)
        result += A[i][j] * B[j][k];
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 두 16&amp;times;16 행렬을 곱했을 때, &lt;b&gt;결과의 (i,k) 위치 값&lt;/b&gt;을 구하는 함수입니다. 행렬 곱셈의 정의 그대로, A의 i행과 B의 k열을 내적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열 주소 공식 (식 3.1)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;&amp;amp;D[i][j] = xD + L(C&amp;middot;i + j)      # L=원소크기, C=열 개수
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴파일러의 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 코드는 매번 j로 인덱싱하며 곱셈을 반복하지만, gcc는 이걸 **포인터 3개(Aptr, Bptr, Bend)**로 바꿔버립니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;int *Aptr = &amp;amp;A[i][0];
int *Bptr = &amp;amp;B[0][k];
int *Bend = &amp;amp;B[N][k];
do {
    result += *Aptr * *Bptr;
    Aptr++;
    Bptr += N;
} while (Bptr != Bend);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 빨라지는가 &amp;mdash; 핵심&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N=16이 &lt;b&gt;컴파일 시점에 이미 알려진 상수&lt;/b&gt;이기 때문에, 주소 = xA + 64i 같은 계산에서 &amp;times;64를 **시프트 연산(salq $6)**으로 바꿀 수 있습니다.&lt;/li&gt;
&lt;li&gt;루프 안에서는 곱셈 없이 &lt;b&gt;덧셈만&lt;/b&gt; 반복합니다 (Aptr++, Bptr += 64).&lt;/li&gt;
&lt;li&gt;정리: **&quot;반복되는 곱셈 &amp;rarr; 사전 계산 한 번 + 반복 덧셈&quot;**으로 바꾸는 것 = 이번 절의 핵심 최적화 기법.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;+ 응용 예제 (연습문제 3.40, 대각선 채우기)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; N; i++) A[i][i] = val;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대각선은 행과 열이 동시에 1씩 증가하므로, 포인터가 &lt;b&gt;&quot;N+1&quot;칸(=68바이트)씩&lt;/b&gt; 건너뛰면 됩니다. 같은 원리의 변형일 뿐입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 가변크기 배열 (3.8.5절) &amp;mdash; 1.5분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배경&lt;/b&gt;: C99부터는 배열 크기를 &lt;b&gt;변수로 선언&lt;/b&gt;할 수 있게 됐습니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;int var_ele(long n, int A[n][n], long i, long j) {
    return A[i][j];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(주의: 크기 n이 반드시 배열보다 &lt;b&gt;먼저&lt;/b&gt; 선언되어야 함 &amp;mdash; 컴파일러가 차원을 미리 알아야 하므로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고정크기 버전과의 결정적 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정크기 (N=16) 가변크기 (n)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n&amp;middot;i 계산&lt;/td&gt;
&lt;td&gt;salq $6 (시프트)&lt;/td&gt;
&lt;td&gt;imulq (진짜 곱셈)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; n이 &lt;b&gt;실행 중에야 정해지는 값&lt;/b&gt;이라, 컴파일러가 &quot;몇 비트 시프트할지&quot;를 미리 알 수 없습니다. 그래서 곱셈을 피할 수 없습니다 &amp;mdash; **&quot;이 경우에는 어쩔 수 없다&quot;**고 책도 명시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;루프 최적화도 여전히 일부 적용됨&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;int *Arow = A[i];
int *Bptr = &amp;amp;B[0][k];
for (j = 0; j &amp;lt; n; j++) {
    result += Arow[j] * *Bptr;
    Bptr += n;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B는 포인터로 바뀌지만, A는 인덱스(Arow[j])를 그대로 유지 &amp;mdash; 책은 이게 &quot;필연적 차이가 아니라 컴파일러의 선택에 따른 결과일 뿐&quot;이라고 설명합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 결론 &amp;mdash; 이 두 절을 관통하는 핵심 원리 (1분)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열 인덱싱은 본질적으로 곱셈(식 3.1)이다. 하지만 같은 배열을 규칙적으로 반복 접근할 때는, 그 곱셈을 매번 새로 계산하지 않고 &quot;이전 위치 + 고정 간격&quot;이라는 덧셈으로 대체할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고정크기&lt;/b&gt;: 크기를 컴파일 시점에 아니까 &amp;rarr; 곱셈 자체(&amp;times;L, &amp;times;C)도 시프트로 바꿀 수 있어 &lt;b&gt;가장 빠름&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가변크기&lt;/b&gt;: 크기를 실행 시점에만 아니까 &amp;rarr; 곱셈은 피할 수 없지만, &lt;b&gt;루프 안 반복 곱셈은 덧셈으로 대체 가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &quot;배열 크기를 컴파일러가 얼마나 미리 알고 있느냐&quot;가 최적화 수준을 결정한다는 것이 두 절의 핵심 메시지입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표용으로 슬라이드(PPT)나 문서 파일로 변환해드릴까요, 아니면 이 정리 그대로 참고하시면서 말로 발표하실 건가요?&lt;/p&gt;</description>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/28</guid>
      <comments>https://notes51162.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 18 Sep 2026 16:13:54 +0900</pubDate>
    </item>
    <item>
      <title>3.6 ~ 3.6.2 정리</title>
      <link>https://notes51162.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.6 ~ 3.6.2 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.6 도입부 &amp;mdash; 왜 이 챕터가 필요한가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지의 코드는 전부 순서대로 실행되는 straight-line code였음&lt;/li&gt;
&lt;li&gt;하지만 if/while/for/switch는 &lt;b&gt;조건에 따라 실행 순서 자체가 달라져야&lt;/b&gt; 함&lt;/li&gt;
&lt;li&gt;기계어가 조건부 동작을 구현하는 두 가지 방법:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;control flow&lt;/b&gt;를 바꾸는 방법 &amp;rarr; jump 명령어 사용 (더 일반적, 더 흔함 &amp;rarr; 먼저 다룸)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;data flow&lt;/b&gt;를 바꾸는 방법 &amp;rarr; 실행 순서는 그대로, 계산되는 값 자체가 달라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컴파일러의 역할: CPU가 이해하는 건 &quot;조건에 따라 점프해라&quot;라는 저수준 기능뿐이라, 이걸 조합해서 if/while 같은 고수준 문법을 흉내 냄&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.6.1 Condition Codes &amp;mdash; 조건을 검사하는 하드웨어 메커니즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 개념&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU에는 CF/ZF/SF/OF라는 1비트 플래그 4개가 있음&lt;/li&gt;
&lt;li&gt;이건 &lt;b&gt;저장소가 아니라 신호등&lt;/b&gt; &amp;mdash; 최근 산술/논리 연산이 실행될 때마다 그 결과 특성으로 즉시 덮어써짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네 가지 플래그&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플래그 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CF&lt;/td&gt;
&lt;td&gt;unsigned 오버플로우 (자리올림 발생)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZF&lt;/td&gt;
&lt;td&gt;결과가 0이었나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SF&lt;/td&gt;
&lt;td&gt;결과가 음수였나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OF&lt;/td&gt;
&lt;td&gt;signed(2의 보수) 오버플로우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;add, sub, and 같은 대부분의 연산은 계산하는 김에 플래그도 부수적으로 세팅 (단, leaq는 예외 &amp;mdash; 플래그 안 건드림)&lt;/li&gt;
&lt;li&gt;inc/dec는 OF&amp;middot;ZF는 세팅하지만 CF는 그대로 둠 (관례적 예외)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cmp, test&lt;/b&gt;: 계산 결과 자체는 버리고 &lt;b&gt;플래그만 남기는 전용 명령어&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cmp S1, S2 &amp;rarr; S2 - S1 계산 (sub와 동작 동일, 결과 저장 안 함)&lt;/li&gt;
&lt;li&gt;test S1, S2 &amp;rarr; S1 &amp;amp; S2 계산 (and와 동작 동일, 결과 저장 안 함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;두 역할이 분리되어 있음: &lt;b&gt;플래그를 세팅하는 놈&lt;/b&gt;(cmp 등) vs &lt;b&gt;플래그를 읽고 판단하는 놈&lt;/b&gt;(다음 절에서 배울 jump/set)&lt;/li&gt;
&lt;li&gt;플래그는 매 연산마다 덮어써지므로, 컴파일러는 보통 cmp/test 직후에 그 결과를 쓰는 명령어를 바로 붙임&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;3.6.1 Condition Codes (조건 코드) 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;조건 코드란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에는 일반적인 정수 레지스터(%rax, %rdi 등) 말고도, &lt;b&gt;1비트짜리 특수 레지스터 몇 개&lt;/b&gt;가 따로 있어요. 이걸 **조건 코드(condition code)**라고 불러요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 간단해요: &lt;b&gt;가장 최근에 실행된 산술/논리 연산의 결과가 어땠는지&lt;/b&gt;(0이었는지, 음수였는지, 오버플로우가 났는지 등)를 이 비트들에 자동으로 기록해둬요. 그리고 나중에 이 비트들을 검사해서 조건부 분기(if문 같은 것)를 구현하는 거예요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네 가지 핵심 플래그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 뜻 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Carry Flag&lt;/td&gt;
&lt;td&gt;최근 연산에서 최상위 비트에서 자리올림(carry)이 발생했다 &amp;rarr; &lt;b&gt;unsigned 연산의 오버플로우 감지용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ZF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Zero Flag&lt;/td&gt;
&lt;td&gt;최근 연산 결과가 &lt;b&gt;0&lt;/b&gt;이었다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Sign Flag&lt;/td&gt;
&lt;td&gt;최근 연산 결과가 &lt;b&gt;음수&lt;/b&gt;였다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Overflow Flag&lt;/td&gt;
&lt;td&gt;최근 연산에서 &lt;b&gt;2의 보수(signed) 오버플로우&lt;/b&gt;가 발생했다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시로 확인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 코드 t = a + b;를 add 명령어로 실행했다고 하면, 각 플래그는 이렇게 설정돼요:&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;CF &amp;larr; (unsigned) t &amp;lt; (unsigned) a          # unsigned 오버플로우
ZF &amp;larr; (t == 0)                             # 0인가
SF &amp;larr; (t &amp;lt; 0)                              # 음수인가
OF &amp;larr; (a&amp;lt;0 == b&amp;lt;0) &amp;amp;&amp;amp; (t&amp;lt;0 != a&amp;lt;0)         # signed 오버플로우
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 OF 부분이 좀 복잡해 보이는데, 풀어보면 이런 뜻이에요: &lt;b&gt;&quot;a와 b가 둘 다 같은 부호였는데, 결과 t의 부호가 그거랑 다르게 나왔다&quot;&lt;/b&gt; &amp;rarr; 이게 바로 signed 오버플로우의 정의예요. (양수+양수인데 결과가 음수로 나오거나, 음수+음수인데 결과가 양수로 나오는 경우)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예외: leaq는 플래그를 안 건드린다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;leaq(주소 계산용 명령어)는 조건 코드를 전혀 바꾸지 않아요. 이건 말 그대로 &quot;주소 계산&quot;이 목적이지 &quot;산술 연산 결과&quot;가 목적이 아니기 때문이에요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연산 종류별로 플래그가 설정되는 방식이 조금씩 다르다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;논리 연산&lt;/b&gt;(xor 등): CF와 OF는 무조건 0으로 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시프트 연산&lt;/b&gt;: CF는 &quot;마지막으로 밀려난 비트&quot;로 설정, OF는 0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;inc/dec&lt;/b&gt;: OF와 ZF는 설정되지만, &lt;b&gt;CF는 건드리지 않고 그대로 둠&lt;/b&gt; (책에서도 &quot;이유는 다루지 않겠다&quot;고 하고 넘어가는, 그냥 관례적인 예외 케이스)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;별도로 존재하는 &quot;플래그만 설정하는&quot; 명령어: cmp, test&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기가 3.6.1절의 실질적인 하이라이트예요. 지금까지 본 플래그들은 &quot;다른 계산을 하는 김에 부수적으로&quot; 설정됐는데, &lt;b&gt;아예 계산 결과 자체는 버리고 플래그만 남기기 위한 전용 명령어&lt;/b&gt;가 따로 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cmp (Compare)&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;cmp S1, S2   &amp;rarr;  S2 - S1 을 계산하지만, 그 결과는 저장 안 하고 플래그만 남김
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 방식은 sub(빼기)와 똑같은데, 차이는 &lt;b&gt;뺀 결과값을 목적지에 실제로 쓰지 않는다&lt;/b&gt;는 것뿐이에요. 두 값이 같으면 ZF가 1로 설정돼요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;test&lt;/h3&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;test S1, S2  &amp;rarr;  S1 &amp;amp; S2 (AND 연산)을 계산하지만, 결과는 버리고 플래그만 남김
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 방식은 and(논리곱)와 똑같은데, 역시 결과값 저장 없이 플래그만 남겨요. 흔히 쓰이는 패턴이 testq %rax, %rax인데, 이건 자기 자신과 AND 연산을 해서 **&quot;%rax가 0인지, 음수인지, 양수인지&quot;**를 그냥 확인하려는 용도예요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이렇게 &quot;결과는 버리고 플래그만 남기는&quot; 명령어가 따로 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 코드에서 if (a &amp;lt; b)를 쓸 때, 우리는 a - b라는 &lt;b&gt;값 자체는 필요 없고&lt;/b&gt;, 단지 &quot;a가 b보다 작은가?&quot;라는 &lt;b&gt;관계&lt;/b&gt;만 알고 싶은 거예요. 그런데 sub를 쓰면 목적지 레지스터의 값이 실제로 바뀌어버려서, 원래 갖고 있던 값을 잃어버려요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cmp는 바로 이 문제를 해결해줘요: **&quot;연산은 하되, 원래 레지스터 값은 보존하고 플래그만 얻고 싶다&quot;**는 니즈에 정확히 맞는 명령어예요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리 &amp;mdash; 3.6.1절에서 남겨야 할 것&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU에는 CF/ZF/SF/OF라는 1비트 플래그들이 있고, 이건 &lt;b&gt;최근 산술/논리 연산의 결과 특성&lt;/b&gt;을 나타낸다&lt;/li&gt;
&lt;li&gt;대부분의 연산 명령어(add, sub, and 등)는 계산하는 김에 이 플래그도 같이 설정해준다 (단, leaq는 예외)&lt;/li&gt;
&lt;li&gt;cmp/test는 &lt;b&gt;계산 결과 자체는 버리고 플래그만 얻기 위한 전용 명령어&lt;/b&gt;다 &amp;mdash; if (a&amp;lt;b) 같은 비교 연산 구현의 기초가 된다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 절(3.6.2)에서는 바로 이 플래그들을 &lt;b&gt;실제로 어떻게 읽어와서 활용하는지&lt;/b&gt;(set 계열 명령어) 다루게 될 거예요 &amp;mdash; 방금 나온 cmp가 남긴 플래그를 실제로 검사하는 방법이죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.6.2 Accessing the Condition Codes &amp;mdash; set 계열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 개념&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;set 명령어 = 플래그를 읽어서 조건이 참이면 1, 거짓이면 0을 &lt;b&gt;바이트 값&lt;/b&gt;으로 저장&lt;/li&gt;
&lt;li&gt;cmp가 &quot;신호등을 켜는&quot; 역할이면, set은 &quot;신호등을 보고 진짜 숫자(0/1)로 바꿔주는&quot; 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;접미사마다 확인하는 플래그 조합이 다름 (sete&amp;rarr;ZF, setl&amp;rarr;SF^OF 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;signed용&lt;/b&gt;(setg, setl, setge, setle)과 &lt;b&gt;unsigned용&lt;/b&gt;(seta, setb, setae, setbe)이 따로 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;signed는 SF&amp;middot;OF 조합으로 판단 (오버플로우 고려 필요)&lt;/li&gt;
&lt;li&gt;unsigned는 CF만 보면 됨&lt;/li&gt;
&lt;li&gt;컴파일러는 C 변수 타입(int vs unsigned)을 보고 알맞은 set을 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;딱 1바이트만 씀&lt;/b&gt; (%al 등) &amp;mdash; 나머지 상위 바이트는 그대로 안 건드림
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래서 보통 xorl %eax, %eax로 레지스터를 미리 0으로 클리어한 뒤 set으로 최하위 바이트만 세팅하는 패턴이 함께 나옴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 줄로 이어보면&lt;/b&gt;: cmp/test가 플래그를 세팅 &amp;rarr; set이 그 플래그를 0/1 &quot;값&quot;으로 변환. 다음 3.6.3(Jump Instructions)에서는 값으로 바꾸는 대신 &lt;b&gt;아예 실행 위치 자체를 점프&lt;/b&gt;시키는 방식이 나와서, set과 대조해보면 이해가 잘 될 거예요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.6.2 Accessing the Condition Codes &amp;mdash; 조건 코드를 &quot;꺼내 쓰는&quot; 법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리 &amp;mdash; 3.6.2에서 남길 것&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cmp/test(혹은 다른 산술 연산)가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;플래그를 세팅&lt;/b&gt;하고, set 계열이 그&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;플래그를 읽어서 0/1 값으로 변환&lt;/b&gt;한다 &amp;mdash; 역할이 분리되어 있다.&lt;/li&gt;
&lt;li&gt;set 뒤 접미사는 &quot;어떤 조건을 물을지&quot;를 정하고, 그중에서도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;signed용(setg, setl...)과 unsigned용(seta, setb...)이 따로 있다&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; 피연산자 타입에 따라 컴파일러가 골라 쓴다.&lt;/li&gt;
&lt;li&gt;set은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1바이트만&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쓰기 때문에, 레지스터 전체를 0/1 값으로 쓰려면 보통 그 앞에 xorl 같은 클리어 명령어가 같이 나온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 절(3.6.3, Jump Instructions)에서는 드디어 이 플래그들을 이용해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실제로 실행 흐름 자체를 바꾸는&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;jump 계열이 등장해요 &amp;mdash; set이 &quot;값으로 바꾸는&quot; 거였다면, jump는 &quot;점프해버리는&quot; 방식이라 이 둘을 대조해서 보면 이해가 훨씬 잘 될 거예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지금까지의 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.6.1에서 배운 걸 정리하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cmp, test, add 같은 연산이 실행되면 CF/ZF/SF/OF 플래그가 세팅된다&lt;/li&gt;
&lt;li&gt;근데 이 플래그들은 그냥 &lt;b&gt;CPU 내부의 1비트 신호등&lt;/b&gt;일 뿐이에요. 이 자체로는 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;C 프로그램이 다룰 수 있는 &quot;값&quot;이 아니&lt;/span&gt;에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 궁금한 게 생기죠 &amp;mdash; if (a &amp;lt; b)처럼 결과를 &lt;b&gt;0 또는 1이라는 값&lt;/b&gt;으로 써야 하는 경우엔 어떻게 하지? 바로 이 역할을 하는 게 &lt;b&gt;set 계열 명령어&lt;/b&gt;예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;set 계열 명령어란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로: **&quot;플래그들을 읽어서, 조건이 참이면 1, 거짓이면 0을 어떤 바이트에 저장해라&quot;**라는 명령어예요.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;cmp %rsi, %rdi     ; rdi - rsi 계산, 플래그만 세팅 (결과는 버림)
setl %al           ; &quot;조건이 less-than이었나?&quot; 플래그 확인 &amp;rarr; 맞으면 %al = 1, 아니면 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;cmp가 &quot;신호등을 켜는&quot; 역할이었다면, set은 &quot;그 신호등을 보고 0 아니면 1이라는 진짜 숫자로 바꿔주는&quot; 역할&lt;/span&gt;이에요. 이제 이 값은 레지스터에 들어있는 진짜 데이터니까, 이후 코드에서 자유롭게 쓸 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여러 종류의 set이 있는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;set 뒤에 붙는 접미사가 다 다른데&lt;/span&gt;(sete, setl, setg, seta...), 이건 **&quot;어떤 조건을 물어볼 것인가&quot;**가 다른 거예요. 조건마다 확인해야 할 플래그 조합이 다르거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 뜻 확인하는 조건&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sete (= setz)&lt;/td&gt;
&lt;td&gt;Equal&lt;/td&gt;
&lt;td&gt;ZF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setne (= setnz)&lt;/td&gt;
&lt;td&gt;Not Equal&lt;/td&gt;
&lt;td&gt;~ZF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sets&lt;/td&gt;
&lt;td&gt;Sign(음수)&lt;/td&gt;
&lt;td&gt;SF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setg (= setnle)&lt;/td&gt;
&lt;td&gt;signed 기준 Greater&lt;/td&gt;
&lt;td&gt;~(SF^OF) &amp;amp; ~ZF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setl (= setnge)&lt;/td&gt;
&lt;td&gt;signed 기준 Less&lt;/td&gt;
&lt;td&gt;SF^OF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seta (= setnbe)&lt;/td&gt;
&lt;td&gt;unsigned 기준 Above&lt;/td&gt;
&lt;td&gt;~CF &amp;amp; ~ZF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setb (= setnae)&lt;/td&gt;
&lt;td&gt;unsigned 기준 Below&lt;/td&gt;
&lt;td&gt;CF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심 포인트 하나: &lt;b&gt;setg/setl(signed용)와 seta/setb(unsigned용)가 따로 있다는 것.&lt;/b&gt; 똑같이 &quot;크다/작다&quot;를 묻는 건데도, signed 비교냐 unsigned 비교냐에 따라 확인하는 플래그가 달라요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;signed는 SF(부호)와 OF(오버플로우)를 조합해서 판단해요. 오버플로우가 났으면 부호만 보고 판단하면 틀리니까요.&lt;/li&gt;
&lt;li&gt;unsigned는 아예 CF(캐리)만 보면 돼요. unsigned는 &quot;음수&quot;라는 개념이 없으니 오버플로우 걱정도 다르게 처리되거든요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;span style=&quot;background-color: #f3c000;&quot;&gt;이게 왜 중요하냐면, &lt;b&gt;컴파일러가 C 코드의 변수 타입(int냐 unsigned냐)을 보고 다른 set 명령어를 골라서 생성&lt;/b&gt;하기 때문이에요. C 코드만 보면 똑같이 a &amp;lt; b인데, a, b가 int냐 unsigned int냐에 따라 어셈블리는 setl이 나올 수도, setb가 나올 수도 있어요.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문법상 특이한 점 &amp;mdash; 딱 1바이트만 건드린다&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;setl %al
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set 명령어는 &lt;b&gt;레지스터 전체(예: %rax 64비트)가 아니라 딱 한 바이트(%al)만&lt;/b&gt; 씁니다. 그리고 나머지 바이트들은 그대로 안 건드려요 &amp;mdash; 즉 원래 %rax에 쓰레기 값이 있었다면, setl %al을 실행해도 상위 바이트들은 여전히 그 쓰레기 값 그대로예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보통 이런 패턴이 같이 나와요:&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;xorl %eax, %eax    ; %eax(따라서 %rax) 전체를 0으로 미리 클리어
cmp  %rsi, %rdi
setl %al           ; 최하위 1바이트만 0 또는 1로 세팅
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xorl %eax, %eax로 미리 레지스터를 0으로 밀어둔 다음에 set으로 최하위 바이트만 갱신하면, 결과적으로 %rax 전체가 깔끔하게 0 또는 1이 되는 거예요. (참고로 32비트 레지스터에 쓰면 상위 32비트도 자동으로 0이 되는 x86-64 규칙 때문에 xorl을 씀 &amp;mdash; 이건 3.4절에서 나온 내용이죠.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;</description>
      <category>week4</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/27</guid>
      <comments>https://notes51162.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 17 Sep 2026 21:59:25 +0900</pubDate>
    </item>
    <item>
      <title>트러블 슈팅 대본</title>
      <link>https://notes51162.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 000입니다. 연결 리스트 3번의 트러블 슈팅 과정을 공유하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;문제&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 원래 순서를 유지하면서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;짝수는&lt;span&gt;&amp;nbsp; 리스트&amp;nbsp;&lt;/span&gt;&lt;/span&gt;앞에, 홀수는 뒤로 이동시키는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실행해보니 원하는 결과가 나오지 않았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;원인&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인 정리해보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홀수를 지웠을 때는 뒤 원소가 shif되니 인덱스는 그대로 두고 같은 자리를 다시 확인해야 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짝수일 때는 다음 자리로 넘어가야 하니 인덱스를 늘려야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 두 경우 모두 &quot;하나를 확인했다&quot;는 사실은 같기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복 횟수를 세는 변수는 항상 늘어나야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;해결&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 인덱스 증가와 반복 횟수 증가는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 조건으로 움직이는 별개의 값이었던 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 for문의 증가식을 비우고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치를 나타내는 i와 반복 횟수를 세는 count를 분리해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 다른 조건으로 늘어나게 만들어 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;배운점&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제를 풀면서 몇 가지를 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 리스트에서 원소를 지우면 뒤 원소들이 당겨지기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 기반 반복문에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 이동과 반복 횟수 카운트를 분리해서 관리해야 안전하다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, for문의 증가식은 조건 없이 항상 실행되기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 따라 다르게 움직여야 할 때는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증가식 자리를 비우고 반복문 안에서 직접 처리해야 한다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 코드를 짜기 전에 주석부터 한 줄씩 달아보면서 각 줄이 실제로 무엇을 하는지 먼저 말로 설명해보는 방법을 익혔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하니 막연히 코드를 치는 게 아니라 로직을 먼저 검증하고 구현할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자 답을 구하는 것보다 이렇게 동료와 함께 원인을 짚어가는 과정이 훨씬 오래 기억에 남았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상입니다. 감사합니다.&lt;/p&gt;</description>
      <category>week4</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/26</guid>
      <comments>https://notes51162.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 17 Sep 2026 12:59:25 +0900</pubDate>
    </item>
    <item>
      <title>LL 05: frontBackSplitLL 풀이 과정</title>
      <link>https://notes51162.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 연결리스트를 앞부분(front)과 뒷부분(back), 두 개의 서브리스트로 나누는 함수를 작성하는 문제.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607569613&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void frontBackSplitLL(LinkedList *ll, LinkedList *resultFrontList, LinkedList *resultBackList);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;규칙&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원소 개수가 홀수면, 남는 원소 1개는 &lt;b&gt;front&lt;/b&gt;로 간다.&lt;/li&gt;
&lt;li&gt;예시: 2, 3, 5, 6, 7 (5개) &amp;rarr; frontList: 2, 3, 5 / backList: 6, 7&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 코드&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607544168&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void frontBackSplitLinkedList(LinkedList *ll, LinkedList *resultFrontList, LinkedList *resultBackList)
{
    int len = ll-&amp;gt;size % 2 == 0 ? ll-&amp;gt;size / 2 : ll-&amp;gt;size / 2 + 1; // front가 가져갈 개수
    int cnt = 0;           // 처리한 원소 개수 (i와는 별개의 카운터)
    int total = ll-&amp;gt;size;  // 루프 시작 전 고정해둔 원본 크기

    for (int i = 0; cnt &amp;lt; total; ) {
        int val = findNode(ll, i)-&amp;gt;item;

        if (cnt &amp;lt; len) {
            insertNode(resultFrontList, resultFrontList-&amp;gt;size, val);
            removeNode(ll, 0);
        } else {
            insertNode(resultBackList, resultBackList-&amp;gt;size, val);
            removeNode(ll, 0);
        }
        cnt += 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽질 기록 &amp;mdash; 어떤 실수들을 거쳤나&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 루프 종료 조건을 ll-&amp;gt;size로 잡았을 때&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607605124&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (int i = 0; cnt &amp;lt; ll-&amp;gt;size; )   // ❌&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ll-&amp;gt;size는 removeNode가 호출될 때마다 계속 줄어드는 값이다. cnt는 늘어나고 ll-&amp;gt;size는 줄어드니, 서로 반대 방향으로 움직이다가 예상보다 훨씬 빨리 조건이 거짓이 되어버린다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트레이스 결과&lt;/b&gt;: cnt=3, ll-&amp;gt;size=2 시점에 3&amp;lt;2가 거짓이 되어 루프가 조기 종료. 뒤쪽 원소(6, 7)가 어디로도 옮겨지지 못한 채 방치됨.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교훈&lt;/b&gt;: 루프 종료 조건은 &lt;b&gt;변하지 않는 고정값&lt;/b&gt;이어야 한다. 그래서 시작 시점에 int total = ll-&amp;gt;size;로 미리 스냅샷을 떠둬야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. removeNode(ll, ll-&amp;gt;size)로 되돌아간 실수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ll을 순회할 때 매번 인덱스 0을 지우면 뒤 원소가 앞으로 당겨오므로, &lt;b&gt;항상 0번 인덱스를 지워야&lt;/b&gt; 순서대로 처리할 수 있다. ll-&amp;gt;size(범위 밖 인덱스)를 지우려 하면 애초에 존재하지 않는 위치라 문제가 생긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607619213&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;removeNode(ll, 0);   // ✅ 항상 맨 앞을 지운다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. insertNode에 엉뚱한 리스트의 size를 넣었을 때&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607631396&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;insertNode(resultFrontList, ll-&amp;gt;size, val);   // ❌&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 위치(맨 뒤에 쌓기)는 &lt;b&gt;삽입 대상 리스트 자신의 크기&lt;/b&gt;를 기준으로 해야 한다. ll-&amp;gt;size는 원본 리스트의 (계속 줄어드는) 크기일 뿐, resultFrontList의 크기와는 무관하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789607643255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;insertNode(resultFrontList, resultFrontList-&amp;gt;size, val);   // ✅
insertNode(resultBackList, resultBackList-&amp;gt;size, val);     // ✅&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 인덱스가 정확히 어느 리스트를 기준으로 한 값인가&quot;를 매번 명확히 구분하는 게 핵심이었다. 3번(insertSortedLL), 4번(moveEvenItemsToBackLL) 문제에서 insertNode(ll, ll-&amp;gt;size, val) 패턴에 익숙해져 있다 보니, 대상이 여러 리스트로 늘어났을 때 무의식적으로 같은 리스트 이름(ll)을 재사용하는 실수가 나왔다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. cnt와 i를 혼동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i는 for문 안에서 선언만 되고 한 번도 증가되지 않는다(findNode(ll, i)도 실질적으로 항상 findNode(ll, 0)). 반면 cnt는 &quot;지금까지 몇 개의 원소를 처리했는가&quot;를 세는 &lt;b&gt;별도의 카운터&lt;/b&gt;로, front/back 분기 조건(cnt &amp;lt; len)과 루프 종료 조건(cnt &amp;lt; total) 모두에 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 if(cnt &amp;lt;= len)처럼 경계값을 잘못 잡아 &lt;b&gt;off-by-one 오류&lt;/b&gt;가 나기도 했다. cnt는 0부터 세므로, len=3일 때 front로 가야 할 cnt 값은 0, 1, 2 세 개뿐 &amp;mdash; cnt &amp;lt; len이 정확한 조건이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 원리 정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지는 3번(moveOddItemsToBackLL), 4번(moveEvenItemsToBackLL) 문제에서도 반복됐던 패턴이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;루프 종료 조건은 고정값이어야 한다.&lt;/b&gt; 순회 중 계속 변하는 값(ll-&amp;gt;size)을 조건에 쓰면 안 되고, 시작 시점에 스냅샷(total)을 떠서 써야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;removeNode(ll, 0)으로 순회할 때는 인덱스를 항상 0으로 고정한다.&lt;/b&gt; 지울 때마다 뒤 원소가 당겨오기 때문.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삽입 위치는 &quot;삽입 대상&quot; 리스트 자신의 size를 기준으로 잡는다.&lt;/b&gt; 여러 리스트를 다룰 때 어떤 size가 어느 리스트 것인지 헷갈리지 않도록 주의.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>week4</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/25</guid>
      <comments>https://notes51162.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 17 Sep 2026 10:14:15 +0900</pubDate>
    </item>
    <item>
      <title>LL 01</title>
      <link>https://notes51162.tistory.com/24</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 코드&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789525237578&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int insertSortedLL(LinkedList *ll, int item)
{
	int i = 0;

	for(i = 0; i &amp;lt; ll-&amp;gt;size; i++){
		int val = findNode(ll, i) -&amp;gt; item;
		
		if (val == item) {
			return -1;
		} else if (val &amp;gt; item) {
			insertNode(ll,i,item);
			return i;
		}
	}
	insertNode(ll,i,item);
	return i;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  핵심 개념: 포인터(*)로 넘긴다는 것&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;LinkedList *ll의 의미&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ll은 LinkedList 구조체 몸통 자체가 아니라, 그 몸통이 저장된 &lt;b&gt;주소값을 담는 포인터 변수&lt;/b&gt;다.&lt;/li&gt;
&lt;li&gt;main()에서 호출할 때 insertSortedLL(&amp;amp;ll, i)처럼 &amp;amp;ll(주소)을 넘기며, 이 값이 함수 파라미터 ll에 복사되어 저장된다.&lt;/li&gt;
&lt;li&gt;즉 main()의 ll(진짜 몸통)과 함수 안의 ll(포인터 변수)은 이름만 같을 뿐 완전히 다른 별개의 변수다. 다만 함수 안의 ll이 담고 있는 값이 main의 ll을 &quot;가리키고&quot; 있을 뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  비유: 함수의 ll은 &quot;우리 집 주소가 적힌 쪽지&quot;이고, main의 ll은 진짜 &quot;우리 집&quot;이다. 쪽지와 집은 다른 물건이지만, 쪽지를 따라가면 집에 도착한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 주소로 넘길까?&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;효율성&lt;/b&gt; &amp;mdash; 구조체 전체를 복사하지 않고 주소값(8바이트)만 전달&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;원본 수정 가능&lt;/b&gt;&lt;/span&gt; &amp;mdash; 주소를 알고 있으므로 함수 안에서 ll-&amp;gt;size++ 같은 방식으로 원본 데이터를 직접 수정 가능 (C는 기본적으로 call by value이기 때문에, 원본을 바꾸려면 주소를 넘겨야 함)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 단, 포인터로 받았다고 항상 원본을 수정하는 건 아니다. 읽기만 하고 안 바꾸는 경우도 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;-&amp;gt; 연산자의 의미&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ll-&amp;gt;size 는 (*ll).size 와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;*ll : ll에 저장된 주소를 따라가서 실제 구조체 몸통에 도달 (역참조)&lt;/li&gt;
&lt;li&gt;.size : 그 몸통 안의 size 멤버 접근&lt;/li&gt;
&lt;li&gt;-&amp;gt;는 이 두 단계(역참조 + 멤버 접근)를 한 번에 처리하는 축약 연산자&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 헷갈리기 쉬운 포인트: *ll은 ll 변수 자신의 주소(&amp;amp;ll)로 가는 게 아니라, ll 안에 저장된 값(=main의 ll 주소)을 따라가는 것이다. 포인터 변수도 그 자체로 메모리 공간을 차지하는 변수이므로, &quot;변수 자신의 주소&quot;와 &quot;변수 안에 든 값(=가리키는 대상의 주소)&quot;은 별개의 개념이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;findNode도 같은 원리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ListNode *findNode(LinkedList *ll, int index);&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;받는 것도, 리턴하는 것도 항상 몸통이 아니라 &lt;b&gt;주소&lt;/b&gt;다.&lt;/li&gt;
&lt;li&gt;findNode(ll, i) &amp;rarr; i번째 노드의 주소를 리턴&lt;/li&gt;
&lt;li&gt;findNode(ll, i) -&amp;gt; item &amp;rarr; 그 주소로 찾아가서 노드의 item(실제 값)을 꺼냄&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드 전체에서 LinkedList든 ListNode든 몸통이 오가는 일은 없고 항상 주소만 오간다는 원칙이 일관되게 적용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  insertSortedLL 로직 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트가 정렬되어 있다는 전제 하에, 앞에서부터 훑으며 다음 세 가지 경우를 판단한다.&lt;/p&gt;
&lt;div&gt;조건의미동작
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;val == item&lt;/td&gt;
&lt;td&gt;이미 같은 값이 리스트에 존재 (중복)&lt;/td&gt;
&lt;td&gt;return -1 (삽입 안 함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;val &amp;gt; item&lt;/td&gt;
&lt;td&gt;현재 노드 값이 새 값보다 큼 &amp;rarr; 정렬 순서상 바로 이 자리가 삽입 위치&lt;/td&gt;
&lt;td&gt;insertNode(ll,i,item); return i;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;val &amp;lt; item&lt;/td&gt;
&lt;td&gt;아직 삽입할 자리가 아님&lt;/td&gt;
&lt;td&gt;아무 처리 없이 다음 반복으로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(for문을 끝까지 다 돎)&lt;/td&gt;
&lt;td&gt;끝까지 봐도 나보다 큰 값이 없었음 = 맨 뒤(혹은 빈 리스트의 첫 자리)에 넣어야 함&lt;/td&gt;
&lt;td&gt;for문 밖에서 insertNode(ll,i,item); return i;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;for문을 벗어난 후 i가 의미 있는 이유&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1789525326827&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(i = 0; i &amp;lt; ll-&amp;gt;size; i++){ ... }
insertNode(ll,i,item);
return i;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for문이 break/return 없이 자연스럽게 끝까지 돌면, 조건 i &amp;lt; ll-&amp;gt;size가 거짓이 되는 순간 멈추므로 탈출 시점의 i는 정확히 ll-&amp;gt;size와 같아진다.&lt;/li&gt;
&lt;li&gt;ll-&amp;gt;size는 곧 &quot;리스트의 맨 뒤 다음 자리&quot; 인덱스이므로, 이 i 값으로 insertNode를 호출하면 자동으로 맨 뒤에 삽입된다.&lt;/li&gt;
&lt;li&gt;리스트가 비어있는 경우(size == 0)도 동일한 원리로 i = 0이 유지되어 첫 노드로 삽입된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 else로 안 쓰고 for문 밖에 따로 뒀는가&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;return은 for문만 끝내는 게 아니라 &lt;b&gt;함수 전체를 즉시 종료&lt;/b&gt;시킨다. (break와 다름)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;따라서 for문 안에서 val == item 또는 val &amp;gt; item으로 return이 실행되면, 그 즉시 함수가 끝나버려서 for문 밖의 코드는 아예 실행되지 않는다.&lt;/li&gt;
&lt;li&gt;결과적으로 for문 밖의 코드는 &quot;for문 안에서 한 번도 return하지 않았을 때만&quot; 실행되는 구조가 되어, 문법적 else 없이도 논리적으로 같은 효과를 낸다.&lt;/li&gt;
&lt;li&gt;C에는 Python의 for-else 같은 문법이 없다.&lt;/li&gt;
&lt;li&gt;만약 val &amp;lt; item을 else로 처리해버리면, 매 반복마다 (아직 뒤에 더 큰 값이 있을지 모르는데도) 성급하게 삽입이 실행되어 정렬이 깨진다. 예: [2,5,8]에 10을 넣을 때 i=0에서 2&amp;lt;10이라고 바로 앞에 넣어버리면 [10,2,5,8]이 되는 오류 발생. &quot;끝까지 다 훑어야만 알 수 있는 정보&quot;이므로 for문 밖에서 딱 한 번 처리하는 게 맞는 설계다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  오늘 배운 핵심 정리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;포인터 변수는 몸통이 아니라 주소값을 담는 별개의 변수다.&lt;/li&gt;
&lt;li&gt;-&amp;gt;는 (*포인터).멤버의 축약형이며, 포인터에 저장된 값(주소)을 따라가 멤버에 접근한다.&lt;/li&gt;
&lt;li&gt;return은 반복문이 아니라 함수 전체를 즉시 종료시킨다.&lt;/li&gt;
&lt;li&gt;for문이 정상 종료되면 루프 변수는 종료 조건의 경계값(size)과 같아진다는 성질을 활용해 &quot;끝까지 못 찾았을 때&quot; 처리를 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;주석이 코드 로직과 항상 일치하는 건 아니므로, 직접 로직을 따라가며 검증하는 습관이 중요하다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>week4</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/24</guid>
      <comments>https://notes51162.tistory.com/24#entry24comment</comments>
      <pubDate>Wed, 16 Sep 2026 11:23:38 +0900</pubDate>
    </item>
    <item>
      <title>C언어 코드 이해하기(typedef, struct)</title>
      <link>https://notes51162.tistory.com/23</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;typedef란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typedef는 긴 타입 이름에 짧은 별명을 붙여주는 키워드다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typedef 없이 구조체를 선언하면, 이 구조체를 쓸 때마다 매번 struct 키워드를 붙여서 길게 써야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;struct _listnode {
    int item;
    struct _listnode *next;
};

struct _listnode myNode;  // 매번 이렇게 길게 써야 함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;typedef로 해결&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;typedef struct _listnode {
    int item;
    struct _listnode *next;
} ListNode;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 선언해두면, 이제부터 struct _listnode 대신 ListNode라는 짧은 별명으로 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;ListNode myNode;  // 짧게 쓸 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 포인트&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;typedef를 선언하는 시점 자체에는 큰 이득이 없다. 그냥 &quot;앞으로 이 이름을 쓰겠다&quot;고 준비해두는 것뿐이다.&lt;/li&gt;
&lt;li&gt;진짜 장점은 이 타입을 반복해서 사용할 때 드러난다. 함수 프로토타입, 매개변수, 지역 변수 선언 등 코드 곳곳에서 struct _listnode 대신 ListNode라고만 쓰면 되니 코드가 짧고 읽기 쉬워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;typedef 없이:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;struct _listnode *findNode(struct _linkedlist *ll, int index);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;typedef 사용 시:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;ListNode *findNode(LinkedList *ll, int index);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 요약&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typedef는 &quot;선언할 때&quot;가 아니라 &quot;그 타입을 계속 써먹을 때&quot; 진가가 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;struct란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct(구조체)는 서로 다른 종류의 여러 값을 하나로 묶어주는 상자다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 필요할까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &quot;사람&quot;이라는 정보를 표현하려면 이름, 나이 같은 여러 값이 필요하다. 이걸 각각 따로 변수로 관리하면 흩어져서 관리하기 번거롭다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;char name[20];
int age;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘이 원래 &quot;한 사람&quot;을 나타내는 정보인데, 코드만 보면 서로 아무 관련 없는 두 변수처럼 보인다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;struct로 묶기&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;struct person {
    char name[20];
    int age;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 name과 age가 &quot;person이라는 하나의 상자 안에 함께 들어있는 값&quot;이라는 게 명확해진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 사용 예: ListNode&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;typedef struct _listnode {
    int item;
    struct _listnode *next;
} ListNode;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 struct _listnode는 &quot;카드 한 장&quot;을 표현하는 상자다. 이 상자 안에는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;item &amp;mdash; 이 카드에 적힌 숫자값&lt;/li&gt;
&lt;li&gt;next &amp;mdash; 다음 카드를 가리키는 포인터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지가 함께 들어있다. 각각 따로 관리하지 않고, &quot;노드 하나&quot;라는 개념으로 묶어서 다루는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;값 꺼내 쓰기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체 안의 값을 쓸 때는 .(점)을 쓴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;struct person p;
p.age = 25;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터로 구조체를 가리킬 때는 -&amp;gt;(화살표)를 쓴다.&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;ListNode *cur;
cur-&amp;gt;item = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄 요약&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct는 서로 관련 있는 여러 값을 하나의 이름 아래 묶어서, &quot;이건 하나의 덩어리다&quot;라는 의미를 코드에 부여하는 도구다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot;상자를 직접 들고 있냐&quot; vs &quot;상자 주소만 들고 있냐&quot;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. .(점) &amp;mdash; 상자를 직접 들고 있을 때&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;struct person p;
p.age = 25;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 p는 &lt;b&gt;진짜 person 상자 그 자체&lt;/b&gt;예요. 상자를 손에 직접 쥐고 있으니까, 그냥 .으로 바로 안을 들여다볼 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. -&amp;gt;(화살표) &amp;mdash; 상자의 &quot;주소&quot;만 들고 있을 때&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;ListNode *cur;
cur-&amp;gt;item = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 cur는 **ListNode 상자 자체가 아니라, 그 상자가 어디 있는지 적힌 쪽지(주소)**예요. *가 붙어 있으니 포인터죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소만 들고 있는 상태에서 안의 내용물을 보려면, 먼저 &quot;그 주소로 찾아가서&quot; 상자를 열어야 해요. 그래서 -&amp;gt;를 씀녀 컴퓨터가 자동으로:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cur가 가리키는 주소로 이동&lt;/li&gt;
&lt;li&gt;그 자리에 있는 진짜 상자를 열어서&lt;/li&gt;
&lt;li&gt;item에 접근&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 단계를 대신 해줘요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사실 -&amp;gt;는 줄임말이다&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;xl&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;cur-&amp;gt;item = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 사실 아래 코드의 줄임 표현이에요:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;(*cur).item = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*cur는 &quot;cur가 가리키는 주소로 가서, 그 안의 진짜 상자를 꺼내라&quot;는 뜻이에요. 그렇게 진짜 상자를 꺼내고 나면 .으로 접근할 수 있죠. 근데 (*cur).item 이렇게 쓰기 귀찮으니까, C에서 cur-&amp;gt;item이라는 짧은 표현을 만들어준 거예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교 예시로 확실히 정리&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;c&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;rust&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;struct person p;      // p는 상자 자체
p.age = 25;            // 그냥 바로 열어서 씀

struct person *ptr = &amp;amp;p;  // ptr은 p의 &quot;주소&quot;
ptr-&amp;gt;age = 25;         // 주소로 찾아가서 열어서 씀 (= (*ptr).age = 25;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 코드 모두 결국 같은 p.age를 바꾸는 거지만:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;p는 상자를 직접 들고 있으니 .&lt;/li&gt;
&lt;li&gt;ptr는 주소만 들고 있으니 -&amp;gt; (또는 *ptr 로 먼저 열고 .)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 줄 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수가 구조체 자체면 ., 변수가 구조체를 가리키는 포인터면 -&amp;gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>week4</category>
      <author>신속다람</author>
      <guid isPermaLink="true">https://notes51162.tistory.com/23</guid>
      <comments>https://notes51162.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 16 Sep 2026 11:19:11 +0900</pubDate>
    </item>
  </channel>
</rss>