<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>EJ 개발 블로그</title>
    <link>https://dev-ej.tistory.com/</link>
    <description>프로젝트 및 개인공부를 하며 얻은 지식들을 정리하고 있습니다!</description>
    <language>ko</language>
    <pubDate>Tue, 8 Sep 2026 04:29:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Development &amp;amp; Study</managingEditor>
    <item>
      <title>[Pintos - Project 3] Pintos와 실제 OS의 ELF 로딩 방식 차이</title>
      <link>https://dev-ej.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Pintos Project 3 (Virutal Memory)를 구현하다가 이상한 점을 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELF 파일을 로딩하는 load_segment는 항상 VM_ANON 타입으로 페이지를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1778811481504&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// process.c - load_segment()
if (!vm_alloc_page_with_initializer(VM_ANON, upage,   // &amp;larr; 왜 ANON?
                                    writable, lazy_load_segment, aux))
    return false;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 파일에서 읽어오는 건데, 굳이 ANON 타입으로 페이지를 만들어서 파일 정보를 끊어버리는 구조가 이해가 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;evict될 때 원본 파일에서 다시 읽으면 되는데, 왜 swap에 저장할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pintos의 ELF 로딩과 mmap 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pintos에서 ELF 로딩과 mmap 구조를 살펴보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1778811743868&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ELF 로딩 (load_segment)
  &amp;rarr; VM_ANON 페이지 생성        &amp;larr; 파일 연결 끊김
  &amp;rarr; lazy_load_segment()에서 파일 1회 읽기
  &amp;rarr; 이후 evict 시 &amp;rarr; swap 영역으로&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1778811869314&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mmap syscall
  &amp;rarr; VM_FILE 페이지 생성        &amp;larr; 파일 연결 유지
  &amp;rarr; evict 시 &amp;rarr; 원본 파일로 flush(반영) 또는 discard&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load_segment는 lazy loading을 위해 파일 정보를 aux 인자로 넘기긴 하지만, 페이지 타입 자체는 VM_ANON이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 즉, 최초 1회 파일에서 읽은 뒤 파일과의 연결은 완전히 끊어진다.&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;반면, mmap은 파일과의 연결을 끊지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 구조체 안에 원본 파일 정보(file, offset)가 계속 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실제 OS는 어떻게 할까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux 같은 실제 OS는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;ELF 로딩에도 file-backed 페이지를 사용&lt;/b&gt;&lt;/span&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;size18&quot;&gt;&lt;b&gt;코드 세그먼트(.text, .rodata(read-only data))&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;evict 시 그냥 버린다.&lt;/li&gt;
&lt;li&gt;다시 필요하면 원본 ELF 파일에서 재로드한다.&lt;/li&gt;
&lt;li&gt;swap 공간을 전혀 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터 세그먼트(.data, .bss)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데이터 세그먼트는 수정이 가능하지만, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;원본 파일에는 반영하면 안된다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, 아래와 같은 코드가 있다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1778813247366&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a = 10;
a = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 페이지가 file-backed로 연결되어 있다면, a = 5 라는 수정이 원본 ELF 파일에 그대로 반영된다. 즉, 다음에 이 프로그램을 실행하면 a의 초기값이 이미 5가 되어 있다는 뜻이다. 당연히&amp;nbsp;이래선&amp;nbsp;안&amp;nbsp;된다. &lt;br /&gt;&lt;br /&gt;그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;COW(Copy-on-Write)&lt;/b&gt;&lt;/span&gt; 방식으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 처음에는 file-backed 페이지로 매핑.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로세스가 해당 페이지에 쓰기를 시도하는 순간 page fault 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 커널이 &lt;b&gt;해당 페이지만 복사&lt;/b&gt;해서 &lt;b&gt;private ANON 페이지로 전환.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이후 수정은 복사본에만 반영, 원본 파일은 그대로 유지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅&lt;b&gt; Private ANON 페이지&lt;br /&gt;&lt;/b&gt;특정 프로세스만 소유하는 익명 메모리 페이지를 뜻한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Anonymous : 백업 파일이 없다. evict 시 swap 영역으로 저장된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Private : 다른 프로세스와 공유되지 않는다. &lt;b&gt;이 프로세스만의 독립적인 복사본&lt;/b&gt;이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pintos가 ELF 로딩시 ANON 페이지로 할당하는 이유&lt;/b&gt;&lt;/h4&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;COW를 제대로 구현하려면 꽤 복잡하기 때문이다.&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;물리&amp;nbsp;프레임&amp;nbsp;참조&amp;nbsp;카운팅&lt;/b&gt; : fork()&amp;nbsp;시&amp;nbsp;부모와&amp;nbsp;자식이&amp;nbsp;같은&amp;nbsp;물리&amp;nbsp;프레임을&amp;nbsp;공유한다.&amp;nbsp;write&amp;nbsp;fault&amp;nbsp;발생&amp;nbsp;시&amp;nbsp;새&amp;nbsp;프레임에&amp;nbsp;복사해야&amp;nbsp;하는데,&amp;nbsp;만약&amp;nbsp;이미&amp;nbsp;아무도&amp;nbsp;공유하지&amp;nbsp;않는&amp;nbsp;ANON&amp;nbsp;프레임이라면&amp;nbsp;굳이&amp;nbsp;복사할&amp;nbsp;필요&amp;nbsp;없이&amp;nbsp;writable로만&amp;nbsp;바꾸면&amp;nbsp;된다.&amp;nbsp;이&amp;nbsp;판단을&amp;nbsp;위해&amp;nbsp;모든&amp;nbsp;프레임마다&amp;nbsp;몇&amp;nbsp;개의&amp;nbsp;프로세스가&amp;nbsp;공유&amp;nbsp;중인지&amp;nbsp;refcount를&amp;nbsp;관리해야&amp;nbsp;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;COW 플래그 구분&lt;/b&gt; : fork() 시 writable 페이지를 read-only로 표시하는데, &quot;진짜 read-only&quot;인지 &quot;COW라서 임시로 read-only인지&quot;를 구분하는 플래그가 따로 필요하다. 없으면 write fault 시 segfault를 낼지, 복사를 할지 알 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dirty 추적&lt;/b&gt; : file-backed 페이지는 evict 시 수정 여부에 따라 파일에서 재로드할지, swap으로 보낼지 판단해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 전부 구현하는 대신, Pintos는 처음부터 전부 ANON 페이지로 만든다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 세그먼트처럼 절대 수정되지 않는 페이지도 evict되면 swap에 저장&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면, 원본 파일에 이미 내용이 있는데도&lt;b&gt; swap 공간을 낭비&lt;/b&gt;하게 된다.&lt;/p&gt;</description>
      <category>Pintos</category>
      <category>Jungle</category>
      <category>Pintos</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/21</guid>
      <comments>https://dev-ej.tistory.com/21#entry21comment</comments>
      <pubDate>Fri, 15 May 2026 14:53:31 +0900</pubDate>
    </item>
    <item>
      <title>[Week10] WIL - Pintos_Project 2</title>
      <link>https://dev-ej.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 Pintos 과정의 절반이 지나갔다.&lt;br /&gt;정글에 적응하고 사람들과 가까워지다 보니 시간이 더욱 빠르게 흘러가는 것 같다. 하지만 공부해야 할 것은 여전히 많고, 한 주라는 시간은 생각보다 짧다. 그래서 지금 이 시점에는 &amp;lsquo;무엇을 가장 우선해야 하는가&amp;rsquo;를 판단하는 것이 중요하다고 느꼈다.&lt;br /&gt;지난 피드백 이후, 팀원들과 이번 주에 가장 가치 있게 가져가야 할 목표가 무엇인지 진지하게 고민해보았다. 제한된 시간 안에 모든 것을 완벽하게 챙기는 것은 현실적으로 어렵다고 판단했고, 결국 우리는 &amp;lsquo;진행 속도가 느린 팀원을 함께 끌어올리는 것&amp;rsquo;을 가장 중요한 우선순위로 정했다.&lt;br /&gt;AI를&amp;nbsp;활용해&amp;nbsp;충분히&amp;nbsp;이해하지&amp;nbsp;못한&amp;nbsp;상태로&amp;nbsp;테스트&amp;nbsp;케이스만&amp;nbsp;통과시키는&amp;nbsp;것은&amp;nbsp;큰&amp;nbsp;의미가&amp;nbsp;없다고&amp;nbsp;생각한다.&amp;nbsp;이번&amp;nbsp;주는&amp;nbsp;팀원들과&amp;nbsp;최대한&amp;nbsp;많이&amp;nbsp;소통하고&amp;nbsp;협력하면서,&amp;nbsp;단순한&amp;nbsp;결과보다도&amp;nbsp;서로가&amp;nbsp;제대로&amp;nbsp;이해하고&amp;nbsp;성장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;시간을&amp;nbsp;만들어보고자&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 역량 평가&lt;/h4&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 100%; height: 550px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 22px;&quot;&gt;&lt;b&gt;문제해결&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 22px;&quot;&gt;system-call-fork 테스트 전까지 모든 테스트 케이스를 통과한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;read/write를 제외한 테스트까지는 통과했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-5&quot; data-message-id=&quot;8563acef-acf8-4ff0-b609-0cb84816655d&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;코드를 작성하기 전에, 지금 구현하려는 코드가 어떤 기능의 일부이며 무엇을 해결하기 위한 것인지 먼저 명확하게 설계하려고 한다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;메서드 기능을 구현하는 것 자체에만 초점을 두지 않고, 프로젝트 전체 흐름을 이해하면서 코드 구조를 설계했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;Pintos의 기존 코드 흐름을 해치지 않으면서 테스트케이스가 통과되도록 구현한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;기존 코드 구조를 해치지 않고, 알맞게 로직을 수정/추가 하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;통과되는 테스트 케이스라도 정말 문제가 없는지 고려해본다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;bad-ptr같이 검증되지 않은 주소에 접근하는 테스트에 대해, 단순히 통과 로직뿐만 아니라 어떤 상황에서도 사용할 수 있도록 엣지 케이스를 고려하여 안정성을 확보하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;복잡한 로직은 함수로 분리하고, 코드 의도를 짧은 주석으로 남겨 팀원이 이해하기 쉽게 작성한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;load 함수 내에서 파싱, stack_push를 하는 것이 가독성이 떨어지고 역할 분리가 되지 않는다고 판단하여 별도의 함수로 분리하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 22px;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 22px;&quot;&gt;진행 속도가 느린 팀원들을 함께 끌어올려, 학습 과정에서 소외되는 사람이 없도록 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;코어타임을 학습 진행속도가 느린 팀원 기준으로 진행하며, 모두가 만족할 수 있는 동료 학습을 진행하였다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 22px;&quot;&gt;&lt;b&gt;태도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 22px;&quot;&gt;테스트 실패를 단순 오류로 넘기지 않고, Pintos 내부 동작 원인을 끝까지 추적하며 해결하는 집요함을 기른다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;매핑되지 않은 주소에 접근했을 때 생기는 문제를 파악하기 위해 끝까지 추적하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 22px;&quot;&gt;&lt;b&gt;AI 활용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 22px;&quot;&gt;AI를 단순 코드 생성 도구가 아니라, Pintos 개념 이해, 테스트 실패 원인 분석, 디버깅 방향 설정, 코드 리뷰 보조 도구로 활용하여 생산성을 높인다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;코드를 직접 생성하는 일은 없도록 하였고, 개념 이해, 테스트 실패 원인 추적, 디버깅 방법 등에만 활용하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 11.6279%; height: 44px;&quot;&gt;&lt;b&gt;학습 민첩성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 44px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 79.6511%; height: 44px;&quot;&gt;낯선 OS 개념을 빠르게 이해하고 구현에 즉시 적용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.8837%; height: 22px;&quot; colspan=&quot;3&quot;&gt;Pintos의 모든 개념을 이해하려 하지 않고, 핵심 부분만 학습하고 바로 구현에 뛰어들었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;❓ Weekly Problem : 매핑되지 않은 주소 접근과 Page Fault&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;open, create 테스트&lt;/span&gt;에서 &lt;b&gt;파일 주소에 대한 검증&lt;/b&gt;을 안했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open-bad-ptr.c와 create-bad-ptr.c 테스트가 통과하지 못하는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwa5wn/dJMcaaysn4l/GaQtaGwlICMnXMhFCM27mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwa5wn/dJMcaaysn4l/GaQtaGwlICMnXMhFCM27mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwa5wn/dJMcaaysn4l/GaQtaGwlICMnXMhFCM27mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwa5wn%2FdJMcaaysn4l%2FGaQtaGwlICMnXMhFCM27mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;450&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt; 두 테스트가 공통적으로 [&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;주소 0x20101234&lt;/span&gt;&lt;/b&gt;]&lt;span data-token-index=&quot;3&quot;&gt;에 대한 접근을 시도한다는 것을 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 주소에 접근하는 것이 뭐가 문제가 되는 것인지 알기 위해, 잠깐 Pintos 메모리 레이아웃에 대해 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pintos 메모리 레이아웃&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HdyGn/dJMcajow5G9/euKEefeWzq4oHx5G3ES2Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HdyGn/dJMcajow5G9/euKEefeWzq4oHx5G3ES2Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HdyGn/dJMcajow5G9/euKEefeWzq4oHx5G3ES2Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHdyGn%2FdJMcajow5G9%2FeuKEefeWzq4oHx5G3ES2Zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;497&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 커널 공간은 0x8004000000 주소부터 매핑이 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파싱한 문자열을 유저 모드에 넘길 때 사용했던 USER_STACK은 0x47480000 주소부터 매핑이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디스크에서 찾은 ELF 파일의 PT_LOAD 정보는 0x400000 주소부터 매핑이된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;➡️ &lt;span style=&quot;background-color: #f6e199;&quot;&gt;접근하려고 하는 0x20101234는 아직 매핑이 되지 않은 영역이다!&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;매핑하지 않은 주소 접근의 결과&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매핑하지 않은 주소에 접근하는 것이 왜 테스트 실패까지 이어지는지 알아보기 위해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 문제가 생겼던 &lt;b&gt;open 함수의 로직을 트래킹해&lt;/b&gt;보며 실제 원인을 파악해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;4481&quot; data-origin-height=&quot;1576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSg9a/dJMcagSX1tM/qp77NzdyL13jKn7xzmcSVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSg9a/dJMcagSX1tM/qp77NzdyL13jKn7xzmcSVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSg9a/dJMcagSX1tM/qp77NzdyL13jKn7xzmcSVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSg9a%2FdJMcagSX1tM%2Fqp77NzdyL13jKn7xzmcSVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;840&quot; height=&quot;295&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;4481&quot; data-origin-height=&quot;1576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 구현한 open 메서드에서 &lt;b&gt;filesys_open&lt;/b&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;2️⃣ 디렉토리 엔트리 탐색 시도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파일 시스템은 &lt;b&gt;[파일 이름 &amp;harr;️ 디스크 위치] 매핑 정보&lt;/b&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;3️⃣ 디렉토리 엔트리를 순회하며 일치하는 파일 이름이 존재하는지 탐색&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;4️⃣ 매핑되지 않은 주소에 접근하여 바이트를 읽기 위해 역참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문자열 비교를 위해 커널이 0x20101234에 실제로 접근해 첫 번째 바이트를 읽으려 시도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  MMU의 pml4 테이블 탐색&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- CPU는 가상 주소를 물리 주소로 변환하기 위해 4단계 페이지 테이블(PML4 -&amp;gt; PDPE -&amp;gt; PED -&amp;gt; PTE)을 순서대로 탐색&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 이 주소는 한 번도 물리 메모리에 매핑된 적이 없으므로, 이 주소에 해당 페이지가 없다는 것을 확인&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 하드웨어가 탐색을 중단하고 Fault 주소를 CPU 내부 레지스터&lt;b&gt;(CR2)&lt;/b&gt;에 기록&lt;br /&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인터럽트 14 발생!&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsmIn/dJMcafNj4fV/xJE1VYiEtk6R1UbiEJKKt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsmIn/dJMcafNj4fV/xJE1VYiEtk6R1UbiEJKKt1/img.png&quot; data-alt=&quot;exception.c&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsmIn/dJMcafNj4fV/xJE1VYiEtk6R1UbiEJKKt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsmIn%2FdJMcafNj4fV%2FxJE1VYiEtk6R1UbiEJKKt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;888&quot; height=&quot;36&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;36&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;exception.c&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5️⃣&lt;/span&gt; 인터럽트 번호에 대한 핸들러 함수 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- exception.c에 각 인터럽트에 대해서 핸들러 함수를 등록해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 14번 인터럽트가 발생하면, page_fault 메서드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림4.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Zaz0/dJMcagFsqsa/keqma1Gww1FI82EsPC1r3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Zaz0/dJMcagFsqsa/keqma1Gww1FI82EsPC1r3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Zaz0/dJMcagFsqsa/keqma1Gww1FI82EsPC1r3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Zaz0%2FdJMcagFsqsa%2Fkeqma1Gww1FI82EsPC1r3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;304&quot; data-filename=&quot;그림4.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6️⃣ &lt;b&gt;page_fault&lt;/b&gt; 메서드 내에서 &lt;b&gt;kill&lt;/b&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;7️⃣ kill 메서드 내에서 현재 레지스터의 권한 레벨을 확인한다.&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;b&gt;PANIC 메서드를 호출&lt;/b&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방안 - pml4_get_page 메서드 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VOq4Z/dJMcaaytmF5/NKHH1jayJfDUoGxdxP7GGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VOq4Z/dJMcaaytmF5/NKHH1jayJfDUoGxdxP7GGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VOq4Z/dJMcaaytmF5/NKHH1jayJfDUoGxdxP7GGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVOq4Z%2FdJMcaaytmF5%2FNKHH1jayJfDUoGxdxP7GGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;823&quot; height=&quot;135&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pml4_get_page는 유저 가상 주소에 대응하는 커널 가상 주소를 반환하며, 매핑이 존재하지 않으면 NULL을 반환한다.&lt;br /&gt;매핑이&amp;nbsp;없는&amp;nbsp;주소에&amp;nbsp;접근하면&amp;nbsp;페이지&amp;nbsp;폴트가&amp;nbsp;발생하므로,&amp;nbsp;반환값이&amp;nbsp;NULL인&amp;nbsp;경우&amp;nbsp;exit을&amp;nbsp;호출해&amp;nbsp;프로세스를&amp;nbsp;즉시&amp;nbsp;종료하도록&amp;nbsp;처리하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10주차 회고&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;57&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;이번 주차는 단순한 진도보다, 팀원 모두가 내용을 이해하고 함께 넘어가는 과정에 더 큰 의미를 두었다.&lt;/p&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;진행도가 낮은 팀원의 코드를 함께 보며 의견을 나누는 시간을 가졌는데, 단순히 정답 코드를 공유하는 것이 아니라, 각자가 어떤 흐름으로 구현을 시도했는지 이야기하고 서로의 관점을 맞춰가며 이해를 도왔다. 그 결과, 팀원 모두가 Argument Passing의 동작 원리와 구현 흐름을 확실히 이해한 채 다음 단계로 넘어갈 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;509&quot; data-start=&quot;289&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해, 잘하는 사람이 일방적으로 설명해주는 것이 팀 전체의 이해로 곧바로 이어지는 것은 아니라는 점을 확실히 느꼈다. 지난주에는 내 코드를 기준으로 merge를 진행하며 개념과 코드를 설명하는 방식이었다면, 이번에는 각자가 직접 고민하고 구현한 내용을 중심으로 함께 토론하는 방식에 가까웠다. 그리고 실제로 이번 주차가 팀원 전체의 이해도 면에서 더 좋은 결과를 만들었다고 느꼈다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;678&quot; data-start=&quot;511&quot; data-ke-size=&quot;size16&quot;&gt;특히 스스로 구현에 성공해 테스트를 통과한 팀원의 반응을 보며, 단순히 빠르게 결과를 만드는 협업보다 각자의 이해와 성장을 이끌어내는 협업이 훨씬 중요하다는 것을 체감할 수 있었다. 동시에, 한 사람이 끌고 가는 수직적인 협업 방식에는 분명한 한계가 있다는 점도 다시 한번 느낄 수 있었던 주차였다.&lt;/p&gt;</description>
      <category>크래프톤 JUNGLE</category>
      <category>Jungle</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/20</guid>
      <comments>https://dev-ej.tistory.com/20#entry20comment</comments>
      <pubDate>Fri, 8 May 2026 15:29:44 +0900</pubDate>
    </item>
    <item>
      <title>[Week9] WIL - Pintos_Project1</title>
      <link>https://dev-ej.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 기대하던 Pintos 학습 주차에 들어섰다. Jungle에 입소하기 전부터 가장 기대하던 파트였던 만큼 설렘과 동시에 부담도 느껴졌다. OS의 동작 원리를 직접 이해하고, 그 로직을 스스로 구현해볼 수 있는 기회는 흔치 않기 때문에 더욱 의미 있게 다가왔다. 앞으로 4주 동안은 깊이 있는 학습에 집중하겠다는 다짐을 했고, 특히 개념을 제대로 체화하기 위해 AI는 학습 보조 용도로만 제한적으로 활용하기로 했다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;핵심 역량 평가&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 96.8605%; height: 937px;&quot; border=&quot;1&quot; data-ke-style=&quot;style14&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 11.9585%; text-align: center;&quot;&gt;&lt;b&gt;문제해결&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 10.0867%; text-align: center;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 77.8385%;&quot;&gt;alarm clock, priority scheduling, priority donation의 핵심 문제를 정확히 정의하고, 최소한 priority-donate&lt;br /&gt;테스트까지 통과할 수 있도록 구현한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;mlfqs, priority-donate-lower, priority-donate-sema 테스트를 제외한 모든 테스트 케이스를 통과했다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%; text-align: center;&quot;&gt;0%&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;이후 Project 2~4에서도 구조를 크게 바꾸지 않도록, 확장성을 고려하여 설계한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;다음 주차부터 하는 내용을 아직 알지 못해, 이를 고려하며 구현을 하는 것은 무리가 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%; text-align: center;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;Pintos의 기존 코드 흐름을 해치지 않으면서 테스트케이스가 통과되도록 구현한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;기존 코드 구조를 해치지 않고, 알맞게 로직을 수정/추가 하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%; text-align: center;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;특정 로직을 수정할때마다 Project 1 테스트 케이스를 전부 반복 실행하며 실패 원인을 분석하고 수정하여 안정성을 높인다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;꾸준히 테스트를 진행하여, 이전에 통과되다가 갑자기 fail이 뜨는 테스트 케이스를 잡아낼 수 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%; text-align: center;&quot;&gt;70%&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;복잡한 로직은 함수로 분리하고, 코드 의도를 짧은 주석으로 남겨 팀원이 이해하기 쉽게 작성한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;thread_yield 로직을 모듈화 한 것이 아니라 필요한 자리마다 복사 붙혀넣기 하다 보니까 코드가 더러워진 부분이 존재한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 11.9585%;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 10.0867%; text-align: center;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 77.8385%;&quot;&gt;팀원들이 같은 파일을 수정하더라도 충돌을 줄일 수 있도록 구현 범위를 나누고, PR/커밋 단위로 변경 이유와 테스트 결과를 공유한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;과제 특성상 모두의 코드를 merge하는 건 불가능했기 때문에 내 코드로 merge를 진행하였다. 대신 대 코드에 대해 전부 설명하는 시간을 가졌다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 11.9585%;&quot;&gt;&lt;b&gt;태도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 10.0867%; text-align: center;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 77.8385%;&quot;&gt;테스트 실패를 단순 오류로 넘기지 않고, Pintos 내부 동작 원인을 끝까지 추적하며 해결하는 집요함을 기른다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;sema_up 로직으로 인해 발생한 TIMEOUT 문제가 터지는 지점을 찾아내기 위해 gdb를 사용해서 끝까지 추적하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 11.9585%;&quot;&gt;&lt;b&gt;AI 활용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 10.0867%; text-align: center;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 77.8385%;&quot;&gt;AI를 단순 코드 생성 도구가 아니라, Pintos 개념 이해, 테스트 실패 원인 분석, 디버깅 방향 설정, 코드 리뷰 보조 도구로 활용하여 생산성을 높인다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;코드를 직접 생성하는 일은 없도록 하였고, 개념 이해, 테스트 실패 원인 추적, 디버깅 방법 등에만 활용하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;height: 44px; width: 11.9585%;&quot;&gt;&lt;b&gt;학습 민첩성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 10.0867%; text-align: center;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;height: 44px; width: 77.8385%;&quot;&gt;스레드, 인터럽트, 세마포어, 락, condition variable, priority donation 같은 낯선 OS 개념을 빠르게 이해하고 구현에 즉시 적용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 99.8838%;&quot; colspan=&quot;3&quot;&gt;Pintos의 모든 개념을 이해하려 하지 않고, 핵심 부분만 학습하고 바로 구현에 뛰어들었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Weekly Problem : sema_up() 메서드에서의 value++ 호출 순서에 따른 오류 발생&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 발표에서 다루었던 문제이지만, 블로그에 다시 정리해두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 한 줄의 위치때문에 모든 테스트 케이스가 TIMEOUT이 뜨는 것을 보며 설계의 중요성에 대해 다시금 깨닫게 되었다.&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;sema_up()의 기존 코드 로직을 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;3494&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg8Osq/dJMcagyyXx2/C7kDZRD2xi6biWzRJcNUSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg8Osq/dJMcagyyXx2/C7kDZRD2xi6biWzRJcNUSk/img.png&quot; data-alt=&quot;sema-&amp;amp;gt;value++가 yield 뒤&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg8Osq/dJMcagyyXx2/C7kDZRD2xi6biWzRJcNUSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg8Osq%2FdJMcagyyXx2%2FC7kDZRD2xi6biWzRJcNUSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;405&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;3494&quot; data-origin-height=&quot;1656&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sema-&amp;gt;value++가 yield 뒤&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위가 높은 thread를 unblock하고, thread_yield 로직을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 제일 나중에 sema-&amp;gt;value++을 수행한다.&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;이렇게 코드를 짰을 때 갑자기 거의 모든 테스트 케이스에서 TIMEOUT이 뜨는 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 처음에 priority-sema 테스트를 예시로 로직의 문제점을 찾아내려고 했다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림4.png&quot; data-origin-width=&quot;2959&quot; data-origin-height=&quot;1569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0spMd/dJMcacbSMHj/GSg1ZRmA5tKN6TlWwgCc8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0spMd/dJMcacbSMHj/GSg1ZRmA5tKN6TlWwgCc8K/img.png&quot; data-alt=&quot;priority-sema 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0spMd/dJMcacbSMHj/GSg1ZRmA5tKN6TlWwgCc8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0spMd%2FdJMcacbSMHj%2FGSg1ZRmA5tKN6TlWwgCc8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;451&quot; data-filename=&quot;그림4.png&quot; data-origin-width=&quot;2959&quot; data-origin-height=&quot;1569&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;priority-sema 테스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 테스트케이스는 메인 스레드에서 10개의 자식스레드를 만들고 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식스레드는 모두 sema_down으로 잠들게 되고, 마지막에 메인 스레드에서 sema_up을 통해 잠든 스레드를 하나씩 깨운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 value++의 위치에 따라 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림5.png&quot; data-origin-width=&quot;4249&quot; data-origin-height=&quot;1779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIgZfi/dJMcagyyYqM/e3hdvm9KKhpTXLuapFp9p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIgZfi/dJMcagyyYqM/e3hdvm9KKhpTXLuapFp9p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIgZfi/dJMcagyyYqM/e3hdvm9KKhpTXLuapFp9p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIgZfi%2FdJMcagyyYqM%2Fe3hdvm9KKhpTXLuapFp9p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;850&quot; height=&quot;356&quot; data-filename=&quot;그림5.png&quot; data-origin-width=&quot;4249&quot; data-origin-height=&quot;1779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 스레드에서 sema_up을 호출하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 자식스레드 하나를 &lt;b&gt;unblock&lt;/b&gt; 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;thread_yield&lt;/b&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;그러면 자식스레드는 sema_down 메서드 내에서 Blocked되었던 위치에서 깨어나고, 다시 while문의 조건문을 재검사하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 정상 로직에서는 sema_up을 하면 sema-&amp;gt;value를 1만큼 증가시키고 스레드가 깨어나야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그런데 현재 코드에서는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;아직 sema-&amp;gt;value를 증가시키지 않았기 때문에, while문을 빠져나가지 못하고 메인 스레드가 다시 block&lt;/span&gt;되게 된다.&lt;/b&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;메인 스레드가 block됨에 따라서 다시 자식스레드의 sema_up의 thread_yield쪽으로 돌아오게 되고, 그제서야 sema-&amp;gt;value++가 수행된다. 이후의 로직은 정상적으로 작동할 것이다. 왜냐하면 이제는 value가 1이기 때문에 sema_up을 호출했을때 sema_down에서 정상적으로 while문을 벗어날 수 있을 것이기 때문이다.&amp;nbsp;&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;하지만 이미 첫번째 sema_up이 무시됨에 따라 10개의 스레드가 깨어난 것이 아니라 9개의 스레드만 깨어나게 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;➡️ &lt;/b&gt;따라서 &lt;b&gt;마지막 남은 스레드가 영원히 blocked&lt;/b&gt;되어 TIMEOUT이 뜨는 것이다!&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실제 테스트케이스의 결과&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dR7O6e/dJMcagSTHDo/NpMAprujGIrXi0Tm08K5K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dR7O6e/dJMcagSTHDo/NpMAprujGIrXi0Tm08K5K0/img.png&quot; data-alt=&quot;priority-sema.output&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dR7O6e/dJMcagSTHDo/NpMAprujGIrXi0Tm08K5K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdR7O6e%2FdJMcagSTHDo%2FNpMAprujGIrXi0Tm08K5K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;447&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;priority-sema.output&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 result 파일에 적혀있는 TIMEOUT만 보고 넘어가서 정확한 문제상황을 파악하지 못했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 테스트 결과인 output파일을 보니 내가 생각한 것과 다른 출력이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9개의 스레드는 깨어나면서 msg를 출력했을 줄 알았는데, &lt;b&gt;그냥 부팅이 진행됨과 동시에 TIMEOUT이 뜬 것이다.&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;Debugging 결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zWQZJ/dJMcafsTU6z/dZBoSa280YoiU3wNFDiCB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zWQZJ/dJMcafsTU6z/dZBoSa280YoiU3wNFDiCB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zWQZJ/dJMcafsTU6z/dZBoSa280YoiU3wNFDiCB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzWQZJ%2FdJMcafsTU6z%2FdZBoSa280YoiU3wNFDiCB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;850&quot; height=&quot;268&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sema_up 메서드에 대해 브레이크를 설정하고 테스트케이스를 돌려보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 멈춘 구간에서 &lt;b&gt;bt(backtrace) 명령어로 call stack을 출력&lt;/b&gt;해보았는데, OS를 부팅할 때&lt;b&gt; idle 스레드&lt;/b&gt;를 만드는 부분에서 sema_up 함수가 호출되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 후 계속 진행하기 위해 continue를 했는데... 갑자기 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무한 Continuing&lt;/b&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;Ctrl + c로 진행을 중단하고 &lt;b&gt;info threads 명령어&lt;/b&gt;로 현재 스레드 정보를 확인해보았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;메인 스레드가 죽고, idle 스레드만 남아있었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 idle 함수가 호출되는 thread_start() 메서드부터 흐름을 추적해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;thread_start()와 idle 스레드&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림9.png&quot; data-origin-width=&quot;4425&quot; data-origin-height=&quot;1587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQpUPZ/dJMcageiE2f/rgK6RsQF8O3BEnMSB1Xsh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQpUPZ/dJMcageiE2f/rgK6RsQF8O3BEnMSB1Xsh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQpUPZ/dJMcageiE2f/rgK6RsQF8O3BEnMSB1Xsh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQpUPZ%2FdJMcageiE2f%2FrgK6RsQF8O3BEnMSB1Xsh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;306&quot; data-filename=&quot;그림9.png&quot; data-origin-width=&quot;4425&quot; data-origin-height=&quot;1587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;thread_start 메서드 (메인 스레드)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread_start()는 &lt;b&gt;스레드 스케줄러를 시작하는 함수&lt;/b&gt;이다. 크게 3가지 일을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ idle 스레드를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 인터럽트를 활성화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ sema_down으로 blocked되고, idle 스레드의 초기화를 기다린다.&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;idle 메서드 (idle 스레드)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ &lt;/b&gt;전역 변수인 idle_started에 자기 자신을 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;&lt;/b&gt;&lt;b&gt;sema_up으로 메인 스레드를 다시 깨운다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3️⃣&lt;/b&gt; 이 후, blocked 되면서 ready_list가 빌 때마다 스케줄러에 의해 다시 선택되어 깨어난다.&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;여기서 idle 메서드에서 blocked된 메인 스레드를 깨우는 부분에서, 위에서 설명한 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;value를 나중에 증가&lt;/b&gt;시키기 때문에 메인 스레드의 sema_down에서 &lt;b&gt;while 반복문을 탈출하지 못하고&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메인스레드가 영원히 잠들게 된다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방안&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림7.png&quot; data-origin-width=&quot;2791&quot; data-origin-height=&quot;1959&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlthxF/dJMcahxvQ9K/Kp1Myu9bHKqnseTe18rdQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlthxF/dJMcahxvQ9K/Kp1Myu9bHKqnseTe18rdQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlthxF/dJMcahxvQ9K/Kp1Myu9bHKqnseTe18rdQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlthxF%2FdJMcahxvQ9K%2FKp1Myu9bHKqnseTe18rdQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;597&quot; data-filename=&quot;그림7.png&quot; data-origin-width=&quot;2791&quot; data-origin-height=&quot;1959&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;thread_yield가 호출되기 전에 value를 1만큼 증가&lt;/b&gt;시켜주면 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 스레드 unblock 후 yield를 했을 때, value가 1이기 때문에 while 조건문을 무사히 탈출할 수 있다.&amp;nbsp;&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;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Week9 회고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 마무리를 하고, 유윤선 코치님의 말씀을 듣고 프로젝트 진행 방식에 대해 고민해보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 주 동안 merge&lt;/b&gt; 없이, 개인의 레포에서 구현을 진행했던 팀에 관한 피드백이 주를 이루었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pintos 특성상 특정 파일을 계속해서 수정해야되기 때문에 분업이 사실상 불가능하고, 이 때문에 브랜치 관리에 소홀했던 것은 우리팀도 마찬가지였다. 물론 중간에 가장 진행도가 높은 코드를 master branch에 merge 하고, 4명이서 같은 코드에서 다시 시작하는 과정을 겪긴 했다. 하지만 이렇게 되면 다른 사람들의 코드는 보지도 못한채로 사라지게 되고, 학습 수준이 느린 사람들은 이 속도를 따라가기 버거울 수도 있다는 것을 다시금 생각하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음주부터는 비록 구현 속도가 느리더라도, &lt;b&gt;팀원들과 함께 나아가는 방향을 고려&lt;/b&gt;해보려고 한다. 각자의 코드를 띄워두고 치열하게 싸우며 conflict도 내보고, 각 코드의 장점들을 선택하여 merge하며 팀원들과 함께 성장하는 주차가 되었으면 한다.&lt;/p&gt;</description>
      <category>크래프톤 JUNGLE</category>
      <category>Jungle</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/19</guid>
      <comments>https://dev-ej.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 30 Apr 2026 22:02:52 +0900</pubDate>
    </item>
    <item>
      <title>[Week8] WIL - 네트워크에 발 담그기</title>
      <link>https://dev-ej.tistory.com/18</link>
      <description>&lt;p data-end=&quot;126&quot; data-start=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;이번 주는 소켓 프로그래밍을 중심으로, 네트워크가 실제로 어떻게 동작하는지에 대해 공부하고 간단한 실습까지 진행했다.&lt;/p&gt;
&lt;p data-end=&quot;247&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;사실 이쪽은 그동안 &amp;ldquo;대충 이런 느낌이겠지&amp;rdquo; 정도로만 알고 있던 영역이었는데, 이번 기회에 제대로 파보게 됐다. 평소에도 한 번은 깊게 이해해보고 싶었던 주제라 그런지 생각보다 재미있게 몰입해서 공부할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;372&quot; data-start=&quot;249&quot; data-ke-size=&quot;size16&quot;&gt;이번 주는 특히 접근 방식을 조금 바꿔보려고 했다.&lt;br /&gt;기존에는 과제 요구사항이나 책 내용 자체를 이해하는 데 집중했다면, 이번에는 &amp;ldquo;이게 실제로 어디에 쓰이는지&amp;rdquo;, &amp;ldquo;실무에서는 어떻게 쓰는지&amp;rdquo;까지 같이 보려고 했다.&lt;/p&gt;
&lt;p data-end=&quot;434&quot; data-start=&quot;374&quot; data-ke-size=&quot;size16&quot;&gt;지난주 이동석 코치님의 피드백을 듣고 나서, 내가 과제 자체에만 너무 매몰되어 있었다는 걸 느꼈기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;511&quot; data-start=&quot;436&quot; data-ke-size=&quot;size16&quot;&gt;그래서 공부하다가 생긴 궁금증들은 그냥 넘기지 않고 따로 찾아보면서 정리했고, 수요 코딩회에서 겪었던 이슈들과 함께 이번 WIL에 기록해두려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;511&quot; data-start=&quot;436&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;핵심 역량 평가&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 100%; height: 550px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;&lt;b&gt;문제해결&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;0%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;proxy.c의 part2(동시성 문제)까지 구현한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;시간 부족으로 인해 proxy.c 코드는 직접 짜지 못했다..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;요청 파싱, 응답 생성, 서버 연결, 캐시 기능을 분리하여 이후 병렬 처리 및 캐시 확장이 가능하도록 구조를 설계한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;직접 코드를 구현하지는 못했지만, 기존 echo.c/tiny.c의 구조적 문제를 파악하고 실제로는 어떤 방법들을 사용하는지 찾아보았다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;0%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;socket, HTTP 프로토콜을 기반으로 tiny 웹서버와 proxy 서버를 직접 구현하고, 요청/응답 흐름이 실제로 동작하도록 만든다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;직접 코드를 구현하지는 못했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;브라우저, curl 등을 활용해 다양한 요청을 테스트하고, 정상/비정상 요청(404, 잘못된 헤더 등)에 대해 올바르게 동작하는지 검증한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;브라우저/curl 등을 활용해 적절하게 테스트를 진행하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;함수 단위로 역할을 분리하고, 주요 로직에 대해 간단한 주석을 작성하여 코드의 흐름을 쉽게 이해할 수 있도록 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;직접 짠 코드는 아니지만, tiny.c의 함수들에 대해 line by line로 학습하며 주석을 달고 이해하였다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;어려웠던 개념을 혼자 알고 넘어가는 것이 아니라, 팀원들과 공유한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;스스로 추가학습한 부분을 노션에 정리하여 팀원들과 공유하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;&lt;b&gt;태도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;네트워크 및 시스템 레벨의 오류가 발생하더라도 로그와 흐름을 기반으로 끝까지 원인을 추적하며 해결한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;수요코딩회에서 b+ 트리가 오염되는 원인을 찾으려 많은 노력을 했지만 결국 정확한 원인을 파악하는 것은 실패했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;&lt;b&gt;AI 활용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot;&gt;tiny 웹서버 및 HTTP 구조 이해, 디버깅 과정에서 AI를 활용하되, 핵심 로직은 직접 구현하고 검증한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;핵심 로직을 직접 구현하지는 못했지만 소켓 프로그래밍 구조를 확실히 이해하기 위해 AI를 적극적으로 활용하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;&lt;b&gt;학습 민첩성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;color: #353638; height: 44px;&quot;&gt;네트워크 개념을 깊게 파고들기보다, AI를 활용해 핵심 이론을 빠르게 학습한 뒤 소켓 프로그래밍 구현 단계에 즉시 적용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;color: #353638; height: 22px;&quot; colspan=&quot;3&quot;&gt;Deep dive를 하지 않기로 맹세했지만 CSAPP를 읽으며 나도 모르게 네트워크 이론에 파고들었던 날이 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획한 내용대로 흘러가지 않은 주차였던 것 같다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSAPP 내용은 간단히 이해하며 넘어가고, tiny.c의 핵심 로직을 직접 설계해보는 것도 좋았을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Weekly Questions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번주에 네트워크 개념을 학습하며 궁금했던 2개의 질문에 대해 찾아본 내용을 정리해두려고 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서버가 항상 accept()만 waiting 하는 구조가 정상적일까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 소개하는 tiny.c의 main 함수를 보다가 이상한 점을 발견했다.&lt;/p&gt;
&lt;pre id=&quot;code_1776930570530&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  while (1)
  {
    clientlen = sizeof(clientaddr);
    // 요청이 들어올 때까지 blocking!
    connfd = Accept(listenfd, (SA *)&amp;amp;clientaddr,
                    &amp;amp;clientlen); 
    // IP 주소 + 포트 &amp;rarr; (호스트 이름, 서비스/포트 문자열)로 변환
    Getnameinfo((SA *)&amp;amp;clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE,
                0);
    printf(&quot;Accepted connection from (%s, %s)\n&quot;, hostname, port);
    doit(connfd);  
    Close(connfd); 
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. accept()로 연결 하나를 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이 연결을 doit()에서 끝까지 처리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 처리 끝나기 전까지는 다른 연결을 안 봄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;-&amp;gt; 연결 하나를 끝까지 처리한 뒤 다음 연결을 받는다.&lt;/b&gt;&lt;/u&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;blockquote data-ke-style=&quot;style3&quot;&gt;  &lt;span style=&quot;color: #000000;&quot;&gt;문제 상황&lt;/span&gt; &lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트 A &amp;rarr; 처리 5초&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트 B &amp;rarr; 처리 1초&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 A의 요청을 먼저 처리하게 되면, B는 1초면 끝나는 일을 6초나 기다려야 되는 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위한 방안으로 멀티 프로세스/멀티 스레드를 통해 작업 공간 자체를 늘려버리는 방법이 있지만, 이는 근본적인 문제를 해결해주지 못할 것이라고 생각했다.&lt;br /&gt;왜냐하면&amp;nbsp;이&amp;nbsp;방식은&amp;nbsp;결국&amp;nbsp;blocking&amp;nbsp;I/O&amp;nbsp;구조를&amp;nbsp;그대로&amp;nbsp;둔&amp;nbsp;채,&amp;nbsp;처리&amp;nbsp;흐름만&amp;nbsp;병렬로&amp;nbsp;늘리는&amp;nbsp;접근이기&amp;nbsp;때문이다.&amp;nbsp;즉,&amp;nbsp;연결&amp;nbsp;하나를&amp;nbsp;끝까지&amp;nbsp;처리하는&amp;nbsp;방식&amp;nbsp;자체는&amp;nbsp;변하지&amp;nbsp;않는다. &lt;br /&gt;&lt;br /&gt;그래서&amp;nbsp;이번에는&amp;nbsp;접근&amp;nbsp;방식을&amp;nbsp;바꿔,&amp;nbsp;&lt;b&gt;여러&amp;nbsp;연결을&amp;nbsp;동시에&amp;nbsp;감시하고&amp;nbsp;준비된&amp;nbsp;작업만&amp;nbsp;처리&lt;/b&gt;하는&amp;nbsp;&lt;b&gt;이벤트&amp;nbsp;기반&amp;nbsp;모델&lt;/b&gt;에&amp;nbsp;대해&amp;nbsp;살펴보게&amp;nbsp;되었고,&amp;nbsp;그&amp;nbsp;핵심이&amp;nbsp;바로&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;epoll_wait&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;epoll_wait&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll은 여러 파일 디스크립터(fd)를 한 번에 감시하는 메커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 accept() 하나만 바라보고 있었다면, 이제는 listen 소켓과 이미 연결된 모든 소켓을 함께 감시한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776932047427&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (1) {
    int n = epoll_wait(epfd, events, MAX, -1);
    // epfd : 감시 대상 목록을 들고 있는 객체(감시 중인 fd 목록)
    // events : 결과를 담아줄 배열.
    // MAX : 한 번에 받아올 최대 이벤트 수
    // timeout(-1) : 얼마나 기다릴지(-1은 이벤트 생길 때까지 무한 대기)

    for (int i = 0; i &amp;lt; n; i++) {
        int fd = events[i].data.fd;

        if (fd == listenfd) {
            // 새로운 연결
            connfd = accept(listenfd, ...);
            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, ...);
        } else {
            // 기존 연결에서 이벤트 발생
            read(fd, ...);
            write(fd, ...);
        }
    }
}&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이벤트 발생 -&amp;gt; 해당 작업만 처리 -&amp;gt; 다시 대기&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ 어떤 이벤트를 기다리는가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll_wait는 단순히 &quot;연결 요청&quot;만 기다리는 게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 새로운 연결 (listenfd)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 읽기 가능(readable)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쓰기 가능(writeable)&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;b&gt;모든 소켓 상태 변화&lt;/b&gt;를 감지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;❓읽기/쓰기 가능 여부 이벤트는 왜 보낼까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하기 위해서는 tiny.c와 실제 서버의 구조 차이를 먼저 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tiny.c는 연결이 도착하면 해당 fd 하나만 확인하면된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776933273014&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;connfd = accept(...);
doit(connfd);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- connfd 소켓 하나에 대해서만 처리하면 되는 구조&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;connfd A&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;connfd B&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;connfd C&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;...&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 서버는 지금 어떤 소켓에 데이터가 와 있는지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 즉, 지금 read/write를 수행해도 되는 소켓이 무엇인지 모른다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;279&quot; data-start=&quot;165&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 등장하는 것이 &lt;b&gt;epoll_wait&lt;/b&gt;이다.&lt;br /&gt;epoll은 서버가 미리 여러 소켓(fd)을 등록해두면, 그 중에서 &lt;b&gt;상태 변화가 발생한 소켓들만 선별하여 알려주는 메커니즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;427&quot; data-start=&quot;281&quot; data-ke-size=&quot;size16&quot;&gt;서버는 epoll_wait을 호출함으로써, &amp;ldquo;지금 읽을 수 있는 소켓&amp;rdquo;, &amp;ldquo;지금 쓸 수 있는 소켓&amp;rdquo;과 같이 &lt;b&gt;현재 I/O가 가능한 소켓들의 목록을 전달받게 된다.&lt;/b&gt;&lt;br /&gt;그리고 이 목록에 포함된 소켓들에 대해서만 read 또는 write를 수행한다.&lt;/p&gt;
&lt;p data-end=&quot;527&quot; data-start=&quot;429&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 서버는 더 이상 모든 소켓을 직접 확인할 필요 없이, &lt;b&gt;커널이 전달해주는 &amp;lsquo;처리 가능한 소켓 집합&amp;rsquo;을 기반으로 효율적으로 작업을 수행하는 구조&lt;/b&gt;로 동작하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;527&quot; data-start=&quot;429&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;527&quot; data-start=&quot;429&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 현대 웹에서의 동적 컨텐츠 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tiny.c 코드를 공부하면서 이해가 되지 않는 부분이 정적 컨텐츠와 동적 컨텐츠를 구분하여 실행하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 웹에 대해서 공부할 때 백엔드 코드에서 이런식으로 분기하는 것을 본 적이 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 CGI 프로그램을 직접 프로세스를 만들어서 실행하는 로직도 이상하다고 느껴졌다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tiny.c에서 1 + 2를 수행하는 로직은 다음과 같은 순서로 흘러간다.&lt;/p&gt;
&lt;pre id=&quot;code_1776948507888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;요청: /cgi-bin/adder?1&amp;amp;2

웹서버
  &amp;rarr; fork()로 adder 프로세스 새로 생성
  &amp;rarr; adder가 1+2 계산
  &amp;rarr; 결과 출력
  &amp;rarr; 프로세스 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;i&gt;&lt;b&gt;-&amp;gt; 매 요청마다 프로세스를 새로 만들고 죽인다.&lt;/b&gt;&lt;/i&gt;&lt;/u&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;- fork 비용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- execve 비용&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;b&gt;완벽히 격리&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;현대 웹에서의 동적 컨텐츠 처리&lt;/b&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;ldquo;파일을 그대로 보내면 정적, 프로그램을 실행하면 동적&amp;rdquo;이라는 식의 구조적인 차이는 거의 사라졌고,&lt;br /&gt;지금은 대부분의 요청이 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;하나의 동일한 처리 흐름&lt;/b&gt;&lt;/span&gt; 안에서 다뤄진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 요청 수신 &amp;rarr; 라우팅 &amp;rarr; 핸들러 실행 &amp;rarr; 응답 생성 &amp;rarr; 반환 &lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 정적 파일이든, DB 조회 결과든, API 응답이든 항상 &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;예를 들어:&lt;br /&gt;- /index.html -&amp;gt; 파일을 읽는 핸들러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- /api/user -&amp;gt; DB 조회하는 핸들러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- /posts/1 -&amp;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;p data-end=&quot;828&quot; data-start=&quot;665&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 이런 방식이 아니었다. 서버가 동적 컨텐츠를 직접 만들어내는 구조가 보편적이지 않았기 때문에, 동적 요청이 들어오면 별도의 프로그램을 실행해 그 결과를 받아오는 방식이 주로 사용되었다. 이때 사용된 방식이 fork() 후 execve()로 CGI 프로그램을 실행하는 구조였다.&lt;/p&gt;
&lt;p data-end=&quot;1008&quot; data-start=&quot;830&quot; data-ke-size=&quot;size16&quot;&gt;반면 현대 웹에서는 요청마다 새로운 프로그램을 실행하지 않는다. 이미 실행 중인 애플리케이션 서버가 요청을 받은 뒤, 그 안에 등록된 라우터와 핸들러 함수를 호출해 응답을 생성한다. 즉, 예전에는 &lt;b&gt;요청마다 프로그램을 실행&lt;/b&gt;했다면, 지금은 &lt;b&gt;계속 실행 중인 서버 내부에서 필요한 로직만 호출&lt;/b&gt;하는 방식에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;1143&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size16&quot;&gt;이 차이 때문에 오늘날의 동적 컨텐츠는 더 이상 특별한 처리 방식으로 분리되지 않는다. 파일 응답이든, 데이터 조회 결과든, 템플릿 렌더링이든 모두 같은 서버 프로세스 안에서 처리되며, 차이는 단지 어떤 로직이 응답을 만들어내느냐에 있다.&lt;/p&gt;</description>
      <category>크래프톤 JUNGLE</category>
      <category>epoll_wait</category>
      <category>Jungle</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/18</guid>
      <comments>https://dev-ej.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 23 Apr 2026 17:49:27 +0900</pubDate>
    </item>
    <item>
      <title>[Week7] WIL - 무한 코어타임 지옥</title>
      <link>https://dev-ej.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 주차와 비교했을때 가장 학습 내용이 어려웠던 Week7을 무사히 마치게 되었다. 학습 내용이 어렵다보니 자연스레 팀원들과 할 말이 많아졌고 코어타임 시간도 최소 2시간 이상 진행하게 되었다. 특히 첫째날 진행한 3시간 가량의 코어타임은 시간이 지나도 잊을 수 없을 것 같다... ㅋㅋ. 처음으로 동료학습에 중점을 두고 진행한 주차였던거 같은데 혼자 공부하는거에 비해 배울점이 정말 많았다. 과제 특성상 혼자 코드구현하는 시간에 투자하는 것이 도움이 될 것이라고 생각을 했었는데, 막상 다른 팀원들의 코드와 설계를 보니 나와 생각이 다른 부분들이 있어서 오히려 혼자하는 것 보다 도움이 되었던 것 같다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이번주 WIL에서는 내가 Malloc_Lab을 진행하면서 도전해본 부분과, 수요코딩회에서 습득한 지식에 대해서 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;핵심 역량 평가&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 287px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 22px;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 22px;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 17px;&quot;&gt;&lt;b&gt;문제해결 / 설계 / 구현 / 품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 17px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 17px;&quot;&gt;동적 메모리 할당에 대한 개념을 완벽히 이해하고, 성능 95점 이상의 코드를 완성한다. 속도/메모리 사용 두 개념의 트레이드오프를 고려하여 균형이 좋은 코드를 짜는것을 목표로한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 17px;&quot; colspan=&quot;3&quot;&gt;segregated 가용 리스트 + first fit 탐색 방법과 CHUNKSIZE 조절을 통해 90점까지는 받을 수 있었다. 하지만 시간부족으로 인해 realloc 최적화나 명시적 가용 리스트에서의 최적화 방법 등 다양한 방법을 구현해보지는 못했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 17px;&quot;&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;유지보수 &lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 17px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 17px;&quot;&gt;남들이 봐도 쉽게 이해할 수 있을 만큼 가독성 좋은 코드를 짜고, 주석을 달아놓는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 17px;&quot; colspan=&quot;3&quot;&gt;함수형 매크로 이름을 기존 책에서 소개된 이름보다 더 직관적인 이름으로 수정하였다. 또 각 메서드에 대해서 주석을 달아놨다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 17px;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 17px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 17px;&quot;&gt;어려웠던 개념을 혼자 알고 넘어가는 것이 아니라, 팀원들과 공유한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 17px;&quot; colspan=&quot;3&quot;&gt;학습할때마다 노션에 학습한 개념과 이슈들을 정리하고, 이를 코어타임때 팀원들과 같이 보며 공유하였다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 22px;&quot;&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;태도 &lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;75%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 22px;&quot;&gt;절대로 mm.c 파일 내용을 구현할 때 AI의 도움을 받지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 22px;&quot; colspan=&quot;3&quot;&gt;묵시적 가용리스트는 AI 없이 구현하게 가능했지만, 명시적 가용 리스트부터는 시간 부족으로 인해 AI의 도움을 받은 부분이 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 22px;&quot;&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;비즈니스 이해&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;60%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 22px;&quot;&gt;수요코딩회에서 다른 사람이 봐도 이해가 명확히 될만한 구성으로 설계를 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 22px;&quot; colspan=&quot;3&quot;&gt;청중에게 실험 결과를 더 명확히 보여주기 위해서 시각자료가 있었으면 더 좋았을 것 같다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 22px;&quot;&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;AI 활용&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 22px;&quot;&gt;AI를 통해 꼬리 질문을 거듭하며 모호한 개념에 대한 이해와 응용력을 극대화한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 22px;&quot; colspan=&quot;3&quot;&gt;수요코딩회 관련 지식을 확실하기 위해 AI의 도움을 적극적으로 받아 학습을 진행했다. 하지만 특정 개념을 오해하여 적절하지 않은 b+ 트리를 만들게 되었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 25.4069%; height: 10px;&quot;&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;학습 민첩성&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 8.60465%; height: 10px;&quot;&gt;0%&lt;/td&gt;
&lt;td style=&quot;width: 69.4186%; height: 10px;&quot;&gt;이번주는 빠르게 학습하는 것이 중요하지 않다고 생각하여 PASS. line by line으로 9.9장의 내용을 완벽하게 이해한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 103.43%; height: 21px;&quot; colspan=&quot;3&quot;&gt;CSAPP 9.9의 내용을 이틀에 걸쳐서 깊게 공부하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Malloc_Lab : Implicit 가용리스트에서 boundary tag의 단점을 줄이는 최적화 기법&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  &lt;b&gt;핵심 아이디어&lt;/b&gt; : footer는 free block에만 있으면 충분하다!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boundary tag를 통해 이전 블록을 O(1)로 찾을 수 있지만, 모든 블록에 footer를 두면 작은 블록이 많을 때 오버헤드가 커진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 작은 객체를 많이 malloc/free 하는 프로그램에서는 header + footer가 블록 절반을 먹을 수도 있다.&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;footer는 언제 필요할까❓&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 블록을 free하려고 할 때 이전 블록이 free인지 확인하고, free라면 그 이전 블록의 크기를 알아내서 coalesce해야 한다.&amp;nbsp;&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;
&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;이전 블록이 allocated라면&lt;b&gt;❓&lt;/b&gt; &lt;/b&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;- 그 블록 크기를 footer로 읽을 필요도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이전 블록이 free일 때만 footer가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;➡️ &lt;span style=&quot;background-color: #f6e199;&quot;&gt;footer는 free block에만 있으면 충분하다.&lt;/span&gt;&lt;/b&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;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 블록이 free라면 그냥 footer에서 정보를 읽으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 그 전에 이전 블록이 free인지 allocated인지를 알아야하는데 어떻게 확인해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- allocated 블록에서 footer가 빠지는 것을 가정.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록의 마지막 3개 비트는 사용하지 않고, 이 중 가장 마지막 비트는 현재 블록의 할당 여부를 나타내는 데 사용한다고 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 다음 비트를 이전 블록의 정보를 담는데 사용하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1776342921767&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        alloc = 1 (현재 블록 할당됨)
          |
          v
0x....0 0 1
        ^
        |
     prev_alloc = 0 (이전 블록 free)&lt;/code&gt;&lt;/pre&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;prev_alloc bit를 get/set하기 위한 매크로&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776343597086&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* prev_alloc bit get */
#define GET_PREV_ALLOC(p) (GET(p) &amp;amp; 0x2)

/* prev_alloc bit set */
#define SET_PREV_ALLOC(p) (PUT(p, GET(p) | 0x2))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명한 것처럼 0x2 주소(뒤에서 2번째 비트 주소) 값을 or 연산자를 통해 get/set 한다.&lt;b&gt;&lt;/b&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;b&gt;구현할 때 고려한 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 어떤 블록을 malloc 해서 allocated로 만들 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 블록의 다음 블록 header의 prev_alloc을 1로 변경(할당됨 표시)&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;2️⃣ 어떤 블록을 free 할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 블록의 다음 블록 header의 prev_alloc을 0으로 변경(free 표시)&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;3️⃣ coalesce 해서 블록들이 합쳐질 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 합쳐진 큰 free block의 footer를 새로 써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 다음 블록 header의 prev_alloc을 0으로 변경&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 확인을 하기 전에 나는 이번 최적화를 통해 메모리 효율 점수가 더 올라갈 것이라고 예상했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 효율 점수는 [ 현재 할당된 payload / 총 heap 크기 ] 로 계산되는데, 4바이트 절약으로 인해 총 heap크기가 감소할 것이고, 이로인해 메모리 효율 점수가 높아질 것이라고 판단했기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1776401611457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;size = 9
기존:        [header 4B][  9B 페이로드  ][footer 4B]  = 블록 24B 필요
footer 제거: [header 4B][  9B 페이로드  ]             = 블록 16B로 충분&lt;/code&gt;&lt;/pre&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;1. 일반 Implicit &lt;/b&gt;&lt;b&gt;가용 리스트 - first fit 탐색&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bufjGo/dJMcaaLKLVW/LYQ5u85Gym071cGqJVP2X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bufjGo/dJMcaaLKLVW/LYQ5u85Gym071cGqJVP2X0/img.png&quot; data-alt=&quot;메모리 효율 51 + 처리량 13 = 64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bufjGo/dJMcaaLKLVW/LYQ5u85Gym071cGqJVP2X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbufjGo%2FdJMcaaLKLVW%2FLYQ5u85Gym071cGqJVP2X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;353&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메모리 효율 51 + 처리량 13 = 64&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. footer 최적화 Implicit 가용 리스트 - first fit 탐색&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/stVBn/dJMcaf0yOnn/vCZWDz8Nr3YBQYzTpz9gFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/stVBn/dJMcaf0yOnn/vCZWDz8Nr3YBQYzTpz9gFK/img.png&quot; data-alt=&quot;메모리 효율 51 + 처리량 12 = 64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/stVBn/dJMcaf0yOnn/vCZWDz8Nr3YBQYzTpz9gFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FstVBn%2FdJMcaf0yOnn%2FvCZWDz8Nr3YBQYzTpz9gFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;415&quot; height=&quot;361&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메모리 효율 51 + 처리량 12 = 64&lt;/figcaption&gt;
&lt;/figure&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;처음에는 implicit + first-fit 방식의 한계 때문이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇더라도 처리량이 떨어져지는 것 외에 메모리 효율이 그대로인 것이 도저히 이해가 가지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에 대한 정답은 trace(테스트 케이스)에 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Align과 block의 크기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청한 size가 16이라고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1776404166303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;size = 16
기존:        [header 4B][  16 페이로드  ][footer 4B]  = 블록 24B 필요
footer 제거: [header 4B][  16 페이로드  ][padding 4B] = block align으로 인해 블록 24B 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시를 보면 footer가 없지만, 블록 주소를 8의 배수로 고정하기 위해서 padding을 4B만큼 채워 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;즉, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;size가 8의 배수인 경우에는 align 조건으로 인해 기존 방식과 같은 크기의 블록을 할당받는다.&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 과제의 trace의 대부분은 전부 8의 배수 크기를 할당하고 있다는 것이다.&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;nbsp;&lt;/p&gt;</description>
      <category>크래프톤 JUNGLE</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/17</guid>
      <comments>https://dev-ej.tistory.com/17#entry17comment</comments>
      <pubDate>Thu, 16 Apr 2026 17:50:43 +0900</pubDate>
    </item>
    <item>
      <title>[기초] 하네스 엔지니어링이란?</title>
      <link>https://dev-ej.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DrekqeDlO1w&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=DrekqeDlO1w&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=DrekqeDlO1w&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bVmTmT/dJMb83ktTfb/gXWgGhEMvVI3gxX2KZtwE1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/r7Fh2/dJMb85vPXcO/5cDxPZENROrVqF0OVQnp71/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;하네스 공식문서 100번 읽은 것처럼 만들어드림&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/DrekqeDlO1w&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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;span style=&quot;background-color: #f3f4f9; color: #0a0a0a; text-align: center;&quot;&gt;요약해 두려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하네스 엔지니어링이란?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에이전트가 실수를 할 때마다, 그 실수가 다시는 반복되지 않도록 환경 자체를 설계하는 것&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 고삐, 인장 같이 말을 다루는 도구를 뜻하는 Harness에서 유래됐다.&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;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;AI 엔지니어링은 어떻게 진화했나&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스가 왜 주목받게 됐는지 이해하려면 흐름을 먼저 보는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프트 엔지니어링&lt;/b&gt; &amp;mdash; AI한테 말을 잘 거는 기술. &quot;이렇게 설명하면 더 잘 이해하더라&quot;는 수준이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨텍스트 엔지니어링&lt;/b&gt; &amp;mdash; 단순히 질문을 잘 하는 걸 넘어, 프로젝트 상황과 배경을 AI에게 체계적으로 전달하는 방식으로 발전했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MCP + 스킬&lt;/b&gt; &amp;mdash; AI에게 도구를 연결하고, 사용할 수 있는 범위를 정해주기 시작했다. 그런데 스킬이 많아지자 AI가 오히려 헷갈려하는 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하네스 엔지니어링&lt;/b&gt; &amp;mdash; 도구나 지시를 넘어, 에이전트가 일하는 환경 자체를 설계하는 단계다. 현재 가장 앞단의 흐름이다.&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;기존 방식의 두 가지 한계&lt;/h3&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;size18&quot;&gt;&lt;b&gt;1. 컨텍스트 부패&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 길어질수록 AI의 컨텍스트 창이 꽉 차면서 앞에서 했던 지시를 잊어버린다. 아무리 처음에 잘 설명해줘도, 대화가 길어지면 리셋된다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;해결책 &amp;mdash; CLAUDE.md (Agents.md)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 세션이 시작될 때마다 에이전트가 가장 먼저 읽는 파일이다. 1000줄짜리 설명서가 아니라, 60줄 이하의 핵심 지도 같은 것이다. 처음부터 완벽할 필요 없고, 실패할 때마다 한 줄씩 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 구조적 제약의 부재&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이건 절대 하면 안 돼&quot;라는 규칙을 말로만 전달하면 언제든 어겨질 수 있다. 앞서 DB 테이블 삭제 사례처럼. 정보의 문제가 아니라 구조의 문제다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;해결책 &amp;mdash; 훅(Hooks)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 저장하기 직전에 자동으로 검사를 실행한다. 문제가 있으면 저장 자체가 안 되고, 에이전트가 스스로 수정하게 된다. 사람이 개입할 필요가 없다. &quot;잘 짜줘&quot;라고 부탁하는 게 아니라, 못 짜면 저장이 안 되는 구조를 만드는 것이다.&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;하네스의 3가지 핵심 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 컨텍스트 파일 (&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;CLAUDE.md / Agents.md&lt;/span&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 작업 시작 전 가장 먼저 읽는 파일이다. 규칙, 금지 사항, 프로젝트 맥락을 담는다. 핵심은 &lt;b&gt;간결함&lt;/b&gt;이다. 항상 적용되는 내용만 넣고, 세부 사항은 별도 파일로 분리한다. 실패할 때마다 한 줄씩 추가하면서 점진적으로 발전시키는 방식이 효과적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 자동 강제 시스템&lt;/b&gt;&lt;/p&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;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;린터&lt;/b&gt;&lt;/span&gt; &amp;mdash; 코드 맞춤법 검사기처럼, 규칙을 어기면 자동으로 에러를 낸다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;프리커밋 훅&lt;/b&gt;&lt;/span&gt; &amp;mdash; 저장 전 자동 검사. 에러가 있으면 에이전트가 스스로 수정하고 다시 시도한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;u&gt;&lt;b&gt;자동 교정 루프&lt;/b&gt; &lt;/u&gt;덕분에 사람이 일일이 검토하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 가비지 컬렉션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 기존 코드를 보고 패턴을 따라간다. 나쁜 코드가 쌓이면 나쁜 패턴이 계속 복제된다. 그래서 주기적으로 청소하는 에이전트가 필요하다.&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;규칙을 위반한 코드가 새로 생겼는지&lt;/li&gt;
&lt;li&gt;사용하지 않는 코드가 쌓였는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 스크립트나 파일이라기 보다는 &quot;주기적으로 AI 한테 청소 작업을 시키는 루틴&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;&lt;b&gt;문서 동기화&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;/docs 폴더의 API 문서랑 실제 코드를 비교해서 달라진 부분을 찾아줘. 문서가 오래된 거면 코드 기준으로 업데이트해줘.&lt;/blockquote&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;우리 코드베이스에서 어디서도 import되거나 호출되지 않는 함수, 클래스, 변수를 찾아서 목록으로 보여줘. 삭제해도 안전한 것들은 바로 지워줘.&lt;/blockquote&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;하네스 핵심 철학&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에이전트가 규칙을 어겼을 때 프롬프트를 고치는 게 아니라, 그 실패가 구조적으로 반복 불가능하도록 환경을 수정한다.&lt;/span&gt;&lt;/blockquote&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;a href=&quot;https://openai.com/ko-KR/index/harness-engineering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://openai.com/ko-KR/index/harness-engineering/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775723501645&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;하네스 엔지니어링: 에이전트 우선 세계에서 Codex 활용하기&quot; data-og-description=&quot;작성자: Ryan Lopopolo, 기술 스태프 멤버&quot; data-og-host=&quot;openai.com&quot; data-og-source-url=&quot;https://openai.com/ko-KR/index/harness-engineering/&quot; data-og-url=&quot;https://openai.com/ko-KR/index/harness-engineering/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bEEx8W/dJMb9lk76E6/BymhIhV3aX2okCoHIU86n1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/ea5ZAK/dJMb9b3S1pa/EkQ0OMAPZYg3GHwINOjjSk/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://openai.com/ko-KR/index/harness-engineering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://openai.com/ko-KR/index/harness-engineering/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bEEx8W/dJMb9lk76E6/BymhIhV3aX2okCoHIU86n1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/ea5ZAK/dJMb9b3S1pa/EkQ0OMAPZYg3GHwINOjjSk/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;하네스 엔지니어링: 에이전트 우선 세계에서 Codex 활용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;작성자: Ryan Lopopolo, 기술 스태프 멤버&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;OpenAI가 공개한 하네스 블로그에서도 동일한 철학을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그들은 5개월 동안 크게 네 가지 규칙을 정해두고 바이브 코딩을 하는 실험을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&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;Agents.md&lt;/b&gt; &amp;mdash; AI 업무 지침서. 해도 되는 것과 절대 안 되는 것을 명시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CI 게이트&lt;/b&gt; &amp;mdash; 저장할 때마다 자동 테스트 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도구 경계&lt;/b&gt; &amp;mdash; AI가 접근할 수 있는 범위와 권한을 사전에 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;피드백 루프&lt;/b&gt; &amp;mdash; AI가 코딩 &amp;rarr; 리뷰 &amp;rarr; 규칙 보강 &amp;rarr; 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>하네스 엔지니어링</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/16</guid>
      <comments>https://dev-ej.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 9 Apr 2026 17:33:18 +0900</pubDate>
    </item>
    <item>
      <title>[Week6] WIL - Hello C World!</title>
      <link>https://dev-ej.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;약 4주간의 알고리즘 주차를 마치고, C언어를 학습하고 활용하는 주차에 들어서게 되었다. 이번 6주차에는 C언어 기초문법을 간단히 학습하고, 이를 활용하여 기본 자료구조를 구현하는 문제들을 푸는것이 주요 과제였다. 또한 C언어의 핵심 개념인 포인터와 동적 메모리 할당 메서드 'Malloc'을 공부해보면서 C언어에 익숙해지는 과정을 거쳤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 4개월 동안 알고리즘 문제를 풀 일이 없을텐데, 이러면 수료후에 다시 알고리즘 문제를 못 풀게 되는것이 아닌지 걱정이 된다. 비록 남은 교육과정이 쉽지만은 않겠지만, 일주일에 2~3문제는 백준/LeetCode 문제를 푸는 습관을 들여보도록 하자... ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 역량 평가&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 358px;&quot; border=&quot;1&quot; data-ke-style=&quot;style14&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 22px;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 22px;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 17px;&quot;&gt;&lt;b&gt;문제해결&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 17px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;LL, Stack and Queue, BT, BST 각 개념당 최소 4문제 이상 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;모든 문제를 해결하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 17px;&quot;&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 17px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;수요코딩회에서 하드코딩의 빈도를 줄이고, 확장성을 최대한 고려한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;시간 부족으로 인해 코드의 몇몇 부분에서 하드코딩을 한 부분들이 존재했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 17px;&quot;&gt;&lt;b&gt;구현 / 품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 17px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;과제물에 대해 의도한 로직대로 수행되는 코드를 짜고, 다양한 테스트케이스 입력을 해보며 결과물을 검증한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;모든 문제에 대해 예외상황까지 철저히 고려한 테스트케이스를 통해 정답을 검증하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 22px;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;가독성 좋은 코드를 작성한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;변수명이나, 코드의 흐름이 직관적으로 보이게끔 코드를 짜려고 노력했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 22px;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 22px;&quot;&gt;70%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;모르는 문제에 대해 해결하면, 혼자 넘어가지 않고 팀원들과 해당 지식을 공유한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;포인터와 Malloc 회의, 수요코딩회에서는 팀원들과 학습한 내용을 최대한 공유하였지만, 과제에 대해서는 그렇게 하지 못했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 22px;&quot;&gt;&lt;b&gt;태도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;이해가 모호한 개념에 대해 절대로 그냥 넘어가지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;포인터와 Malloc 개념, 그리고 수요코딩회에서 산출된 코드들을 대충 알고 넘어가는 것이 아닌, 깊은 이해를 하려고 노력했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 10px;&quot;&gt;&lt;b&gt; 비즈니스 이해 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 10px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 10px;&quot;&gt;수요코딩회에서 다른 사람이 봐도 이해가 명확히 될만한 구성으로 설계를 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 21px;&quot; colspan=&quot;3&quot;&gt;가독성을 살리기 위해 pretty table로 터미널 출력 내용을 보기 좋게 하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 22px;&quot;&gt;&lt;b&gt; AI 활용 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 22px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;AI를 통해 꼬리 질문을 거듭하며 모호한 개념에 대한 이해와 응용력을 극대화한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 99.9999%;&quot; colspan=&quot;3&quot;&gt;AI를 활용한 학습을 할 때는, 깊은 학습을 위한 꼬리 질문을 반복하며 문제 및 개념에 대해 확실히 이해하려고 노력하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 12.6162%; height: 16px;&quot;&gt;&lt;b&gt;학습 민첩성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10.5232%; height: 16px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 16px;&quot;&gt;C언어 문법과 포인터/Malloc 개념을 빠르게 습득하여 문제에 적용해본다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 16px;&quot; colspan=&quot;3&quot;&gt;이중 포인터 내용을 깊게 학습하고, 링크드 리스트 과제에서 적절히 활용하였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이번 주 핵심 개념 - 포인터와 Malloc&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이번 주차의 핵심 주제는 C언어를 활용한 자료구조 구현이라고 보는 것이 더 적절하다. 그러나 대부분의 문제는 포인터와 동적 할당에 대한 이해 없이는 해결하기 어려웠다. 이에 따라, 이번 주차에 해당 개념들을 확실히 정리해두는 것이 이후 C언어 학습과 Pintos 프로젝트 진행에 큰 도움이 될 것이라 판단하였다. 따라서 기존에 개념적으로만 알고 있던 내용들을 보다 깊이 있게 추가 학습하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이중 포인터 활용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;값 복사(pass by value)&lt;/p&gt;
&lt;pre id=&quot;code_1775717877039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function(x);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어에서는 함수를 호출할 때 인자 자체가 전달되는게 아니라, 인자의 값이 복사돼서 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775717922512&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void f(int *t) {
    t = NULL;
}

int main() {
    int x = 10;
    int *p = &amp;amp;x;

    f(p);
    // %p는 반드시 void* 타입을 요구
    printf(&quot;%p\n&quot;, (void*)p);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 f(p)를 호출할 때,&lt;/p&gt;
&lt;pre id=&quot;code_1775717940053&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int *p = &amp;amp;x; // 주소값 복사&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 p 포인터 변수에 x의 주소값을 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 주소값이 복사됐을뿐 실제 p와는 다른 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 똑같이 &amp;amp;x를 가리킬 뿐, 함수 호출할때 인자로 넘긴 p와 함수 내에서 사용하는 t는 전혀 다른 객체라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DHHSE/dJMcaibRIO8/v1qHIGfRlFsWyaQsEWzy2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DHHSE/dJMcaibRIO8/v1qHIGfRlFsWyaQsEWzy2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DHHSE/dJMcaibRIO8/v1qHIGfRlFsWyaQsEWzy2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDHHSE%2FdJMcaibRIO8%2Fv1qHIGfRlFsWyaQsEWzy2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;890&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 포인터 변수 t가 NULL을 가리키게 해도, 기존 포인터 변수 p는 그대로 x의 주소를 가리키고 있기 때문에, printf시 NULL이 아닌 &amp;amp;x 값이 그대로 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775718699794&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void f(int *t) {
    *t = 20;   // 가리키는 대상 변경
}

int main() {
    int x = 10;
    int *p = &amp;amp;x;

    f(p);

    printf(&quot;%d\n&quot;, x); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 포인터 변수 t 자체가 아니라, *t로 역참조하여 값을 바꾸면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t가 가리키는 주소, 즉 x가 저장된 메모리를 직접 수정하기 때문에, printf 시 10이 아닌 변경된 값 20이 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;포인터 자체를 바꾸는 법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 함수의 인자로 넘기는 *p가 수정되어 NULL을 가리키게 하려면 어떻게 하면 될까?&lt;/p&gt;
&lt;pre id=&quot;code_1775718860191&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void f(int **t) {
    *t = NULL;
}

int main() {
    int x = 10;
    int *p = &amp;amp;x;

    f(&amp;amp;p);
    // %p는 반드시 void* 타입을 요구
    printf(&quot;%p\n&quot;, (void*)p);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드와 달리 f 함수는 인자를 이중 포인터(int **)로 받고 있다. 호출 시 &amp;amp;p를 전달하므로, t는 p 자체의 주소를 가리키게 된다. 따라서 *t로 역참조하면 p가 저장된 메모리에 직접 접근할 수 있고, *t = NULL은 p의 값을 NULL로 덮어쓴다. 결과적으로 printf에서는 x의 주소가 아닌 NULL이 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blF2pG/dJMcacbCVJY/0uhI1xMNg5wxPD5RHQjp5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blF2pG/dJMcacbCVJY/0uhI1xMNg5wxPD5RHQjp5k/img.png&quot; data-alt=&quot;이중 포인터를 인자로 받아, 포인터 변수(p)가 저장한 주소값을 직접 수정 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blF2pG/dJMcacbCVJY/0uhI1xMNg5wxPD5RHQjp5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblF2pG%2FdJMcacbCVJY%2F0uhI1xMNg5wxPD5RHQjp5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;932&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이중 포인터를 인자로 받아, 포인터 변수(p)가 저장한 주소값을 직접 수정 가능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Malloc&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중(runtime)에 필요한 크기만큼 메모리를 힙(heap) 영역에서 할당받는 함수이다.&lt;/p&gt;
&lt;pre id=&quot;code_1775719297648&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void *malloc(size_t size);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- size 바이트만큼 메모리를 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 성공하면 그 메모리의 시작 주소를 반환하고, 실패하면 NULL 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 할당한 공간에 어떤 자료형이 들어오는지 모르기 때문에, 범용 포인터 타입인 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;void*&lt;/b&gt;&lt;/span&gt; 를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; &lt;i&gt;void* 포인터&lt;/i&gt;&lt;br /&gt;&lt;/b&gt;아직 타입이 정해지지 않은 주소이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; 즉 주소는 들고 있지만, 그 주소를 어떤 타입으로 읽어야 할지는 아직 모르는 상태이다.&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;컴파일러는 void* 타입의 변수를 보고 그 주소의 메모리를 어떤 자료형으로 해석해야 할지 알 수 없기 때문에 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;u&gt;역참조가 &lt;b&gt;불가능&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;하다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;void *vp = malloc(sizeof(int));&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;*vp = 10; &amp;lt;-- 불가능&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메모리 누수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775719639223&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void f() {
    int *p = malloc(sizeof(int));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 끝나면 p라는 포인터 변수는 사리지지만, 힙에 만든 공간은 그대로 남아 있을 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 p라는 변수 자체는 스택에 저장되고, p 안의 값은 힙 메모리에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775719729650&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;free(p)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 p라는 변수 자체를 지우는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p가 가리키고 있는 힙 메모리를 운영체제/메모리 할당기에게 반납하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;➡️&lt;u&gt;&lt;i&gt;&lt;b&gt;하지만 포인터 변수 p와, 그 안에 있던 힙 메모리 주소값은 남아있을 수 있다.&lt;/b&gt;&lt;/i&gt;&lt;/u&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;b&gt;free 전&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- p : &quot;0x500&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 0x500 : 사용자가 쓰는 유효한 힙 메모리&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;free 후&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- p : 여전히 &quot;px500&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하지만 0x500은 이제 메모리 할당기에게 반납한 유효하지 않은 공간&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;이와 같은 상황에서 p 같은 포인터를 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;댕글링 포인터(dangling pointer)&lt;/b&gt;&lt;/span&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지 하기 위해서는...&lt;/p&gt;
&lt;pre id=&quot;code_1775719867353&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;free(p);
p = NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 p에 저장된 옛 주소를 초기화해주는 습관을 들이는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;❓ free 후 값을 읽으면 어떻게 되는가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775719903593&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int *p = malloc(sizeof(int));
*p = 10;
free(p);

printf(&quot;%d\n&quot;, *p);   // 위험&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;정의되지 않은 동작(undefined behavior)&lt;/b&gt; 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 우연히 10이 출력될 수도 있음&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;보통 malloc/free는 직접 운영체제랑 매번 1:1로 일하는 게 아니라, 중간의 &lt;b&gt;메모리 할당기(allocator)&lt;/b&gt;가 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로그램이 malloc 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 할당기가 큰 힙 영역 안에서 적당한 블록을 하나 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- free 하면 그 블록을 &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;여기서 블록을 재사용 가능으로 전환할때 초기화를 보장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 malloc 직후에 값을 바로 읽으면 이전에 저장되었던 쓰레기값을 읽어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 malloc 직후에 값을 바로 읽지 말고 &lt;b&gt;바로 초기화&lt;/b&gt;를 하거나 &lt;b&gt;calloc&lt;/b&gt;을 쓰는것이 안전하다.&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;&lt;b&gt;수요 코딩회&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Developer-EJ/SQL-Parser&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Developer-EJ/SQL-Parser&lt;/a&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;b&gt;INSERT&lt;/b&gt;와 &lt;b&gt;SELECT&lt;/b&gt; 기능을 제공하는 간단한 &lt;b&gt;SQL Parser&lt;/b&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매주 수요코딩회의 진행 양상이 점점 비슷해지고 있는 것 같아서 고민을 해봤다. 매주 팀프로젝트를 하는 것은 흔하지 않은 기회이기 때문에 새로운 것을 도전해보는 것도 좋을 것이라고 생각했다. 그래서 Agents.md 파일에 모든 협업 규칙을 넣어두는 기존 방식과는 다른 새로운 방법을 적용시켜보기로 했다. 특히, 최근 자주 언급되는 &amp;nbsp;'&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;하네스 엔지니어링&lt;/b&gt;&lt;/span&gt;'에 대해 공부해보았고 프로젝트에 적용시켜보았다.&amp;nbsp;&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;a href=&quot;https://dev-ej.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.04.09 - [AI] - [기초] 하네스 엔지니어링이란?&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775723640195&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[기초] 하네스 엔지니어링이란?&quot; data-og-description=&quot;https://www.youtube.com/watch?v=DrekqeDlO1w 해당 영상을 보고 공부한 내용을 간략하게 요약해 두려고 한다. 하네스 엔지니어링이란?에이전트가 실수를 할 때마다, 그 실수가 다시는 반복되지 않도록 환경 &quot; data-og-host=&quot;dev-ej.tistory.com&quot; data-og-source-url=&quot;https://dev-ej.tistory.com/16&quot; data-og-url=&quot;https://dev-ej.tistory.com/16&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzrQf4/dJMb8SXyJyw/FeWkmE1wgJk7XUxntQsGK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cIRpCp/dJMb8954z9B/7LuZKRwX4unKuwq5reYazk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://dev-ej.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-ej.tistory.com/16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzrQf4/dJMb8SXyJyw/FeWkmE1wgJk7XUxntQsGK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cIRpCp/dJMb8954z9B/7LuZKRwX4unKuwq5reYazk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[기초] 하네스 엔지니어링이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.youtube.com/watch?v=DrekqeDlO1w 해당 영상을 보고 공부한 내용을 간략하게 요약해 두려고 한다. 하네스 엔지니어링이란?에이전트가 실수를 할 때마다, 그 실수가 다시는 반복되지 않도록 환경&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-ej.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;size18&quot;&gt;&lt;b&gt;AI 활용 전략&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;계층적 컨텍스트 분리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;src/*/CLUADE.md&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;AI 역할 격리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;에이전트 경계 강제&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;CLAUDE.md 규칙&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;모듈 충돌 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;인터페이스 계약&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;include/interface.h&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;공유 상태 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;하네스(Harness)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;.githooks/pre-commit&amp;nbsp;(커밋&amp;nbsp;시점)&amp;nbsp;+&amp;nbsp;&lt;br /&gt;PostToolUse (수정 즉시)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;AI 출력 자동 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;피드백 루프 기억&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;REVIEW_RULES.md&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;실수 패턴 누적 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;스펙 주도 생성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;tests/TEST_SPEC.md&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;AI 생성 방향 명세&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;프로젝트는&amp;nbsp;AI가&amp;nbsp;협업할&amp;nbsp;때&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;문제를&amp;nbsp;구조적으로&amp;nbsp;막는&amp;nbsp;여섯&amp;nbsp;가지&amp;nbsp;기법을&amp;nbsp;사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저&amp;nbsp;&lt;b&gt;src/*/CLAUDE.md&lt;/b&gt;로 컨텍스트를 계층 분리해 각 AI가 자기 역할에만 집중하게 했고, CLAUDE.md 규칙으로 모듈 간 코드 충돌을 원천 차단했다. 여기서 루트에 있는 CLAUDE.md 파일은 프로젝트의 대략적인 흐름만 기재해두었고, 실제 기능폴더마다 들어있는 CLAUDE.md 파일에 각 기능에 대한 요구사항을 정의해두었다.&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;include/interface.h&lt;/b&gt;를&amp;nbsp;유일한&amp;nbsp;통신&amp;nbsp;채널로&amp;nbsp;고정해&amp;nbsp;공유&amp;nbsp;상태를&amp;nbsp;최소화했으며,&amp;nbsp;&lt;b&gt;pre-commit&amp;nbsp;hook&lt;/b&gt;과&amp;nbsp;&lt;b&gt;lint.sh&lt;/b&gt;로 구성된 하네스가 AI가 생성한 코드를 &lt;b&gt;코드 수정&lt;/b&gt; 및 &lt;b&gt;커밋&amp;nbsp;시점에&amp;nbsp;자동&amp;nbsp;검증&lt;/b&gt;해&amp;nbsp;잘못된&amp;nbsp;코드가&amp;nbsp;저장소에&amp;nbsp;들어오지&amp;nbsp;못하게&amp;nbsp;막았다.&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;REVIEW_RULES.md&lt;/b&gt;에 AI가 저지른 실수 패턴을 누적 기록해 같은 문제가 반복되지 않도록 했고, &lt;b&gt;TEST_SPEC.md&lt;/b&gt;로 AI가 만들어야 할 결과물을 체크리스트 형태로 미리 명세해 생성 방향을 통제했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;느낀점&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVzqV4/dJMcahqtX0G/bKzgg5lq5xWASfKr7mLID1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVzqV4/dJMcahqtX0G/bKzgg5lq5xWASfKr7mLID1/img.png&quot; data-alt=&quot;테이블에 INSERT, SELECT 명령어로 접근가능한 간단한 SQL 처리기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVzqV4/dJMcahqtX0G/bKzgg5lq5xWASfKr7mLID1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVzqV4%2FdJMcahqtX0G%2FbKzgg5lq5xWASfKr7mLID1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1716&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1716&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테이블에 INSERT, SELECT 명령어로 접근가능한 간단한 SQL 처리기&lt;/figcaption&gt;
&lt;/figure&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;오전 내내 개인 공부와 설계를 중점적으로 다루었고, 실제 구현은 2시간도 채 걸리지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 맡은 부분의 코드를 공부하여 서로 발표하는 시간을 가졌고, AI 사용 방식에 대해서도 다양한 대화를 나누었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, Agent의 차이로 인해 협업 과정에서 문제가 될 수도 있다는 점이 놀라웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드에서 지정한 규칙을 Codex쪽에서 마음대로 우회하여 진행시키는 것을 보고 설계 단계에서 조금 더 시간을 들이는 것도 좋았을 것이라는 생각이 든다.. ㅋㅋ&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;비록 결과물이 화려하거나 차별점이 있지는 않았지만 새로운 AI 활용 방식을 적용시켜봤고, 팀원들과도 효과적으로 소통하고 협업했던 것 같아서 의미있던 시간이었던 것 같다.&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>
      <category>크래프톤 JUNGLE</category>
      <category>Jungle</category>
      <category>malloc</category>
      <category>포인터</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/15</guid>
      <comments>https://dev-ej.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 9 Apr 2026 17:12:12 +0900</pubDate>
    </item>
    <item>
      <title>백준 9251- LCS 풀이</title>
      <link>https://dev-ej.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/9251&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/9251&lt;/a&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;dp 알고리즘의 대표격인 문제라 정리해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LCS(Longest Common Subsequence, 최장 공통 부분 수열) 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;부분 수열이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원래 수열에서 몇 개를 골라 순서는 유지한 채 만든 수열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 순서는 유지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 중간 원소는 건너뛰어도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어 원래 수열이 [2, 3, 5, 6, 9] 라면, 부분 수열은&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- [2, 3, 5, 6]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- [2, 5, 6]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- [3, 9]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;... 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런식으로 원래 순서만 안 바꾸면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 DP 알고리즘 대표 문제일까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP의 핵심 사고 방식은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 큰 문제를 작은 문제로 나누고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 그 작은 문제의 답을 재사용해서 큰 문제를 푼다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCS 문제는 다음과 같은 특성을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 완전탐색으로 풀면 경우의 수가 너무 많다.&lt;/b&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;2. 같은 부분 문제가 계속 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;문자열 A의 앞 i글자&lt;/b&gt;와 &lt;b&gt;B의 앞 j글자&lt;/b&gt;의 &lt;b&gt;LCS&lt;/b&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;3. 큰 문제의 답이 작은 문제의 답으로 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 따라서 DP 알고리즘을 통해 최적의 해답을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. dp[i] 정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 전체를 한 번에 보지 말고, 앞에서부터 조금씩 잘라서 생각해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1775131969703&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i][j] = A의 앞 i글자와 B의 앞 j글자의 LCS 길이&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 점화식 세우기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCS의 점화식은 2개의 경우의 수를 고려해봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ 마지막 문자가 같은 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 A와 B의 마지막 문자가 같다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;A[i - 1] == B[j - 1]&lt;/b&gt; 인 상황&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문자가 같다면 그 문자는 공통 부분 수열에 포함시킬 수 있다. 즉...&lt;/p&gt;
&lt;pre id=&quot;code_1775132104318&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i][j] = dp[i-1][j-1] + 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 &lt;b&gt;A 앞 i - 1 글자&lt;/b&gt;와 &lt;b&gt;B 앞 j - 1 글자&lt;/b&gt;의 LCS 뒤에 마지막 같은 문자 하나만 추가하면 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ 마지막 문자가 다른 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 A와 B가 다르다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;A[i - 1] != B[j - 1]&lt;/b&gt; 인 상황&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 dp[i][j] 를 채우려면 더 작은 부분 문제의 결과를 이용해서 채워야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 통해 이해를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- A = &quot;ABC&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- B = &quot;AEB&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;인 상황에서 &lt;b&gt;dp[3][3]&lt;/b&gt; 을 생각해보자. (A의 3번째 문자와 B의 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;A[2]는 'C', B[2]는 'B'이므로 LCS에 추가할 수 없다. LCS는 두 문자열에 공통으로 존재하는 같은 문자들만 순서를 지켜서 뽑아야 하므로, 'C'와 'B'를 한 번에 채택하는 것은 불가능하다.&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;1. A의 마지막 문자 'C'를 사용하지 않고, A의 범위를 하나 줄인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. B의 마지막 문자 'B'를 사용하지 않고, B의 범위를 하나 줄인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, dp[i-1][j] 와 dp[i][j-1]를 비교해서, 그중 더 긴 LCS의 길이를 dp[i][j]로 선택하면 된다.&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;pre id=&quot;code_1775132468739&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 두 문자가 같을때
if A[i - 1] == B[j - 1]:
    dp[i][j] = dp[i - 1][j - 1] + 1
# 두 문자가 다를때
else:
    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. base case&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 둘 중 하나라도 길이가 0이면 LCS의 길이는 당연히 0이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dp[0][j] = 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dp[i][0] = 0&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;/p&gt;
&lt;pre id=&quot;code_1775132526368&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A = input()
B = input()

len_A = len(A)
len_B = len(B)

dp = [[0] * (len_B + 1) for _ in range(len_A + 1)]

for a in range(1, len_A + 1):
    for b in range(1, len_B + 1):
        if A[a - 1] == B[b - 1]:
            dp[a][b] = dp[a - 1][b - 1] + 1
        else:
            dp[a][b] = max(dp[a - 1][b], dp[a][b - 1])

print(dp[len_A][len_B])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i][j] 를 A의 앞 i 글자, B의 앞 j 글자로 정의했기 때문에, dp의 인덱스를 1부터 시작하게끔 구현하였다. 그래서 실제 문자열에 접근할때는 -1을 해주는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 모든 인덱스를 0부터 시작하게 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇게 되면 이전 경우의 dp 인덱스가 접근할 때, 즉&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[a-1][b-1]과 같이 이전 인덱스에 접근할때 음수 인덱스에 접근하게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 불필요한 예외처리 코드가 추가되어야 한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>DP</category>
      <category>LCS</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/14</guid>
      <comments>https://dev-ej.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 2 Apr 2026 21:24:37 +0900</pubDate>
    </item>
    <item>
      <title>백준 12865 - 평범한 배낭 풀이</title>
      <link>https://dev-ej.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/12865&quot;&gt;https://www.acmicpc.net/problem/12865&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP 문제를 풀면서 기억해두면 좋을 케이스라 기록해두기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제 정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 구하는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전탐색을 하기에는 경우의 수가 너무 많고, 중복되는 계산이 반복되기 때문에 dp로 풀어야겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 상태 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775129457548&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i] = 무게가 i일 때 가치합의 최대값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 점화식 세우기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775129498114&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# item은 [weight, value]로 구성
dp[i] = max(dp[i], dp[i - item[0]] + item[1])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 배낭 용량이 id일 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이 물건을 안 넣었을 때 기존 i무게 에서의 최대 가치&lt;/span&gt;와 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이 물건을 넣었을 때의 최대 가치&lt;/span&gt;를 &lt;/b&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;size18&quot;&gt;&lt;b&gt;3. 초기값 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 존재하지 않는 경우의 무게는 고려할 필요가 없기 때문에 초기값을 굳이 세워둘 필요는 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1775129661774&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 전부 0으로 초기화하면서 생성
dp = [0] * (weight + 1)&lt;/code&gt;&lt;/pre&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;같은 물건을 2번 이상 담는 경우의 수가 존재&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&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;u&gt;&lt;b&gt;배낭 문제는 한 번 선택한 물품은 다시 선택할 수 없다.&lt;/b&gt;&lt;/u&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;pre id=&quot;code_1775129993074&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 오답 코드
# 첫번째 아이템의 weight 부터 순차적으로 dp를 채워간다.
N, max_weight = map(int, input().split())
items = [list(map(int, input().split())) for _ in range(N)]

dp = [0] * (max_weight + 1)

for weight, value in items:
    for i in range(weight, max_weight + 1):
        dp[i] = max(dp[i], dp[i - weight] + value)

print(dp[max_weight])&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1775130024739&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for i in range(weight, max_weight + 1):&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 무게부터 큰 무게로 가면서 dp를 갱신하고 있다.&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;b&gt;무게 = 3, 가치 = 4인 물건&lt;/b&gt;이 딱 하나있고, &lt;b&gt;배낭의 최대 무게가 6&lt;/b&gt;이라고 가정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp의 초기값은 모두 0으로 채워져있다. 이제 정방향으로 반복문을 돌아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;i = 3&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775130124079&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[3] = max(dp[3], dp[0] + 4) = 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i를 1씩 증가시키면서 dp[i] 값을 채워나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 i가 6일때 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;i = 6&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775130178331&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[6] = max(dp[6], dp[3] + 4) = 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[3]은 방금 같은 물건으로 만든 값 4가 들어있기 때문에, dp[6]은 무게가 3인 물건을 두 번 넣은 것처럼 계산되어 8이 된다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 역방향으로 탐색하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775130278543&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[6] = max(dp[6], dp[3] + 4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 쪽부터 역순으로 내려오면, &lt;u&gt;&lt;b&gt;dp[i - weight]는 아직 이번 물건으로 갱신되기 전의 값&lt;/b&gt;&lt;/u&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;size18&quot;&gt;&lt;b&gt;i = 6&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775130406724&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[6] = max(dp[6], dp[3] + 4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 dp[3]은 아직 0이다. i = 3을 아직 처리하지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 dp[6] = 4가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 i = 3까지 dp[i]는 4로 초기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 2차원 배열 풀이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금처럼 상태가 2개 필요한 문제에서는 2차원 배열 풀이를 고려해볼 수 있다.&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;를 상태로 둘 수 있다.&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;size18&quot;&gt;&lt;b&gt;1. 상태 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775130556936&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i][w] = 앞에서부터 i개 물건만 고려했을 때, 배낭 허용 무게가 w일 때 얻을 수 있는 최대 가치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 점화식 세우기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775130582955&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight] + value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 항상 i - 1 만 참고하기 때문에, 같은 물건을 두 번 쓸 일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 현재 물건이 너무 무거워서 못 넣는다면, 현재 물건은 없는 셈 치고 이전 결과를 그대로 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1775130616855&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if w &amp;lt; weight:
  dp[i][w] = dp[i - 1][w]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 초기값 설정&lt;/b&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;pre id=&quot;code_1775130673421&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;N, K = map(int, input().split())
items = [tuple(map(int, input().split())) for _ in range(N)]

dp = [[0] * (K + 1) for _ in range(N + 1)]

for i in range(1, N + 1):
    weight, value = items[i - 1]

    for w in range(K + 1):
        if w &amp;lt; weight:
            dp[i][w] = dp[i - 1][w]
        else:
            dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight] + value)

print(dp[N][K])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 각 물건들을 추가할때마다 i를 증가시키고, 항상 i - 1번째 값들을 참고하기 때문에, 같은 물건을 두 번 담을 수가 없다.&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>dp 알고리즘</category>
      <category>백준 12865</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/13</guid>
      <comments>https://dev-ej.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 2 Apr 2026 20:52:13 +0900</pubDate>
    </item>
    <item>
      <title>[Week5] WIL - DP 알고리즘 부수기</title>
      <link>https://dev-ej.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;약 4주간 진행한 알고리즘 주차가 종료되었다. 이제 백준 기준으로 실버까지의 문제는 어느정도 풀어낼 수 있게 되었다. 하지만 이대로 C언어 주차로 넘어가게 되면 알고리즘 해결 감각이 다시 초기화될 것이 뻔히 보인다. 비록 알고리즘 주차는 끝났지만, 매일 혹은 이틀에 한번 정도는 알고리즘 문제를 꾸준히 푸는 습관을 들이도록 하겠다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 역량 평가&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 360px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;역량&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;달성도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;&lt;b&gt;문제해결&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;백준 골드5 수준의 문제를 AI, 구글링 도움 없이 풀 수 있음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;LCS 문제를 제외하고는 스스로 해결할 수 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;&lt;b&gt;설계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;최대한 효율적인 코드(시간 복잡도/공간 복잡도 고려)를 짜는 것을 목표로 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;최대한 시간 복잡도를 낮추는 방식으로 코드를 짜려고 노력했다. 또한 풀이법이 두가지가 있다면(1차원, 2차원 배열) 두 방법 모두 사용하여 문제를 풀어보았다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;75%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;문제당 실패 횟수를 3회 이하로 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;공간복잡도 및 시간복잡도를 고려하지 못한 경우를 제외하고 문제를 잘못 읽어서 틀린 경우는 거의 없었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;&lt;b&gt;품질&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 17px;&quot;&gt;100%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 17px;&quot;&gt;수요코딩회에서 버그없이 돌아가는 결과물을 만든다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;의도한 동작들이 모두 버그없이 돌아가는 미니 리액트 프로젝트를 완성했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;주석을 통해 명확한 풀이를 기록한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;과정이 복잡한 내용은 주석을 통해 재학습이 편하도록 했다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;코어타임 뿐만 아니라 팀원들과 적극적인 교류를 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;코어타임이 끝나고도, 문제를 팀원 모두가 이해할 수 있도록 시간을 투자하여 동료학습을 꾸준하게 진행했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;태도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;모르겠는 문제에 대해 절대 AI를 사용한 코드 생성을 하지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 22px;&quot; colspan=&quot;3&quot;&gt;최대한 개념 학습을 할 때만 도움을 받았고, 코드 생성은 최대한 하지 않으려고 노력했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 10px;&quot;&gt;&lt;b&gt;AI 활용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 10px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 10px;&quot;&gt;맞힌 문제라도 AI에 코드 리뷰를 맡겨 더 최적화된 방법이 있는지를 모색한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 21px;&quot; colspan=&quot;3&quot;&gt;문제를 해결했어도, 더 좋은 방법이 있는지 AI에게 코드리뷰를 맡겨보았다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;&lt;b&gt;학습 민첩성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.5697%; height: 22px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 76.8605%; height: 22px;&quot;&gt;수요코딩회에서 리액트의 Component, State, Hooks에 대한 개념을 탑다운 방식으로 빠르게 학습한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 99.9999%; height: 17px;&quot; colspan=&quot;3&quot;&gt;빠르게 기존 결과물을 리팩토링하고, 각자 맡은 부분을 공부하여 코드를 설명해주는 시간을 통해 빠르게 학습할 수 있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이번 주 핵심 개념 - DP(Dynamic Programming)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 개념을 학습했던 이전 주차들과는 달리 이번주는 핵심 개념이 DP, 그리고 이로부터 파생된 그리디 알고리즘 두개 뿐이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP 알고리즘을 응용할 수 있는 문제들은 모두 점화식이 다르고, 풀이법이 고정된 느낌이 아니라 적응하는 과정이 매우 험난했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 DP 알고리즘의 간단한 정의와 종류. 그리고 DP 문제 중 가장 어렵게 느껴졌던 LCS와 평범한 배낭 문제 풀이과정까지 정리해두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DP 정의&lt;/h3&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;pre id=&quot;code_1775117531202&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;f(5) = f(4) + f(3)
     = (f(3)+f(2)) + (f(2)+f(1))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; f(3), f(2)가 계속 중복 계산 된다.&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;DP 문제는 크게 두 가지 템플릿을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 점화식형&lt;/h4&gt;
&lt;pre id=&quot;code_1775117624208&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i] = dp[i-1] + dp[i-2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 현재 값을 이전 값들로 정의하는 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제들은 보통 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;&lt;span&gt;1️⃣&lt;/span&gt; &lt;span data-token-index=&quot;1&quot;&gt;상태 정의 (dp 배열 의미 정하기)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775117688220&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i] = i번째까지의 최적값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;1&quot;&gt;개인적으로 생각하는 가장 중요한 단계이다. dp[i]가 정확히 어떤것을 정의하는지를 인지하고 설계를 해야 문제를 풀 수 있다.&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;span data-token-index=&quot;0&quot;&gt;&lt;span&gt;2️⃣&lt;/span&gt; 점화식 세우기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775117732478&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i] = 이전 값들로부터 계산&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 수식과 같이 dp[i]를 이전 값들로부터 계산할 수 있는 점화식을 세우는 단계이다. 문제마다 점화식이 다르기 때문에 다양한 문제를 풀어보면서 규칙을 찾는 연습을 하는 것이 중요하다.&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;span data-token-index=&quot;0&quot;&gt;&lt;span&gt;3️⃣&lt;/span&gt; 초기값 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775117791563&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[0], dp[1] 같은 값들 정의&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base case를 정의하는 단계이다. 이 부분이 예외상황을 제대로 처리하지 못하면 점화식을 통해 정답을 도출할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 값 갱신형&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i]가 특정 규칙을 항상 만족하는 것이 아니라 점화식으로 정의하기 힘든 문제들을 풀이할때 사용하는 방법이다. 처음 접했을때 굉장히 당황스러운 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 갱신형 문제는 &lt;b&gt;현재 상태&lt;/b&gt;를 하나 잡고 &lt;b&gt;거기서 갈 수 있는 다음 상태&lt;/b&gt;들을 보면서 &lt;b&gt;다음 상태 값을 업데이트&lt;/b&gt; 하는 방식이다. 이 때문에 BFS와 비슷하게 보이기도 한다.&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;pre id=&quot;code_1775117985004&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 최소값 갱신
dp[nxt] = min(dp[nxt], dp[cur] + cost)

# 최대값 갱신
dp[nxt] = max(dp[nxt], dp[cur] + value)

# 가능 여부 체크
if dp[cur]:
    dp[nxt] = True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 백준 2253 '점프' 문제를 풀 때 값 갱신 형태로 dp[i]를 정의하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2253&quot;&gt;https://www.acmicpc.net/problem/2253&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775127340612&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# i + k 칸까지 k만큼 건너뛰어서 가는 경우
dp[i + k][k] = min(dp[i + k][k], dp[i][k] + 1)&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본질적으로는 둘 다 DP이다. 차이는 보는 방식 / 구현하는 방식의 차이에 가깝다. 작은 상태들의 답은 이용해서 큰 상태의 답을 구한다는 진리는 변하지 않는다.&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;Leetcode - Word Break&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/word-break/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/word-break/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775118682804&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Word Break - LeetCode&quot; data-og-description=&quot;Can you solve this real interview question? Word Break - Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words. Note that the same word in the dictionary may&quot; data-og-host=&quot;leetcode.com&quot; data-og-source-url=&quot;https://leetcode.com/problems/word-break/&quot; data-og-url=&quot;https://leetcode.com/problems/word-break/description&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LujWw/dJMb86nXUDM/YD2wVKe7uHHzMdJbKb5OAK/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/word-break/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leetcode.com/problems/word-break/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LujWw/dJMb86nXUDM/YD2wVKe7uHHzMdJbKb5OAK/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Word Break - LeetCode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Can you solve this real interview question? Word Break - Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words. Note that the same word in the dictionary may&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leetcode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;h3 data-ke-size=&quot;size23&quot;&gt;DP 유형 - Top-down과 Bottom-Up&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예제인 피보나치 문제를 통해 Top-down과 Bottom-Up 방식에 대해 이해할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;1. DP(Top-down, Memoization)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775118928900&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp = {}

def fib(n):
    if n in dp:
        return dp[n]
    
    if n &amp;lt;= 1:
        return n
    
    dp[n] = fib(n-1) + fib(n-2)
    return dp[n]&lt;/code&gt;&lt;/pre&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Top-down 장단점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&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;- fib(100)을 구할 때 fib(50)이 필요 없으면 아예 계산하지 않는다.&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;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&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;- n이 매우 크면 스택 오버플로가 날 수 있고, 함수 호출마다 스택 프레임을 쌓기 때문에 bottom-up 보다 느린 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;2. DP(Bottom-Up)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775119123435&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def fib(n):
    dp = [0] * (n+1)
    dp[1] = 1

    for i in range(2, n+1):
        dp[i] = dp[i-1] + dp[i-2]

    return dp[n]&lt;/code&gt;&lt;/pre&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;- fib(0) = 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fib(1) = 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fib(2) = fib(1) + fib(0)&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;size18&quot;&gt;&lt;b&gt;핵심 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 없이 반복문만 쓰기 때문에 stack overflow 위험이 없고, 함수 호출 오버헤드도 없어서 실제 실행 속도가 더 빠른 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Bottom-Up 장단점&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;속도와 안정성이 좋다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 반복문이라 최적화도 쉽다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;풀 필요 없는 부분까지 다 채운다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 부분 문제를 채우는 순서를 직접 파악해야하기 때문에, 점화식이 복잡하면 구현이 까다로울 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❓&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;왜 Top-down 방식만 Memoization이라는 용어를 사용할까?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모이제이션은 &quot;필요할 때 저장&quot;하는 전략으로, 2가지 핵심을 가진다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 1. 요청이 왔을 때 계산&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 2. 결과를 저장해서 중복 계산 방지&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bottom-up도 결과를 저장해서 중복 계산을 방지하는 방식이지만, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;요청과 무관하게 미리 전부 채워두는 방식&lt;/b&gt;&lt;/span&gt;이라 첫 번째 조건을 만족하지 못한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 주의 문제 - LCS, 평범한배낭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP알고리즘 위주의 문제를 풀다보니 설계 단계에서 많은 생각을 요구하는 문제가 많았다. 그 중 특히 재미있게 풀었던 두개의 문제에 대해 따로 정리를 해두었다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;a style=&quot;color: #ef5369; text-align: start;&quot; href=&quot;https://dev-ej.tistory.com/14&quot;&gt;2026.04.02 - [알고리즘] - 백준 9251- LCS&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775133162477&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;백준 9251- LCS&quot; data-og-description=&quot;https://www.acmicpc.net/problem/9251처음 문제를 봤을때, 접근법이 선뜻 떠오르지 않았던 문제이다.dp 알고리즘의 대표격인 문제라 정리해둔다. LCS(Longest Common Subsequence, 최장 공통 부분 수열) 문제부분 수&quot; data-og-host=&quot;dev-ej.tistory.com&quot; data-og-source-url=&quot;https://dev-ej.tistory.com/14&quot; data-og-url=&quot;https://dev-ej.tistory.com/14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eBQZV/dJMb81fSYXn/OjrlkeriEHKMDcrm48sLB1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/0ttNP/dJMb84XY6DE/kDZSBcIMtIY14ZJIHkTZE1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://dev-ej.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-ej.tistory.com/14&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eBQZV/dJMb81fSYXn/OjrlkeriEHKMDcrm48sLB1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/0ttNP/dJMb84XY6DE/kDZSBcIMtIY14ZJIHkTZE1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;백준 9251- LCS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.acmicpc.net/problem/9251처음 문제를 봤을때, 접근법이 선뜻 떠오르지 않았던 문제이다.dp 알고리즘의 대표격인 문제라 정리해둔다. LCS(Longest Common Subsequence, 최장 공통 부분 수열) 문제부분 수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-ej.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;a style=&quot;color: #ef5369;&quot; href=&quot;https://dev-ej.tistory.com/13&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.04.02 - [알고리즘] - 백준 12865 - 평범한 배낭 풀이&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775133178047&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;백준 12865 - 평범한 배낭 풀이&quot; data-og-description=&quot;https://www.acmicpc.net/problem/12865DP 문제를 풀면서 기억해두면 좋을 케이스라 기록해두기로 했다. 문제 정의배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 구하는 문제이다.완전탐색을 하기에는 &quot; data-og-host=&quot;dev-ej.tistory.com&quot; data-og-source-url=&quot;https://dev-ej.tistory.com/13&quot; data-og-url=&quot;https://dev-ej.tistory.com/13&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bc5faO/dJMb9kT3lyw/EsCylWyYR2HbA4WuXAyHuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ju3Bn/dJMb9jgxB5D/KUpX5ayLj9UPn6f2ihRngK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://dev-ej.tistory.com/13&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-ej.tistory.com/13&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bc5faO/dJMb9kT3lyw/EsCylWyYR2HbA4WuXAyHuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ju3Bn/dJMb9jgxB5D/KUpX5ayLj9UPn6f2ihRngK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;백준 12865 - 평범한 배낭 풀이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.acmicpc.net/problem/12865DP 문제를 풀면서 기억해두면 좋을 케이스라 기록해두기로 했다. 문제 정의배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 구하는 문제이다.완전탐색을 하기에는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-ej.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>크래프톤 JUNGLE</category>
      <category>DP</category>
      <author>Development &amp;amp; Study</author>
      <guid isPermaLink="true">https://dev-ej.tistory.com/12</guid>
      <comments>https://dev-ej.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 2 Apr 2026 17:49:13 +0900</pubDate>
    </item>
  </channel>
</rss>