<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>turing_machine.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 07 Oct 2026 06:52:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>turing_machine.log</title>
            <url>https://velog.velcdn.com/images/turing_machine/profile/597851d3-1181-46a6-a988-ddcf3471578c/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. turing_machine.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/turing_machine" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CS:APP 9.9절 Dynamic Memory Allocator 시뮬레이터 (short1-bal.rep)
]]></title>
            <link>https://velog.io/@turing_machine/CSAPP-9.9%EC%A0%88-Dynamic-Memory-Allocator-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@turing_machine/CSAPP-9.9%EC%A0%88-Dynamic-Memory-Allocator-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Wed, 07 Oct 2026 06:52:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/turing_machine/post/d071e7b6-5599-40c7-ac5b-c7652d886cfa/image.png" alt=""></p>
<p>(short1-bal.rep)</p>
<hr>
<p>[오후 3:24:40] 힙 초기화 완료: 프롤로그(16B), 대형 가용 블록(20,000B), 에필로그(0B)
<img src="https://velog.velcdn.com/images/turing_machine/post/3255adce-fe6b-4e16-a19d-37859e733c14/image.png" alt=""></p>
<p>Q. 현재 상태에서 프롤로그와 에필로그는 왜 내부 단편화가 없을까?
A. 내부 단편화(Internal Fragmentation)의 정의는 &quot;유저가 요청한 페이로드(Payload)보다 할당된 블록 크기가 더 커서 남는 버려진 공간.</p>
<p>버그나 누락이 아니라, 페이로드를 전혀 담지 않는 경계 메타데이터 블록이므로, 내부 단편화(패딩) 계산 대상에서 원천 배제되었기 때문.</p>
<ul>
<li><p>프롤로그 블록 (PRO, 16 Bytes)</p>
<ul>
<li>내부 구성<ul>
<li>8바이트 헤더 + 8바이트 푸터 =  16바이트.   </li>
</ul>
</li>
<li>페이로드(사용자 데이터 영역)<ul>
<li>아예 없음. 요청 크기 대비 남는 공간(패딩) 이라는 개념 자체가 성립하지 않음</li>
</ul>
</li>
</ul>
</li>
<li><p>에필로그 블록 (EPI, 0 Bytes)</p>
<ul>
<li>내부 구성<ul>
<li>힙의 끝을 표시하는 크기 0짜리 헤더 1개. </li>
<li>전체 크기 자체가 0바이트: 블록 자체의 크기가 0바이트이므로 남는 공간(패딩)이 들어갈 자리도 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<p>a 0 2040</p>
<p>[오후 3:50:20] 블록 분할 발생: [20000B] -&gt; [할당 2064B] + [가용 자투리 17936B]</p>
<p>[오후 3:50:20] malloc(2040) 호출 -&gt; 정렬/헤더 포함 요구 크기: 2064B</p>
<ul>
<li>힙에 ptr[0] 과 8바이트 패딩이 안착된 상태.</li>
<li>헤더(8) + 페이로드(2040) + 푸터(8) = 2056바이트이지만, 16바이트 정렬 배수를 맞추기 위해 2064바이트로 올림 됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/dd333685-7f31-4428-b174-87430dd9e3be/image.png" alt=""></p>
<hr>
<p>a 1 2040</p>
<p>[오후 4:07:24] 블록 분할 발생: [17936B] -&gt; [할당 2064B] + [가용 자투리 15872B]</p>
<p>[오후 4:07:24] malloc(2040) 호출 -&gt; 정렬/헤더 포함 요구 크기: 2064B</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/37653050-4497-4318-9d9a-24b0b7e3fb59/image.png" alt=""></p>
<hr>
<p>f 1</p>
<ul>
<li>방금 해제된 ptr[1] 자리가 뒤쪽 가용 공간과 즉시 합쳐져 거대한 녹색 가용 블록인 FB (17936B | a=0)으로 확장</li>
</ul>
<p>[오후 4:15:36] Case 2: 다음 가용 블록과 병합 -&gt; 새 크기: 17936B</p>
<p>[오후 4:15:36] free(ptr[1]) 호출: 크기 2064B 반납</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/4dbc97c0-b6d1-4af3-8ccb-aa1d49b53952/image.png" alt=""></p>
<hr>
<p>a 2 48</p>
<p>[오후 4:22:48] 블록 분할 발생: [17936B] -&gt; [할당 64B] + [가용 자투리 17872B]</p>
<p>[오후 4:22:48] malloc(48) 호출 -&gt; 정렬/헤더 포함 요구 크기: 64B</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/77d5d33c-0010-4959-80bf-c6720d547bbd/image.png" alt=""></p>
<hr>
<p>a 3 4072</p>
<p>[오후 7:07:46] 블록 분할 발생: [17872B] -&gt; [할당 4096B] + [가용 자투리 13776B]</p>
<p>[오후 7:07:46] malloc(4072) 호출 -&gt; 정렬/헤더 포함 요구 크기: 4096B</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/df88219f-5c08-4015-9484-8cb93f6e7a27/image.png" alt=""></p>
<hr>
<p>f 3</p>
<p>[오후 7:20:36] Case 2: 다음 가용 블록과 병합 -&gt; 새 크기: 17872B</p>
<p>[오후 7:20:36] free(ptr[3]) 호출: 크기 4096B 반납
<img src="https://velog.velcdn.com/images/turing_machine/post/27f0b150-f79d-464c-8cf7-feeb13d30c86/image.png" alt=""></p>
<hr>
<p>a 4 4072</p>
<p>[오후 7:23:30] 블록 분할 발생: [17872B] -&gt; [할당 4096B] + [가용 자투리 13776B]</p>
<p>[오후 7:23:30] malloc(4072) 호출 -&gt; 정렬/헤더 포함 요구 크기: 4096B</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/a3109640-4af7-42e8-a0c2-8c4a63a2ae03/image.png" alt=""></p>
<hr>
<p>f 0</p>
<p>[오후 7:30:29] Case 1: 인접 블록 모두 할당 상태 -&gt; 병합 없음</p>
<p>[오후 7:30:29] free(ptr[0]) 호출: 크기 2064B 반납</p>
<hr>
<p>f 2</p>
<p>[오후 7:30:57] Case 3: 이전 가용 블록과 병합 -&gt; 새 크기: 2128B</p>
<p>[오후 7:30:57] free(ptr[2]) 호출: 크기 64B 반납</p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/fcd61bb1-8e82-4d0f-9643-b342b6c47598/image.png" alt=""></p>
<hr>
<p>a 5 4072</p>
<p>[오후 7:37:18] 블록 분할 발생: [13776B] -&gt; [할당 4096B] + [가용 자투리 9680B]</p>
<p>[오후 7:37:18] malloc(4072) 호출 -&gt; 정렬/헤더 포함 요구 크기: 4096B</p>
<hr>
<p>f 4</p>
<p>[오후 7:39:33] Case 3: 이전 가용 블록과 병합 -&gt; 새 크기: 6224B</p>
<p>[오후 7:39:33] free(ptr[4]) 호출: 크기 4096B 반납</p>
<hr>
<p>f 5</p>
<p>[오후 7:39:59] Case 4: 이전 및 다음 블록 동시 병합 -&gt; 새 크기: 20000B</p>
<p>[오후 7:39:59] free(ptr[5]) 호출: 크기 4096B 반납</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1) 주소 버스와 메모리의 관계, 2) 스택/힙 메모리 할당의 장단점, 3) 가상 메모리에서 스택을 그렇게 설계한 이유, 4) First/Next/Best-fit 요청, 5) 경계 태그의 장점]]></title>
            <link>https://velog.io/@turing_machine/1-%EC%A3%BC%EC%86%8C-%EB%B2%84%EC%8A%A4%EC%99%80-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%9D%98-%EA%B4%80%EA%B3%84-2-%EC%8A%A4%ED%83%9D%ED%9E%99-%EB%A9%94%EB%AA%A8%EB%A6%AC-%ED%95%A0%EB%8B%B9%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90-3-%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90%EC%84%9C-%EC%8A%A4%ED%83%9D%EC%9D%84-%EA%B7%B8%EB%A0%87%EA%B2%8C-%EC%84%A4%EA%B3%84%ED%95%9C-%EC%9D%B4%EC%9C%A0-4-FirstNextBest-fit-%EC%9A%94%EC%B2%AD-5-%EA%B2%BD%EA%B3%84-%ED%83%9C%EA%B7%B8%EC%9D%98-%EC%9E%A5%EC%A0%90</link>
            <guid>https://velog.io/@turing_machine/1-%EC%A3%BC%EC%86%8C-%EB%B2%84%EC%8A%A4%EC%99%80-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%9D%98-%EA%B4%80%EA%B3%84-2-%EC%8A%A4%ED%83%9D%ED%9E%99-%EB%A9%94%EB%AA%A8%EB%A6%AC-%ED%95%A0%EB%8B%B9%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90-3-%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90%EC%84%9C-%EC%8A%A4%ED%83%9D%EC%9D%84-%EA%B7%B8%EB%A0%87%EA%B2%8C-%EC%84%A4%EA%B3%84%ED%95%9C-%EC%9D%B4%EC%9C%A0-4-FirstNextBest-fit-%EC%9A%94%EC%B2%AD-5-%EA%B2%BD%EA%B3%84-%ED%83%9C%EA%B7%B8%EC%9D%98-%EC%9E%A5%EC%A0%90</guid>
            <pubDate>Tue, 06 Oct 2026 04:45:43 GMT</pubDate>
            <description><![CDATA[<ol>
<li>컴퓨터시스템에서의 버스(Bus)의 개념을 기술하고, 주소 버스가 병렬로 처리할 수 있는 최대 비트 수와 메모리 사이즈 간의 상관 관계를 설명하시오.</li>
</ol>
<p>컴퓨터 시스템에서 버스는,
CPU, 메모리, I/O 장치 간에 신호(데이터, 주소, 제어 신호)를 주고받는 물리적 통신 선로다.
바이트 단위 주소 지정 시스템을 기준으로,
주소 버스가 N 비트일 때, 접근 가능한 최대 메모리 용량은 2^N 바이트다.</p>
<p>예를 들어 32비트 버스는 최대 2^32바이트(4GB), 64비트 버스는 최대 2^64바이트(16EB)까지 직접 주소를 지정할 수 있다.</p>
<hr>
<ol start="2">
<li>스택(Stack)을 이용한 메모리 할당과 malloc을 이용한 동적 메모리 할당의 차이점을 설명하고, 각각의 장단점을 비교하여 서술하시오.</li>
</ol>
<p>스택 메모리: 함수 호출 시 생성되는 지역 변수와 매개변수가 할당되는 영역이다.
-장점: 스택 포인터의 단순 이동만으로 할당/해제가 이루어지기에 빠르며, 함수 종료 시 자동으로 정리되어 메모리 누수 위험이 없다. 
-단점: 컴파일 시점에 크기가 제한적이어서 (스택 오버플로우 위험) 대용량 데이터를 저장하기 어렵고, 함수의 생명주기를 벗어나 유지될 수 없다.</p>
<p>malloc(힙) 동적 메모리: 런타임에 프로그래머의 요청에 따라 동적으로 할당되는 영역이다.
-장점: 실행 중에 필요한 만큼 유연하게 메모리를 할당할 수 있으며, 전역적으로 스코프에 구애받지 않고 생명주기를 수동 제어할 수 있다.
-단점: 오버헤드가 크다. 가용 블록 탐색, 메타데이터 관리, 시스템 콜 비용으로 스택보다 속도가 느리다. 내부/외부 단편화가 발생할 수 있고, 메모리 누수나 댕글링 포인터 등의 관리 부담이 생긴다.</p>
<hr>
<ol start="3">
<li>스택이 고주소에서 저주소 방향으로 성장하도록 설계된 이유는 무엇인가? 프로세스의 전체 메모리 구조 관점에서 그 이유를 자신의 의견을 중심으로 서술하시오.</li>
</ol>
<p>프로세스의 데이터 크기가 가변적인 두 영역은 힙과 스택이다. 
힙은 저주소에서 고주소 방향으로 성장하고, 스택은 반대로 고주소에서 저주소 방향으로,
서로 마주보며 성장하도록 설계되었다.</p>
<p>두 영역이 양 끝에서 서로를 향해 자라도록 배치하면, 
사전에 스택과 힙의 최대 크기를 고정 분할할 필요 없이, 남은 중간 유휴 공간을 동적으로 유연하게 공유할 수 있다. 즉, 한 쪽 영역의 낭비나 충돌(Overflow)을 최소화할 수 있다.</p>
<hr>
<ol start="4">
<li>메모리에는 다음과 같은 블록들이 있으며, 각 블록의 크기는 괄호 안에 표시되어 있다.</li>
</ol>
<p>메모리 블록: A(10), B(50), C(25), D(30), E(40)</p>
<p>다음 순서로 메모리 요청이 들어온다.</p>
<p>요청 순서: 1(30), 2(25), 3(25), 4(10)</p>
<p>First-fit에 대한 예시를 보고, Next-fit과 Best-fit일 때요청에 대한 메모리 할당 순서를 기록하시오. </p>
<p>예) First-fit</p>
<p>1 - B
2 - C
3 - D
4 - A</p>
<p>Next-fit: 
1-B, 
2-C,
3-D,
4-E</p>
<p>Best-fit: 
1-D, 
2-C, 
3-E, 
4-A</p>
<hr>
<ol start="5">
<li>동적 메모리 할당 구현 시 경계 태그(boundary tag)를 사용할 때 가장 큰 장점은 무엇인가? </li>
</ol>
<p>인접 이전 블록의 즉각적인 O(1) 시간 병합(Coalescing):</p>
<p>블록 끝(Footer)에 헤더와 동일한 크기/할당 정보를 복제해 둠으로써,
특정 블록을 해제할 때 물리적으로 바로 앞에 위치한 이전 블록의 가용 여부와 크기를,
리스트 역추적 없이 상수 시간(O(1))에 즉시 확인하고 병합할 수 있다.
그래서 외부 단편화를 빠르고 효율적으로 줄일 수 있다.</p>
<p>다음 블록 병합: 내 헤더의 크기 정보만 더하면 되므로 원래부터 O(1) 가능
이전 블록 병합: 내 앞 블록의 크기를 모르므로 원래는 O(N) -&gt; 경계 태그 덕분에 O(1)로 단축
따라서 경계 태그의 핵심 가치는 &quot;이전 블록까지 포함하여 양방향 모두를 O(1)에 병합할 수 있게 만든 것&quot;</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[예외는 어떻게 생각해내야 하는가?]]></title>
            <link>https://velog.io/@turing_machine/%EC%98%88%EC%99%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%83%9D%EA%B0%81%ED%95%B4%EB%82%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@turing_machine/%EC%98%88%EC%99%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%83%9D%EA%B0%81%ED%95%B4%EB%82%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Mon, 05 Oct 2026 08:23:22 GMT</pubDate>
            <description><![CDATA[<p>시스템 프로그래머들이 예외를 찾아내는 접근 방식은 3가지 기준점으로 정형화되어 있습니다</p>
<p>① &quot;0, 음수, NULL&quot; (경계값의 3대장)</p>
<ul>
<li>함수가 인자(Argument)를 받을 때, <ul>
<li>입력될 수 있는 가장 극단적인 값을 기계적으로 대입해 보는 훈련<ul>
<li>포인터가 들어온다면: &quot;만약 아무 주소도 가리키지 않는 0번지(NULL)가 들어오면?&quot;<ul>
<li>ptr == NULL일 때 역참조(*ptr)하면 즉시 세그멘테이션 폴트(프로그램 강제 종료)가 나므로 무조건 방어해야 함.</li>
</ul>
</li>
<li>크기/숫자가 들어온다면: &quot;만약 크기가 0이 들어오면?&quot;</li>
<li>음수가 들어오면?<ul>
<li>0바이트를 달라고 하거나 0바이트로 줄이라는 엉뚱한 요청을 어떻게 처리할 것인가?</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>② 함수의 생명주기(Lifecycle) 뒤틀기</p>
<ul>
<li>정상적인 흐름은 malloc $\rightarrow$ realloc $\rightarrow$ free 순서입니다.</li>
<li>하지만 사용자는 이 순서를 절대 지켜주지 않는다고 가정합니다:<ul>
<li>할당도 안 해놓고(ptr == NULL) 바로 늘려달라고 떼쓰면?<ul>
<li>&quot;아, 그냥 새로 만들어줘야겠네(malloc).&quot;</li>
</ul>
</li>
<li>늘려달라면서 크기를 0으로 주면? <ul>
<li>&quot;아, 방을 빼겠다는 소리구나(free).&quot;</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>③ 리눅스 man page(설명서) 확인하기</p>
<ul>
<li>가장 현실적인 정답은 &quot;머리로 상상하지 않고 스펙 문서를 읽는 것&quot;입니다.</li>
<li>새로운 시스템 함수나 라이브러리 함수를 구현할 때, 개발자들은 머리를 쥐어짜지 않고 터미널에 man [함수이름]을 칩니다.</li>
<li>매뉴얼의 {RETURN VALUE, ERRORS, DESCRIPTION} 섹션<ul>
<li>어떤 비정상적인 입력이 들어올 수 있고 각각 어떻게 처리해야 하는지가 전부 적혀 있습니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CS:APP 9장 9절 (명시적 할당)]]></title>
            <link>https://velog.io/@turing_machine/CSAPP-9%EC%9E%A5-9%EC%A0%88-%EB%AA%85%EC%8B%9C%EC%A0%81-%ED%95%A0%EB%8B%B9</link>
            <guid>https://velog.io/@turing_machine/CSAPP-9%EC%9E%A5-9%EC%A0%88-%EB%AA%85%EC%8B%9C%EC%A0%81-%ED%95%A0%EB%8B%B9</guid>
            <pubDate>Thu, 01 Oct 2026 12:13:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="📑-99-동적-메모리-할당">📑 9.9 동적 메모리 할당</h3>
<ul>
<li>9.9.1 malloc과 free 함수</li>
<li>9.9.2 왜 동적 메모리 할당인가?</li>
<li>9.9.3 할당기 요구사항과 목표</li>
<li>9.9.4 단편화</li>
<li>9.9.5 구현 이슈</li>
<li>9.9.6 묵시적 가용 리스트</li>
<li>9.9.7 할당한 블록의 배치</li>
<li>9.9.8 가용 블록의 분할</li>
<li>9.9.9 추가적인 힙 메모리 획득하기</li>
<li>9.9.10 가용 블록 연결하기</li>
<li>9.9.11 경계 태그로 연결하기</li>
<li>9.9.12 종합 설계: 간단한 할당기의 구현</li>
<li>9.9.13 명시적 가용 리스트</li>
<li>9.9.14 분리 가용 리스트</li>
</ul>
</blockquote>
<h1 id="한-줄-요약-이-절이-해결하려는-핵심-문제">한 줄 요약: 이 절이 해결하려는 핵심 문제</h1>
<h1 id="메모리-구조비트-블록-레이아웃">메모리 구조/비트: 블록 레이아웃</h1>
<h1 id="팀-논의-질문-이-구현에서-왜-이렇게-처리했을까">팀 논의 질문: 이 구현에서 왜 이렇게 처리했을까?</h1>
<hr>
<p>9.9 동적 메모리 할당</p>
<ul>
<li>물리메모리 vs. 가상메모리</li>
<li>{mmap 함수, munmap 함수} 로도 가상메모리 영역을 생성/삭제 가능</li>
</ul>
<p>808
각각의 프로세스에 대해서, 커널은 힙의 꼭대기를 가리키는 변수 brk(break)를 사용한다
-&gt; 스택에서 push/pop 을 위해서 스택의 꼭대기가 어디인지 추적하는 포인터가 있듯이, 변수 brk 는 힙의 꼭대기가 어디인지 추적하는 포인터인가?
808
할당기는 힙을 다양한 크기의 <strong>블록</strong>들의 집합으로 관리한다. 각 블록은 할당되었거나 가용한 가상메모리의 연속적인 묶음이다.
808</p>
<ul>
<li>명시적 할당기: ex) malloc 패키지 (할당: malloc 함수, 반환: free 함수)</li>
<li>묵시적 할당기: ex) 가비지 컬렉터 (언제 프로그램에 의해 사용되지 않고 블록을 반환하는지를 할당기가 특정할 수 있어야 함)
808 
메모리 할당은 다양한 문맥에서 일어나는 일반적인 아이디어다. (이번 절에서는 힙 메모리를 관리하는 할당기만을 논한다)</li>
</ul>
<p>그림9.33 힙heap.</p>
<p>9.9.1 malloc과 free 함수</p>
<p>809
프로그램은 malloc 함수를 호출해서 힙으로부터 블록들을 할당받는다.</p>
<pre><code>#include &lt;stdlib.h&gt;
void *malloc(size_t size);
Returns: pointer to allocated block if OK, NULL on error</code></pre><p>stdlib.h 를 불러온 뒤 malloc(원하는_바이트수) 를 호출하면, 성공 시 메모리의 시작 주소를 주고, 메모리가 부족해 실패하면 NULL 을 준다. 
그래서 malloc() 을 호출한 뒤 if (p == NULL) 로 잘 할당되었는지 확인하는 에러 체크 코드를 항상 작성해야 한다.
809
malloc 함수는 블록 내에 포함될 수 있는 어떤 종류의 데이터 객체에 대해서 적절히 정렬된 최소 size 바이트를 갖는 메모리 블록의 포인터를 리턴한다** (malloc은 어떤 엄격한 데이터를 집어넣든 CPU가 최고 속도로 에러 없이 읽을 수 있도록, 8이나 16의 배수로 규격화된 주소에 요청한 크기 이상의 넉넉한 공간을 보장해서 넘겨준다)**</p>
<ul>
<li><p><code>최소 size 바이트를 갖는</code></p>
<ul>
<li>요청한 것보다 절대 작게 주지 않는다. 만약 작게 주면 buffer overflow 가 발생하기 때문이다.</li>
<li>더 크게는 줄 수 있다. <ul>
<li>힙 관리용 헤더/푸터 를 붙여야 하기 때문</li>
<li>하드웨어 정렬 단위(8 or 16 바이트)의 배수로 블록 크기를 올림해야 하기 때문.</li>
</ul>
</li>
</ul>
</li>
<li><p><code>적절히 적렬된</code></p>
<ul>
<li>CPU 는 메모리를 1바이트씩 찔끔찔끔 읽지 않는다. 데이터 버스 규격에 맞춰 8 or 16 바이트 단위의 경계선에 걸쳐서 한 번에 긁어옴<ul>
<li>32비트 모드: 주소가 항상 8의 배수(끝 3비트가 000)인 블록을 리턴</li>
<li>64비트 모드: 주소가 항상 16의 배수(끝 4비트가 0000)인 블록을 리턴</li>
</ul>
</li>
<li>CPU 가 메모리에 2번 접근해서 앞뒤를 잘라 합쳐야 한다면, 성능이 반토막난다.</li>
<li>따라서 항상 하드웨어가 가장 빠르게 읽을 수 있도록 주소가 정렬되어 있다.</li>
</ul>
</li>
<li><p><code>어떤 종류의 데이터 객체에 대해서</code></p>
<ul>
<li>C언어는 다양한 자료형이 있고, 요구하는 정렬 규격이 다르다.<ul>
<li>char: 1바이트 정렬 (어디에 있든 상관없음)</li>
<li>int: 4바이트 정렬 (주소가 4의 배수여야 함)</li>
<li>double, long, 포인터: 8바이트 정렬 (주소가 8의 배수여야 함)</li>
<li>long double / SIMD 벡터(__m128): 16바이트 정렬 필요</li>
</ul>
</li>
<li>가장 큰 정렬 기준(16바이트)이 들어오더라도 문제없도록 무조건 최상위 수준으로 정렬된 주소를 내어준다<ul>
<li>16 의 최소 공배수는 1, 2, 4, 8, 16이다.</li>
<li>따라서 어떤 주소가 16의 배수라면, 그 주소는 반드시 1, 2, 4, 8의 배수이다.</li>
<li>따라서 16바이트에 맞추면, 그 어떤 데이터 타입이 들어와도 정렬 규격을 반드시 만족한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>8의 배수
- bin: 맨끝 3비트가 000
- hex: 맨끝은 0 또는 8 (0000 = 0, 1000 = 8)

16의 배수
- bin: 맨끝 4비트가 0000
- hex: 맨끝은 0</code></pre><p>메모리 블록 크기를 나타내는 헤더(Header)를 저장할 때 이 규칙을 활용한다.
모든 블록의 크기와 주소가 8 또는 16의 배수로 정렬되면, 블록 크기를 2진수로 나타냈을 때,
하위 3개 비트(끝 3자리)는 항상 000으로 비어 있게 된다.</p>
<ul>
<li>낭비되는 끝 3비트를 그냥 두지 않고<ul>
<li>맨 마지막 비트 1개를 할당 플래그(Allocated bit) 로 활용한다<ul>
<li>1 (이 블록이 현재 할당되었음)</li>
<li>0 (이 블록이 빈 상태임)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>동적 메모리 3형제</p>
<ul>
<li>malloc: 크기만 잡아서 주소 넘김 (쓰레기값 남아있음)</li>
<li>calloc: 메모리 잡고 전부 0으로 덮어씀<ul>
<li>내부적으로 malloc 을 먼저 부른 다음, 그 자리에 <code>memset(ptr, 0, size)</code> 으로 0으로 초기화해서 반환함</li>
</ul>
</li>
<li>realloc: 기존의 데이터는 보존하면서, 메모리 크기를 늘리거나 줄임<ul>
<li>만약 바로 뒤에 연속된 빈 공간이 없다면, 데이터를 새 자리로 복사(memcpy)해 옮겨 심은 뒤, 이전 자리는 <code>free</code> 한다</li>
</ul>
</li>
</ul>
<p>heap 영역은 아래에서 위로 자란다.</p>
<ul>
<li>brk (break pointer) 은 맨 꼭대기 경계선(울타리)을 가리키는 포인터다.<ul>
<li>brk 아래쪽은 이미 OS한테 허락받아 쓸 수 있다.</li>
<li>brk 위쪽은 아직 OS 소유다 (접근하면, Segmentation Falut 난다)</li>
</ul>
</li>
</ul>
<p>sbrk 시스템 콜 함수 (Space Break)</p>
<ul>
<li><p>sbkr(incr): 울타리를 몇 바이트 더 위로 밀어 올릴 것인가?</p>
<ul>
<li>incr 가 양수인 경우: sbrk(4096) 을 호출하면, 커널이 brk(울타리)를 위로 4KB 밀어 올려준다. 그 결과 힙 영역이 4KB 커진다.</li>
<li>incr 가 0인 경우: sbrk(0) 을 호출하면, 지금 brk 가 어디인지 조회하는 용도로 쓴다.</li>
<li>incr 가 음수인 경우: sbrk(-100) 을 호출하면, brk(울타리)을 아래로 내려서 OS한테 반납한다 (합법)<ul>
<li>하지만 규칙대로, 이전의 brk 주소를 return 한다. 그래서 return 값 처리가 복잡해진다.</li>
</ul>
</li>
</ul>
</li>
<li><p>sbrk() 는 왜 새로운 brk 주소가 아니라, 이전의 brk 주소를 return 할까?</p>
<ul>
<li>영역을 확장하기 전의 위치가, 방금 확장한 영역이 시작점 이기 때문.<ul>
<li>원래 울타리 위치: 0x1000</li>
<li>sbrk(100) 호출 -&gt; 새 울타리 위치: 0x1064</li>
<li>리턴값: 0x1000 (새로 얻은 100바이트 땅의 시작 주소)</li>
</ul>
</li>
<li>malloc 입장에서는 sbrk() 가 돌려준 주소를 그대로 새 청크의 시작 주소로 쓰면 되기에 효율적이다.</li>
</ul>
</li>
<li><p>메모리가 꽉차서 실패하면 (ENOMEM = Erro NO Memory) (void *)-1 을 return 한다</p>
</li>
</ul>
<pre><code>void free(void *ptr);
                        Returns: nothing</code></pre><p>ptr 인자는 malloc, calloc, realloc 에서 획득한 할당된 블록의 시작을 가리켜야 한다  </p>
<ul>
<li>free 는 size 를 인자로 받지 않는다. 인자 ptr 바로 앞에 헤더(메타데이터가 담김)를 숨겨두었다</li>
<li>free 는 아무것도 return 하지 않는다. 그래서 뭔가 잘못되었다는 것을 알릴 수 없고, 런타임 에러 발생 여지가 있다.</li>
</ul>
<p>free(ptr)의 내부 동작</p>
<ul>
<li>크기를 모르기 때문에, 건네 받은 주소의 바로 앞(헤더)을 까봐야 크기를 알 수 있다. <pre><code>// ptr 바로 앞 주소로 4 or 8 바이트 이동해서 숨겨진 헤더를 읽음
header = ptr - 8;
size = get_size(header);    // &quot;아, 이 블록이 N 바이트 짜리구나!&quot;
set_free(header);            // &quot;이제 이 블록은 빈 블록(Free)으로 변경!&quot;</code></pre></li>
</ul>
<p>규칙을 어기는 경우</p>
<ul>
<li>블록 중간 주소를 넘기면? <code>free(ptr + 4)</code><ul>
<li><code>free</code>는 무조건 인자 앞으로 8바이트 이동해서 메모리를 읽으려고 한다</li>
<li>근데 이 경우, 진짜 헤더가 아니라, payload 의 한가운데다.</li>
<li>엉뚱한 데이터를 블록 크기로 해석하면서, 힙 관리 구조가 깨지고, <code>free(): invalide pointer</code> 에러와 함께 SIGSEGV 가 터진다.</li>
</ul>
</li>
<li>스택 변수나 엉뚱한 주소를 넘기면? <code>int a; free(&amp;a);</code><ul>
<li>힙 영역이 아닌 영역을 건드리면, 프로그램이 즉사한다.</li>
</ul>
</li>
<li>이미 해제된 주소를 또 넘기면? <code>Double Free</code><ul>
<li>힙 내부 연결 고리(List) 가 꼬이거나, 순환 참조 루프에 빠져, 보안 취약점의 통로가 된다.</li>
</ul>
</li>
</ul>
<p>811
그림 9.34 (상태 a~e)
812
더 큰 MAXN 값을 사용해서 다시 컴파일하는 것이다... 고정된 배열 크기아 있다는 것은, 수백만 라인의 코드와 수많은 사용자가 있는 큰 규모의 소프트웨어 제품에서는, 관리가 어렵다. 더 나은 방법은 n값을 알 수 있을 때, 배열을 런타임에 동적으로 할당하는 것이다. 이 방법으로, 배열의 최대 크기는 가용한 가상메모리의 양에 의해서만 제한된다.</p>
<hr>
<pre><code>int main()
{
    int *array, i, n;

    scanf(&quot;%d&quot;, &amp;n);
    array = (int *)Malloc(n * sizeof(int));
    for (i = 0; i &lt; n; i++)
        scanf(&quot;%d&quot;, &amp;array[i]);
    free(array);
    exit(0);
}</code></pre><ol>
<li><code>int *array, i, n;</code> (컴파일 타임의 질서)</li>
</ol>
<ul>
<li>스택 프레임에 변수 3개가 자리잡는다.<ul>
<li>n: 정수 4바이트</li>
<li>i: 정수 4바이트</li>
<li>array: 8바이트 주소표(포인터)</li>
</ul>
</li>
<li>컴파일러는 <code>array</code>라는 이름이 스택의 베이스 포인터로부터 몇 바이트 아래에 있는지 미리 알고 있다.</li>
<li>하지만 그 주소표가 가리킬 데이터 본체는 아직 세상에 존재하지 않는다.</li>
</ul>
<ol start="2">
<li><code>scanf(&quot;%d, &amp;n);</code> (카오스의 시작)</li>
</ol>
<ul>
<li>런타임에 유저가 키보드로 숫자를 입력한다.</li>
<li>유저가 <code>5</code>를 넣을지, <code>100만</code>을 넣을지, 프로그램을 실행하기 전까지는 CPU와 컴파일러는 전혀 알 수 없다.</li>
<li>이 순간 정적 배열(<code>int arr[n]</code>) 대신, 동적 메모리 할당이 필요해진다.</li>
</ul>
<ol start="3">
<li><code>array = (int *)Malloc(n * sizeof(int));</code> (주차권 발급)
(대문자 Malloc 은 malloc 이 실패해 NULL을 뱉었을 때, 에러를 출력하고 프로그램을 안전하게 종료시키는 얇은 래퍼 함수다.)</li>
</ol>
<ul>
<li>바이트 계산: <code>sizeof(int)</code> 는 4바이트이므로, 만약 n = 10 이라면 총 40바이트의 연속된 공간이 필요하다.</li>
<li>할당자 내부 동작<ul>
<li>힙 영역의 빈 청크들을 뒤져서 40바이트를 담을 수 있는 자리를 찾는다.</li>
<li>이때 40바이트 딱 맞게 주는 게 아니다. 앞쪽에 메타데이터(헤더 4 or 8바이트)를 붙이고, 하드웨어 4 or 16바이트 정렬 규칙에 맞춘 블록을 마련한다.</li>
<li>주소표 반환: 할당자는 헤더를 건너뛴, 실제 데이터가 들어갈 페이로드의 첫번째 바이트 주소(<code>0x5555...</code>)를 <code>&amp;rax</code>(8바이트 레지스터)에 담아 돌려준다.</li>
<li>대입: 스택에 있던 8바이트 변수 <code>array</code>에 그 주소값이 복사된다. 이제 <code>array</code>는 힙의 그 거대한 공간을 통제하는 유일한 핸들이 된다.</li>
</ul>
</li>
</ul>
<ol start="4">
<li><code>for (i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;array[i]);</code> (포인터 연산)</li>
</ol>
<ul>
<li>array[i] 는 C문법 설탕이고, 본질은 <code>*(array + i)</code> 이다.</li>
<li>array 의 타입이 <code>int * (4바이트 단위)</code>이므로, CPU는 주소 연산을 다음과 같이 한다:<ul>
<li>실제 주소 = array + (4 X i)</li>
</ul>
</li>
<li>힙의 베이스 주소에서 4바이트씩 전진하며 유저가 입력한 정수를 차곡차곡 채워 넣는다.</li>
</ul>
<ol start="5">
<li><code>free(array);</code> (주차권 반납과 헤더)</li>
</ol>
<ul>
<li>free 함수에는 오직 <code>array</code> 주소만 던진다. 배열 크기 n 이나 40바이트 라는 정보를 전혀 전달하지 않는다.</li>
<li>free는 전달받은 <code>array - 8</code> (<code>array</code> 주소에서 앞으로 8바이트 이동) 해서 숨겨진 헤더를 까본다.</li>
<li>헤더에서 &quot;아, 이 블록은 x바이트 크기구나!&quot; 라는 사실을 알아내고, 해당 블록의 할당 비트를 0으로 바꿔 Free List 에 편입시킨다.</li>
<li>만약 앞뒤에 다른 빈 블록이 있다면 병합(Coalescing) 작업까지 수행한다.</li>
<li>스택의 변수 <code>array</code>는 여전히 그 힙 주소값이 그대로 남아 있다 (Dangling Pointer)</li>
</ul>
<ol start="6">
<li><code>exit(0);</code> (자원 정리)</li>
</ol>
<ul>
<li>운영체제에게 정상 종료(0) 신호를 보내며 프로세스의 모든 가상 주소 공간(스택, 힙, 코드)이 일괄 해제된다.</li>
</ul>
<hr>
<p>RAM 메모리는 1차원 배열이다. 스택과 힙도 1차원이다.</p>
<ul>
<li>(논리적으로) 가상 메모리 공간은 0x0000000000000000 번지부터 바이트 단위로 인덱스가 매겨진 거대한 1차원 <code>char memory[]</code> 배열과 같다.</li>
<li>Stack, Heap, BSS, Data, Code(Text) 영역은 그저 하나의 1차원 주소선 위에 구역(Segment)만 나눠둔 것이다.<ul>
<li>Heap 은 낮은 주소에서 높은 주소 방향으로 자라고,</li>
<li>Stack 은 높은 주소에서 낮은 주소 방향으로 자라고,</li>
<li>둘다 동인한 1차원 수직선 위를 오르내릴 뿐이다.</li>
</ul>
</li>
</ul>
<p>malloc() 은 항상 &quot;연속된&quot; 공간을 할당하는가?</p>
<ul>
<li>소프트웨어 (가상 메모리) 관점<ul>
<li>malloc(4) 을 호출했을 때, 20바이트는 0x1000 에 주고 나머지 20바이트는 0x5000 에 떨어뜨려서 주는 일은 절대 없다.</li>
<li>이유: C언어의 핵심인 포인터 연산(<code>*(ptr + i)</code>) 때문이다. <ul>
<li><code>array[3]</code> 을 읽으려면 CPU는 단순히 시작 주소에 오프셋을 더하는 단일 덧셈 연산(<code>array + 3 * 4</code>)을 수행한다.</li>
<li>공간의 중간이 끊겨 있다면, 덧셈 한 번으로 다음 원소를 찾아갈 수 없으므로, C언어의 배열 문법과 포인터 연산 전체가 성립하지 않는다.</li>
</ul>
</li>
</ul>
</li>
<li>하드웨어 (물리 메모리) 관점<ul>
<li>실제 물리 RAM: 페이지 테이블(MMU)이 중간에서 매핑해주기 때문에, 물리적으로 4KB 단위 페이지들이 흩어져 있어도 상관없다. 하지만 CPU 와 프로그램은 이를 인지하지 못하며, 연속된 주소 공간으로만 인식하고 사용한다.</li>
</ul>
</li>
</ul>
<hr>
<p>813
프로그래머들은 할당기를 정확하고 효율적으로 사용하기 위해서 어떻게 이들이 동작하는지 이해할 필요가 있다.
9.11절에서 할당기의 잘못된 사용으로 발생할 수 있는 위험한 에러들에 대해 설명할 것이다.</p>
<h3 id="993-할당기-요구사항과-목표">9.9.3 할당기 요구사항과 목표</h3>
<p>요구사항</p>
<ul>
<li>임의의 요청 순서 처리하기</li>
<li>요청 즉시 응답하기</li>
<li>힙만 사용하기</li>
<li>블록 정렬하기 (정렬 요건)</li>
<li>할당된 블록을 수정하지 않기</li>
</ul>
<p>813
일반적으로, 할당과 반환 요청들을 만족시키기 위한 평균 시간을 최소화해서 처리량을 최대화한다.
813
한 시스템에서 모든 프로세스에 의해 할당된 가상메모리의 양은 디스크 내의 스왑 공간의 양에 의해 제한된다.
814
$U_k = \frac{\max_{i \le k} P_i}{H_k}$</p>
<ul>
<li>비율 0~1<ul>
<li>분모: OS 한테 뜯어낸 전체 땅</li>
<li>분자: 진짜로 쓴 알맹이 데이터</li>
</ul>
</li>
<li>비율 1 (100%) 로 꽉 채우는 것은 불가능하다.<ul>
<li>헤더도 붙여아하고, 8 or 16 바이트 정렬도 맞춰야 하고, 단편화(중간에 쪼개진 빈 공간)도 생기기 때문에, 분모는 분자보다 항상 크다.</li>
</ul>
</li>
<li>Malloc Lab 점수 산출 기준:<ul>
<li>과제 채점기(mdriver)를 돌리면 두 가지 점수가 나옵니다:<ul>
<li>Throughput (처리량): 초당 malloc/free를 얼마나 빠르게 처리하는가?   </li>
<li>Memory Utilization (메모리 이용도): 바로 $U_{n-1}$ 값. 땅을 낭비하지 않고 얼마나 알뜰하게 썼는가?</li>
</ul>
</li>
</ul>
</li>
<li>트레이드오프(긴장 관계): <ul>
<li>처리량을 늘리려고 대충 빈곳에 던져주면 이용도가 개판이 되고, </li>
<li>이용도를 극대화하려고 빈틈없이 맞추려다 보면 탐색 속도가 느려진다.</li>
</ul>
</li>
</ul>
<h3 id="994-단편화">9.9.4 단편화</h3>
<p>2종류의 단편화가 있다.
외부 단편화는 측정하기 어렵고 예측 불가능하기 때문에 
할당기들은 대개 <code>많은 수의 더 작은 가용 블록들</code>보다는, <code>더 적은 수의 더 큰 가용 블록들</code>을 유지하려는 방법들을 채택하고 있다.</p>
<p>내부 단편화는 현재의 힙 공간으로 감당 가능하지만,
외부 단편화는 현재의 힙 공간으로 감당 불가능하다. 그래서 OS 에게 추가 힙 공간을 요청(<code>sbrk</code>)해야 한다.</p>
<ul>
<li>내부 단편화<ul>
<li>정량화가 단순하다. 할당된 블록의 크기와 이들의 데이터 사이의 차이의 합이다.<ul>
<li>시간상 어디서든 내부 단편화의 양은 이전에 요청한 패턴과 할당기 구현에만 의존한다.</li>
</ul>
</li>
</ul>
</li>
<li>외부 단편화<ul>
<li>할당된 요청을 만족시킬 수 있는 메모리 공간이 전체적으로 공간을 모았을 때는 충분한 크기가 존재하지만, 이 요청을 처리할 수 있는 단일한 가용블록은 없는 경우에 발생한다.<ul>
<li>이전 요청의 패턴과 할당기 구현에만 의존하는 것이 아니라, 미래의 요청 패턴에도 의존한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="995-구현-이슈">9.9.5 구현 이슈</h3>
<p>815
이 초보적인 할당기는 디자인 공간에서 극단점에 해당한다.
815
처리량과 이용도 사이에 좋은 <code>균형</code>을 갖는 실용적인 할당기는 다음 이슈들을 고려해야 한다:</p>
<ul>
<li>가용 블록 구성: 어떻게 가용 블록을 지속적으로 추적하는가?</li>
<li>배치: 새롭게 할당된 블록을 배치하기 위한 가용 블록을 어떻게 선택하는가?</li>
<li>분할: 새롭게 할당된 블록을 가용 블록에 배치한 후 가용 블록의 나머지 부분들로 무엇을 할 것인가?</li>
<li>연결: 방금 반환된 블록으로 무엇을 할 것인가?</li>
</ul>
<p>배치, 분할, 연결은 서로 다른 가용 블록 구조와 관련된다.
묵시적 가용 리스트로 알려진, 간단한 가용 블록 구조의 맥락에서 소개할 것이다.</p>
<p><code>반납된 빈 땅을 재활용</code> 하면서도, <code>속도 저하를 최소화</code>하기 위해 <code>4가지 설계 변수</code>를 결정해야 한다.
4가지 각각을 어떻게 조합하느냐에 따라 과제의 Throughput(처리 속도)과 Utilization(메모리 이용도) 점수 판도가 완전히 갈리게 된다.</p>
<p>4가지 핵심 설계</p>
<ul>
<li>1) 가용 블록 구성 (Free Block Organization): 힙 안에 블록들(할당/가용)이 마구 섞여 있을 때, 가용 블록을 어떻게 지속적으로 추적하는가?<ul>
<li>암묵적 가용 리스트 (Implicit List)<ul>
<li>빈 블록만을 위한 별도의 장부 없이, 헤더의 크기 정보를 이용해서, 힙의 모든 블록(할당/가용)을 차례대로 탐색</li>
</ul>
</li>
<li>명시적 가용 리스트 (Explicit List)<ul>
<li>빈 블록 내부의 페이로드 공간 (어차피 비어있으므로 유저 데이터가 없음)에 <code>next</code>, <code>prev</code> 포인터를 심어서, 빈 블록들끼리만 이중 연결 리스트(Doubly Linked List)로 엮어둔다.</li>
<li>할당된 블록은 건너뛰고, 빈 블록들만 순회하므로, 탐색 속도가 크게 오른다.</li>
</ul>
</li>
<li>분리 가용 리스트 (Segregated Free List)<ul>
<li>크기별로 리스트를 여러 개 만든다 (ex. 16<del>32바이트 용, 33</del>64바이트 용, 65~128바이트 용)</li>
<li>현대 상용 할당기(<code>ptmalloc</code>, <code>jemalloc</code>)가 채택하는 방식으로, 탐색 시간이 거의 $O(1)$ 에 가까워진다.</li>
</ul>
</li>
</ul>
</li>
<li>2) 배치 (Placement): 가용 블록이 여러 개 있을 때, 어떻게 선택하는가?<ul>
<li>First-Fit<ul>
<li>리스트의 처음부터 탐색하다가, 요청 크기를 수용할 수 있는 가장 첫 번째 빈 블록을 바로 선택</li>
<li>탐색 속도가 비교적 빠르지만, 리스트 앞쪽에 자투리 조각들이 쌓이는 경향이 있다.</li>
</ul>
</li>
<li>Next-Fit<ul>
<li>직전 탐색이 끝난 위치부터 다음 탐색을 시작</li>
<li>First-Fit 보다 속도가 빠를 수 있으나, 메모리 이용도가 떨어지는 경우가 많을 수 있다.</li>
</ul>
</li>
<li>Best-Fit<ul>
<li>들어갈 수 있는 모든 빈 블록을 검사한 뒤, 최적의 (크기 차이가 가장 작은) 블록을 고름</li>
<li>단편화를 줄여 메모리 이용도는 최고 수준이지만, 매번 리스트 전체를 뒤져야 하므로 처리량이 급감한다.</li>
</ul>
</li>
</ul>
</li>
<li>3) 분할 (Splitting): 가용 블록의 나머지 부분들로 무엇을 할 것인가?<ul>
<li>분할 하지 않음<ul>
<li>100바이트를 통째로 할당 및 사용</li>
<li>구현은 편하지만, 무려 80바이트의 심각한 내부 단편화</li>
</ul>
</li>
<li>분할 (내부 단편화를 획기적으로 줄이는 필수(?) 테크닉)<ul>
<li>예를 들어, 20바이트가 필요한데, 찾아낸 빈 블록이 100바이트 크기라면, 어떻게 처리할 것인가?</li>
<li>100바이트를 쪼개서 앞쪽 24바이트 (헤더 포함)는 할당 블록으로 만들고,</li>
<li>남은 76바이트는 새로운 작은 가용 블록으로 헤더/푸터 를 다시 세팅해서 가용 리스트에 남겨둔다.</li>
</ul>
</li>
</ul>
</li>
<li>4) 연결 (Coalescing): 방금 반환된 블록으로 무엇을 할 것인가?<ul>
<li>연결하지 않음<ul>
<li>작은 빈 조각들을 방치: 외부 단편화가 폭발, 전체 빈 공간은 넉넉하지만, 시스템 호출 sbrk() 을 할 경우가 아주 많아질 수 있음</li>
</ul>
</li>
<li>즉시 연결 (Immediate Coalescing)<ul>
<li><code>free</code> 가 불리자마자 <code>앞 블록의 푸터</code> 와 <code>뒤 블록의 헤더</code> 를 확인하여, 비어있는 이웃이 있다면, 즉시 하나의 거대한 빈 블록으로 병합</li>
</ul>
</li>
<li>지연 연결 (Deferred Coalescing)<ul>
<li><code>free</code> 때는 그냥 두고, 나중에 <code>malloc</code> 이 빈 공간을 찾다가 실패했을 때, 힙 전체를 한 번에 싹 훑으며 병합</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>*<em>빈 조각들을 최적으로 채워 넣는 빈 패킹(Bin Packing) 문제는 유한한 경우의 수를 다룸에도 불구하고 다항 시간 안에 완벽한 답을 낼 수 없는 대표적인 난제다. 할당기를 설계한다는 것은 거대한 조합 속에서 &#39;수학적 완벽성&#39;을 찾는 것이 아니라, 경험적 휴리스틱 (First-fit, Segregated list 등) 을 통해 유한한 자원 내에서 실패 확률 (외부 단편화, 지연 시간) 을 실용적인 수준으로 억제하는 작업이다.
*</em></p>
<hr>
<h3 id="996-묵시적-가용-리스트-헤더-내-필드에-의해-묵시적으로-연결됌">9.9.6 묵시적 가용 리스트 (헤더 내 필드에 의해 묵시적으로 연결됌)</h3>
<p>모든 실용적인 할당기는 블록 경계를 구분하고, 할당/가용 블록을 구분하는 데이터 구조를 필요로 한다.
대부분의 할당기는 이 정보를 블록 내에 저장한다.</p>
<p>1워드 헤더, 데이터, 추가적인 패딩</p>
<ul>
<li>헤더가 인코딩하는 정보<ul>
<li>블록 크기 (헤더, 패딩 포함)</li>
<li>블록 할당 여부</li>
</ul>
</li>
</ul>
<p>패딩을 하는 이유는 여러 가지다. </p>
<ul>
<li><p>Minimum Block Size 충족: 해제 시 <code>next / prev</code> 포인터를 담을 수 있는 최저 바이트(16 or 24) 확보</p>
</li>
<li><p>외부 단편화 극복 전략: 할당기 정책상, 블록 분할 후 남는 자투리가 너무 작아 못 쓰게 될 바엔, 기존 블록 패딩으로 흡수</p>
</li>
<li><p>정렬 요구사항 충족: CPU의 4/8/16 바이트 단위 메모리 버스 정렬 규칙 만족</p>
</li>
<li><p>캐시 라인(64바이트) 정렬: 캐시 라인 분할 접근 방지 -&gt; CPU 메모리 읽기 사이클 최소화</p>
</li>
<li><p>멀티스레드 환경의 False Sharing 방지: 코어 간 불필요한 캐시 무효화 핑퐁 방지</p>
</li>
</ul>
<p>할당기는 간접적으로 가용 블록 전체 집합을, 힙 내의 전체 블록을 다니면서 방문할 수 있다.
(가용 블록들끼리 직접 이어주는 연결 고리(포인터)가 없다.</p>
<ul>
<li>블록 A의 헤더를 읽어서, 크기가 S 바이트임을 확인한다.</li>
<li>주소에 S 를 더해서(<code>ptr + S</code> ) 다음 블록 B의 헤더로 이동한다.</li>
<li>빈 공간을 찾을 때까지, 이 작업을 힙의 끝(에플로그 블록)까지 징검다리 건너듯 반복한다. 가용 블록으로 곧장 점프할 수 없고, 중간에 놓인 할당 블록들을 전부 밟고 지나가야 하므로, &quot;간접적&quot;이라고 표현했다.</li>
</ul>
<p>장점: 단순성
단점: 연산(할당된 블록 배치, 가용 리스트 탐색) 비용은 힙에 있는 전체 할당/가용 블록의 수에 비례한다.</p>
<hr>
<h3 id="997-할당된-블록의-배치">9.9.7 할당된 블록의 배치</h3>
<ul>
<li>First fit<ul>
<li>장점: 리스트의 마지막에 가장 큰 가용 블록들을 남겨두는 경향이 있다.</li>
<li>단점: 리스트의 앞부분에 작은 가용 블록들을 남겨두는 경향이 있다.<ul>
<li>큰 블록을 찾는 경우, 검색 시간이 늘어난다.</li>
</ul>
</li>
</ul>
</li>
<li>Next fit<ul>
<li>이전 검색에서 가용 블록을 발견했다면, 다음 검색에서는 리스트의 나머지 부분에서 원하는 블록을 찾을 가능성이 높다는 희망</li>
<li>장점: 리스트의 앞부분에 많은 작은 크기의 조각들로 구성되는 경우, First fit 에 비해서 아주 빠른 속도</li>
<li>단점: First fit 에 비해서 나쁜 메모리 이용도를 가지는 경향</li>
</ul>
</li>
<li>Best fit<ul>
<li>장점: 대체로 더 좋은 메모리 이용도</li>
<li>단점: 묵시적 가용 리스트에서는, 힙을 싹다 검색해야 한다.<ul>
<li>대안: 정책을 단순화해서, 힙을 모두 검색하지 않는, segregated free list </li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="998-가용-블록의-분할">9.9.8 가용 블록의 분할</h3>
<h3 id="999-추가적인-힙-메모리-획득하기">9.9.9 추가적인 힙 메모리 획득하기</h3>
<hr>
<h3 id="9910-가용-블록-연결하기">9.9.10 가용 블록 연결하기</h3>
<p>빠른 할당기들은 종종 지연 연결의 형태를 선택한다는 것을 알아야 한다.</p>
<hr>
<h3 id="9911-경계-태그로-연결하기">9.9.11 경계 태그로 연결하기</h3>
<h3 id="9912-종합-설계-간단한-할당기의-구현">9.9.12 종합 설계: 간단한 할당기의 구현</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[malloc() 의 문제의식은 뭐였을까?
malloc() 은 왜 void* 를 return 하는가?]]></title>
            <link>https://velog.io/@turing_machine/malloc-%EC%97%90-%EB%8C%80%ED%95%9C-%ED%98%84%EC%9E%AC-%EB%82%B4-%EC%83%9D%EA%B0%81-malloc-%EC%9D%80-%EC%99%9C-void-%EB%A5%BC-return-%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@turing_machine/malloc-%EC%97%90-%EB%8C%80%ED%95%9C-%ED%98%84%EC%9E%AC-%EB%82%B4-%EC%83%9D%EA%B0%81-malloc-%EC%9D%80-%EC%99%9C-void-%EB%A5%BC-return-%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Thu, 01 Oct 2026 08:36:17 GMT</pubDate>
            <description><![CDATA[<p>malloc 예시: 주차장에서 자동차를 주차하는 발렛 요원의 업무 알고리즘</p>
<p>필요해보이는 개념:  청크, 페이지, OS 간에 상호작용</p>
<p>malloc 에 대한 현재 내 생각</p>
<ul>
<li>문제의식: <ul>
<li>컴파일 타임에 증명할 수 없는 데이터를 담기 위한 그릇이 필요하다. 현실은 아마도 카오스 세계일 것이기에 런타임의 데이터를 100% 예측할 수 없을 것이고, 단 하나의 완벽한 malloc 함수는 없을 것이다. 그래서 수십 년동안 IT 에서 이 난제를 풀기 위해 고민해왔다.<ul>
<li>불확실한 상황에서 하드웨어 리소스를 어떻게 사용할 것인가? <ul>
<li>메모리 활용도 높이기 (트레이드오프: Latency)</li>
<li>처리 속도 높이기 (트레이드오프: Fragmentation)</li>
</ul>
</li>
</ul>
</li>
<li>당연히 다양한 맥락이 있을 것이고, 특정한 맥락이 조금씩 바뀔지라도, 경향성이 패턴으로 포착되는 한, 나름의 적당한 해법(특정한 malloc 알고리즘)이 있을 것이다.</li>
</ul>
</li>
</ul>
<hr>
<p>의문: malloc() 은 왜 <code>void*</code> 를 return 하는가?</p>
<ul>
<li>할당해준 메모리에 호출자가 어떤 타입의 데이터를 담을지 미리 알 수 없기 때문<ul>
<li>malloc() 입장에서 타입을 몰라도 모든 타입에 대해 범용적으로 메모리를 할당<ul>
<li>만약 C언어에서 void*가 없었다면, 타입마다 함수를 따로 만들어야 했을 것임<ul>
<li>malloc_int()</li>
<li>malloc_char()</li>
<li>malloc_struct_node() </li>
<li>등등...</li>
</ul>
</li>
</ul>
</li>
<li>C언어 규칙상 void* 는 어떤 포인터 타입으로든 명시적인 캐스팅 없이 암묵적으로 형변환된다 (Implicit Conversion). 반환값을 임의의 특정 포인터 변수에 그대로 대입할 수 있다.<ul>
<li><code>int *arr = malloc(sizeof(int) * 10);</code></li>
<li><code>char *str = malloc(sizeof(char) * 32);</code></li>
<li><code>struct Node *node = malloc(sizeof(struct Node));</code></li>
</ul>
</li>
<li>malloc() 은 타입 정보가 없기에 데이터 해석 규칙이나 크기 정보를 모른다. 순수하게 물리적 메모리 시작 위치만 건네준다. <ul>
<li>특정 포인터의 타입은 컴파일러에게 &quot;이 주소에서 몇 바이트를 읽어야 하는가&quot;,  &quot;포인터 연산(p + 1) 시 몇 바이트를 건너뛰어야 하는가&quot; 를 알려준다.<ul>
<li>int *p; -&gt; p + 1 은 4바이트 이동</li>
<li>double *p; -&gt; p + 1 은 8바이트 이동</li>
<li>void *p; -&gt; 가리키는 대상의 크기 정보가 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>결론: 성공해서 정상적인 주소를 주든, 실패해서 NULL을 주든, 기계어 관점에서는 8바이트 주소 레지스터(%rax)를 쓰는 <code>void *</code> 타입 포인터를 반환하는 것은 고정이다.</p>
<hr>
<p>  <img src="https://velog.velcdn.com/images/turing_machine/post/9d64f7b3-bfea-4878-8c4b-e10609d5c724/image.png" alt=""><img src="https://velog.velcdn.com/images/turing_machine/post/5fdf200f-9e93-42db-be0b-8a58fb91c09c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[C언어에서 UB(Undefined Behavior) 의 수학적 구조 ]]></title>
            <link>https://velog.io/@turing_machine/C%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C-UBUndefined-Behavior-%EC%9D%98-%EC%88%98%ED%95%99%EC%A0%81-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@turing_machine/C%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C-UBUndefined-Behavior-%EC%9D%98-%EC%88%98%ED%95%99%EC%A0%81-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 24 Sep 2026 13:05:59 GMT</pubDate>
            <description><![CDATA[<p><strong>C언어에서 UB 는 공허참 패턴이다.</strong></p>
<hr>
<p><strong>진리표(Truth table)는 공리다</strong>: <strong>인간이 합리적으로 생각하는 기준</strong>으로 정했다</p>
<p><code>P -&gt; Q  :  결과</code></p>
<p><code>T -&gt; T  :  T</code> [증명 패턴] ··· (1)
<code>T -&gt; F  :  F</code> [반례 패턴] ··· (2)
<code>F -&gt; T  :  T</code> <strong>[공허참 패턴] ··· (3)</strong>
<code>F -&gt; F  :  T</code> <strong>[공허참 패턴] ··· (4)</strong></p>
<p>수학에서는 오직 (1) 만 내용적 정보를 준다. 
(3)과 (4)는 내용적 정보를 주지 않는다.</p>
<p>반증 불가능하면 과학이 아니다. 
<strong>(3)과 (4)는 과학이 아니다.</strong></p>
<p><strong>공허참 패턴 예시</strong></p>
<ul>
<li>ex1: SF소설을 쓸 수 있다. <strong>애시당초 뻥이기 때문.</strong></li>
<li>ex2: 어떤 집합에 조건적으로 이름 지을 때, 공집합은 어떤 조건도 만족하지 않기에, 어떤 이름을 붙여도 <strong>항상 참이다.</strong></li>
</ul>
<hr>
<p><strong>C언어에서 UB 는 공허하게 참이다. 항상 참이다. 애시당초 뻥이기 때문.</strong></p>
<p>C 언어 표준은 프로그래머와 컴파일러 사이의 거대한 조건 명제(P -&gt; Q)로 이루어져 있다.</p>
<p><strong>전제 P: **
**프로그래머는 유효하지 않은 메모리를 참조하지 않고, 오버플로우를 내지 않으며, 언어 규격을 엄격히 준수한다.</strong>
<del>(대담한 선언이다. 왜냐면 꼼꼼한 수학자조차 실수를 하기 때문이다.)</del></p>
<p><strong>결론 Q:</strong> 
<strong>컴파일러는 프로그래머의 의도대로 정확히 동작하는 기계어 코드를 생성한다.</strong></p>
<p>우리가 기대하는 정상적인 프로그래밍 세계는 P가 참(T)인 세계다.
프로그래머가 규칙을 지켰으니(P = T), 
컴파일러도 약속된 기계어를 안전하게 번역해낸다(Q = T).</p>
<p>프로그래머가 이미 해제된 스택 주소를 역참조하거나(Dangling Pointer), 
유효하지 않은 메모리를 건드리는 순간,
전제 조건 P는 거짓(F)이 된다.</p>
<ul>
<li>** F -&gt; T** (컴파일러가 코드를 우연히 정상 작동시킴): <strong>논리적으로 문제없음 (참).</strong></li>
<li>** F -&gt; F** (컴파일러가 프로그램을 강제 종료시키거나 하드디스크를 포맷함): <strong>논리적으로 문제없음 (참).</strong></li>
</ul>
<p><strong>C 언어에서 Undefined Behavior(UB)를 발생시키는 것은, 
*<em>시스템의 전제가 깨져 *</em>어떤 헛소리를 해도 논리적으로 &#39;참&#39;이 되어버리는 공허참</strong>의 늪에 빠지는 것이다.</p>
<p>하드웨어는 항상 물리 법칙에 따라 특정 전압을 읽지만, 
언어의 논리 체계 안에서는 이미 공리계가 붕괴되어 <strong>어떤 결과도 보장할 수 없게 된다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[x86 명령어 movl 에 드는 위화감]]></title>
            <link>https://velog.io/@turing_machine/x86-%EB%AA%85%EB%A0%B9%EC%96%B4-movl-%EC%97%90-%EB%93%9C%EB%8A%94-%EC%9C%84%ED%99%94%EA%B0%90</link>
            <guid>https://velog.io/@turing_machine/x86-%EB%AA%85%EB%A0%B9%EC%96%B4-movl-%EC%97%90-%EB%93%9C%EB%8A%94-%EC%9C%84%ED%99%94%EA%B0%90</guid>
            <pubDate>Tue, 22 Sep 2026 05:14:29 GMT</pubDate>
            <description><![CDATA[<p>어셈블리 명령어: movl a, b </p>
<p>실행 전: a = 10, b = 999
실행 후: a = 10, b = 10</p>
<p>원본(a) 은 원래 값을 유지한다.</p>
<p>move 라는 단어는 Ctrl+X, Ctrl+V 라는 뉘앙스인데,
실제로는 Ctrl+C, Ctrl+V 이다.</p>
<pre><code>#include &lt;stdio.h&gt;

int main(void) {
    int a = 10;
    int b = 999;

    printf(&quot;=== movl 실행 전 ===\n&quot;);
    printf(&quot;a = %d (주소: %p)\n&quot;, a, (void*)&amp;a);
    printf(&quot;b = %d (주소: %p)\n\n&quot;, b, (void*)&amp;b);

    // 인라인 어셈블리로 movl 직접 실행
    // GCC / Clang 기본 AT&amp;T문법: movl [출발지], [목적지]
    __asm__ (
        &quot;movl %1, %0\n\t&quot;  // %1(출발지 a)의 값을 %0(목적지 b)로 복사
        : &quot;=r&quot; (b)         // 출력(Output): 수정되는 목적이 변수 b (%0)
        : &quot;r&quot; (a)          // 입력(Input): 읽기만 하는 출발지 변수 a (%1)
    );

    printf(&quot;=== movl 실행 후 ===\n&quot;);
    printf(&quot;a = %d (여전히 10으로 온전히 유지됌)\n&quot;, a);
    printf(&quot;b = %d (999에서 10으로 덮어씌워짐)\n\n&quot;, b);

    // 원본 a를 이후에도 정상적으로 재사용할 수 있는지 확인
    int c = a + 5;
    printf(&quot;a를 재사용한 계산 (a + 5) = %d\n&quot;, c);

    return 0;
}</code></pre><pre><code>./test_mov
=== movl 실행 전 ===
a = 10 (주소: 0x7fffff6bf00c)
b = 999 (주소: 0x7fffff6bf010)

=== movl 실행 후 ===
a = 10 (여전히 10으로 온전히 유지됌)
b = 10 (999에서 10으로 덮어씌워짐)

a를 재사용한 계산 (a + 5) = 15</code></pre><p>movl을 수행하더라도 
원본 레지스터/변수 a에는 아무런 부작용(쓰레기 값 발생, 비워짐 등)이 없으며, 
단순 Read &amp; Copy 동작임을 직접 확인했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axioms and Definitions]]></title>
            <link>https://velog.io/@turing_machine/Axioms-and-Definitions</link>
            <guid>https://velog.io/@turing_machine/Axioms-and-Definitions</guid>
            <pubDate>Tue, 22 Sep 2026 02:09:28 GMT</pubDate>
            <description><![CDATA[<p>We will build the machine from pure mathematical axioms, one atomic definition at a time. Every claim will be supported by its formal definition.</p>
<hr>
<h1 id="axiom-1-the-machine-state-as-a-tuple">Axiom 1: The Machine State as a Tuple</h1>
<p>A 64-bit x86-64 CPU is a discrete deterministic state machine. 
Its core state at any discrete clock step $t$ is a mathematical tuple:</p>
<p>$$\mathcal{S}_t = \langle \mathcal{R}, \mathcal{M}, \text{rip} \rangle$$</p>
<h3 id="1-registers-mathcalr-a-finite-set-of-sixteen-named-64-bit-words">1. Registers ($\mathcal{R}$): A finite set of sixteen named 64-bit words:</h3>
<p>$$\mathcal{R} = { \text{rax}, \text{rbx}, \text{rcx}, \text{rdx}, \text{rsi}, \text{rdi}, \text{rbp}, \text{rsp}, \text{r8} \dots \text{r15} }$$</p>
<p>Each register is a function mapping a register name to an integer in ${0, \dots, 2^{64}-1}$: </p>
<p>$$\mathcal{R}: \text{Name} \to \mathbb{W}_{64}$$</p>
<h3 id="2-memory-mathcalm-a-contiguous-byte-addressable-array">2. Memory ($\mathcal{M}$): A contiguous byte-addressable array:</h3>
<p>$$\mathcal{M}: \mathbb{W}<em>{64} \to \mathbb{W}</em>{8}$$</p>
<p>Reading an 8-byte word (64 bits) from address $a$ means 
fetching the 8 consecutive bytes starting at address $a$:</p>
<p>$$\mathcal{M}<em>{64}[a] = \sum</em>{k=0}^{7} \mathcal{M}[a+k] \cdot 2^{8k} \quad (\text{Little-Endian representation})$$</p>
<h3 id="3-instruction-pointer-textrip-a-single-64-bit-scalar-holding-the-memory-address-of-the-next-instruction-to-execute">3. Instruction Pointer ($\text{rip}$): A single 64-bit scalar holding the memory address of the next instruction to execute:</h3>
<p>$$\text{rip} \in \mathbb{W}_{64}$$</p>
<hr>
<h1 id="axiom-2-operand-syntax-source-to-destination">Axiom 2: Operand Syntax (Source $\to$ Destination)</h1>
<p>In x86-64 AT&amp;T syntax, an instruction is a transition function $\mathcal{T}: \mathcal{S}<em>t \to \mathcal{S}</em>{t+1}$.The general syntax is strictly:</p>
<p>$$\text{OPCODE} \quad \text{Source}, \quad \text{Destination}$$</p>
<ul>
<li>Rule of Data Flow: </li>
</ul>
<p>The value is read from $\text{Source}$, modified by $\text{OPCODE}$, and written to $\text{Destination}$.</p>
<ul>
<li>The Dollar Sign Prefix ($): </li>
</ul>
<p>Denotes an immediate constant (a pure mathematical scalar $c \in \mathbb{Z}$).</p>
<p> $$0 \implies \text{Value } 0$
 $$4 \implies \text{Value } 4$</p>
<ul>
<li>The Percent Sign Prefix (%): </li>
</ul>
<p>Denotes a register name in $\mathcal{R}$.</p>
<p> %rax</p>
<h4 id="concrete-evidence">Concrete Evidence:</h4>
<p>$$\text{Instruction: } \texttt{movq $0, %rax}$$</p>
<ul>
<li>Mathematical Definition: $$\mathcal{R}_{t+1}[\text{rax}] \leftarrow 0$$</li>
<li>Proof of Direction: 
The source is $0 (left). The destination is %rax (right). The scalar 0 is placed into register rax.</li>
</ul>
<hr>
<h1 id="axiom-3-address-calculation-via-parentheses">Axiom 3: Address Calculation via Parentheses</h1>
<p>Parentheses denote Memory Dereferencing (pointer arithmetic). 
If $k \in \mathbb{Z}$ is an integer literal and $\text{reg} \in \mathcal{R}$, the notation:</p>
<p>$$k(\text{%reg})$$</p>
<p>evaluates to the physical memory location at the address:</p>
<p>$$\text{Effective Address} = \mathcal{R}[\text{reg}] + k$$</p>
<p>Therefore:</p>
<p>$$\texttt{movq } \text{Source}, \quad k(\text{%reg}) \implies \mathcal{M}_{64}[\mathcal{R}[\text{reg}] + k] \leftarrow \text{Value}(\text{Source})$$</p>
<h4 id="concrete-evidence-for--8rbp-and--24rbp">Concrete Evidence for -8(%rbp) and -24(%rbp):</h4>
<p>Suppose %rbp currently holds the address $1000$ (i.e., $\mathcal{R}[\text{rbp}] = 1000$).</p>
<ul>
<li>Case 1: -8(%rbp)</li>
</ul>
<p>$$\text{Effective Address} = 1000 + (-8) = 992$$</p>
<p>The instruction movq $0, -8(%rbp) does:</p>
<p>$$\mathcal{M}_{64}[992] \leftarrow 0$$</p>
<ul>
<li>Case 2: -24(%rbp)</li>
</ul>
<p>$$\text{Effective Address} = 1000 + (-24) = 976$$</p>
<p>The instruction subq $1, -24(%rbp) does:</p>
<p>$$\mathcal{M}<em>{64}[976] \leftarrow \mathcal{M}</em>{64}[976] - 1$$</p>
<h4 id="why-did-the-compiler-pick-992-and-976">Why did the compiler pick $992$ and $976$?</h4>
<p>Because each 64-bit integer takes 8 bytes.</p>
<ul>
<li>Byte interval for slot 1: $[992, 999]$ (8 bytes wide $\implies$ called sum in C).</li>
<li>Byte interval for slot 2: $[976, 983]$ (8 bytes wide $\implies$ called n in C).</li>
</ul>
<hr>
<h1 id="axiom-4-arithmetic-transformation-rules">Axiom 4: Arithmetic Transformation Rules</h1>
<p>Let us formalize the four basic arithmetic instructions:</p>
<table>
<thead>
<tr>
<th>Instruction</th>
<th>Formal State Transformation</th>
<th>Plain Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>movq S, D</td>
<td>$D \leftarrow S$</td>
<td>Overwrite $D$ with $S$.</td>
</tr>
<tr>
<td>addq S, D</td>
<td>$D \leftarrow D + S$</td>
<td>Add $S$ to $D$, store result in $D$.</td>
</tr>
<tr>
<td>subq S, D</td>
<td>$D \leftarrow D - S$</td>
<td>Subtract $S$ from $D$, store result in $D$.</td>
</tr>
<tr>
<td>cmpq S2, S1</td>
<td>Discard $(S_1 - S_2)$, update $\text{Flags}$</td>
<td>Compare: compute $S_1 - S_2$ only to set flags.</td>
</tr>
</tbody></table>
<p>*<em>Crucial Detail on <code>cmpq S2, S1</code>:
*</em></p>
<p>The comparison computes Destination minus Source ($\text{Second} - \text{First}$).
Therefore, <code>cmpq $0, %rax</code> computes:</p>
<p>If $\mathcal{R}[\text{rax}] &gt; 0$, the result is positive, and the machine records &quot;Greater Than&quot;.</p>
<p><strong>Step-by-Step Mathematical Trace of 3 Instructions</strong>
Let the initial state at step $t=0$ be:</p>
<ul>
<li>$\mathcal{R}[\text{rbp}] = 1000$</li>
<li>$\mathcal{M}_{64}[992] = 5$  (the value at <code>-8(%rbp)</code>)</li>
<li>$\mathcal{R}[\text{rax}] = 10$</li>
</ul>
<p><strong>Step 1</strong>: <code>movq $0, -8(%rbp)</code></p>
<ul>
<li><strong>Input State</strong>: $\mathcal{M}_{64}[992] = 5$</li>
<li><strong>Transformation</strong>: Write immediate constant $0$ to address $1000 - 8 = 992$.</li>
<li><strong>Output State</strong>: $\mathcal{M}_{64}[992] = 0$.</li>
</ul>
<p><strong>Step 2</strong>: <code>addq $7, %rax</code></p>
<ul>
<li><strong>Input State</strong>: $\mathcal{R}[\text{rax}] = 10$</li>
<li><strong>Transformation</strong>: $\mathcal{R}[\text{rax}] \leftarrow \mathcal{R}[\text{rax}] + 7 = 10 + 7 = 17$.</li>
<li><strong>Output State</strong>: $\mathcal{R}[\text{rax}] = 17$.</li>
</ul>
<p><strong>Step 3</strong>: <code>subq $1, -8(%rbp)</code></p>
<ul>
<li><strong>Input State</strong>: $\mathcal{M}_{64}[992] = 0$</li>
<li><strong>Transformation:</strong> $\mathcal{M}<em>{64}[992] \leftarrow \mathcal{M}</em>{64}[992] - 1 = 0 - 1 = -1$.</li>
<li><strong>Output State</strong>: $\mathcal{M}_{64}[992] = -1$ (stored as two&#39;s complement 0xFFFFFFFFFFFFFFFF).</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSAPP Section 3.6: Control (Condition Codes, Jumps, and Loops)]]></title>
            <link>https://velog.io/@turing_machine/CSAPP-Section-3.6-Control-Condition-Codes-Jumps-and-Loops</link>
            <guid>https://velog.io/@turing_machine/CSAPP-Section-3.6-Control-Condition-Codes-Jumps-and-Loops</guid>
            <pubDate>Mon, 21 Sep 2026 06:42:01 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="how-control-flow-is-mathematically-mapped-to-hardware">How control flow is mathematically mapped to hardware?</h3>
<hr>
<ol>
<li>The Human Desire vs. The Physical Axiom</li>
</ol>
<ul>
<li><p>The Desire: Conditional branching.</p>
<ul>
<li>$$\text{If   condition } P \text{ is true, execute block } A\text{; otherwise, execute block } B.$$</li>
</ul>
</li>
<li><p>The Physical Axiom (Axiom 4 — Monotonic Execution):</p>
<ul>
<li>The Program Counter (%rip) naturally increments monotonically from one instruction to the next:</li>
</ul>
<p>$$%rip \leftarrow %rip + \text{sizeof}(\text{current_instruction})$$</p>
</li>
</ul>
<p>The CPU cannot &quot;choose&quot; a block of code directly. 
It can only do one of two things:</p>
<p>1)  Continue to the next sequential address.
2) Overwrite %rip with a target address (a Jump).</p>
<hr>
<ol start="2">
<li>The Bridge: Condition Codes ($\mathcal{C}$)</li>
</ol>
<p>To decide whether to jump, the CPU maintains a special 1-bit register collection called Condition Codes (or the EFLAGS register):</p>
<p>$$\mathcal{C} = \langle \text{CF}, \text{ZF}, \text{SF}, \text{OF} \rangle$$</p>
<p>Whenever the ALU executes an operation (like sub, add, cmp), 
these 1-bit flags are updated automatically as side effects:</p>
<table>
<thead>
<tr>
<th align="left">Flag</th>
<th align="left">Name</th>
<th align="left">Mathematical Definition</th>
<th align="left">Hardware Meaning</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>ZF</code></td>
<td align="left">Zero Flag</td>
<td align="left">$\text{Result} == 0$</td>
<td align="left">The operation produced a zero (e.g., $a - b = 0$, so $a == b$).</td>
</tr>
<tr>
<td align="left"><code>SF</code></td>
<td align="left">Sign Flag</td>
<td align="left">$\text{Result} &lt; 0$</td>
<td align="left">The most significant bit (MSB) of the result is 1 (negative).</td>
</tr>
<tr>
<td align="left"><code>OF</code></td>
<td align="left">Overflow Flag</td>
<td align="left">$(a &gt; 0, b &gt; 0, \text{Res} &lt; 0) \lor (a &lt; 0, b &lt; 0, \text{Res} &gt; 0)$</td>
<td align="left">Two&#39;s-complement signed overflow occurred.</td>
</tr>
<tr>
<td align="left"><code>CF</code></td>
<td align="left">Carry Flag</td>
<td align="left">$\text{Unsigned Overflow}$</td>
<td align="left">An unsigned addition carried out of the MSB, or a borrow occurred.</td>
</tr>
</tbody></table>
<p>Crucial Rule: leaq does not alter condition codes. Pure arithmetic (addq, subq, cmpq, testq) does.</p>
<hr>
<ol start="3">
<li>The 3-Step Machine Recipe for Any if or Loop</li>
</ol>
<p>Every conditional construct in C is compiled into this exact 3-step sequence:</p>
<pre><code class="language-text">[Step 1: Set Flags]   ───&gt;   cmpq %rsi, %rdi      (Compute %rdi - %rsi, discard result, set flags)
[Step 2: Read Flags]  ───&gt;   jg   .L_greater      (Jump if ZF=0 and SF=OF)
[Step 3: Fallthrough] ───&gt;   ...                  (Code executed if false)</code></pre>
<hr>
<h1 id="test3_6c">test3_6.c</h1>
<pre><code class="language-text">long max(long a, long b) {
    if (a &gt; b) return a;
    else return b;
}</code></pre>
<h4 id="compiled--o0">compiled -O0</h4>
<pre><code class="language-text">cat test3_6.s

        .file   &quot;test3_6.c&quot;
        .text
        .globl  max
        .type   max, @function
max:
.LFB0:
        .cfi_startproc
        endbr64
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movq    %rdi, -8(%rbp)
        movq    %rsi, -16(%rbp)
        movq    -8(%rbp), %rax
        cmpq    -16(%rbp), %rax
        jle     .L2
        movq    -8(%rbp), %rax
        jmp     .L3
.L2:
        movq    -16(%rbp), %rax
.L3:
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   max, .-max
        .ident  &quot;GCC: (Ubuntu 13.3.0-ubuntu2~24.04.1) 13.3.0&quot;
        .section        .note.GNU-tack,&quot;&quot;,@progbits
        .section        .note.gnu.property,&quot;a&quot;
        .align 8
        .long   1f - 0f
        .long   4f - 1f
        .long   5
0:
        .string &quot;GNU&quot;
1:
        .align 8
        .long   0xc0000002
        .long   3f - 2f
2:
        .long   0x3
3:
        .align 8
4: 
</code></pre>
<pre><code>                  +-----------------------------------+
                  |           Function Entry          |
                  |  pushq   %rbp                     |
                  |  movq    %rsp, %rbp               |
                  |  movq    %rdi, -8(%rbp)   (save a)|
                  |  movq    %rsi, -16(%rbp)  (save b)|
                  +-----------------------------------+
                                    |
                                    v
                  +-----------------------------------+
                  |             Condition             |
                  |  movq    -8(%rbp), %rax   (%rax=a)|
                  |  cmpq    -16(%rbp), %rax  (a - b) |
                  +-----------------------------------+
                                    |
                            jle .L2 (a &lt;= b)
                           /                 \
                 [ True ] /                   \ [ False ]
                         /                     \
                        v                       v
      +----------------------------+  +----------------------------+
      |      .L2 (Else Block)      |  |      Then-Fallthrough      |
      |  movq  -16(%rbp), %rax     |  |  movq  -8(%rbp), %rax      |
      |        (%rax = b)          |  |        (%rax = a)          |
      +----------------------------+  |  jmp   .L3                 |
                    |                 +----------------------------+
                    \                               /
                     \                             /
                      -----&gt;        .L3       &lt;----
                                     |
                                     v
                  +-----------------------------------+
                  |             Function Exit         |
                  |  popq    %rbp                     |
                  |  ret                              |
                  +-----------------------------------+</code></pre><h4 id="step-by-step-flow">Step-by-Step Flow</h4>
<pre><code class="language-text">                  [Input Registers] ──────────&gt; [%rdi = a]   [%rsi = b]
                                   │            │
                                   ▼            ▼
[Stack Memory Frame] ───────&gt; [-8(%rbp)]   [-16(%rbp)]
                                   │            │
                                   ▼            ▼
[ALU Operation] ────────────&gt; cmpq calculates: (%rax - %rsi) = (a - b)
                                   │
                                   ▼
[Flags Register EFLAGS] ────&gt; Updates ZF, SF, OF, CF
                                   │
                                   ▼
[Decision Point] ───────────&gt; Does a &lt;= b hold? ((SF ^ OF) | ZF == 1)
                              ├── YES ──&gt; Jump to .L2 ──&gt; Load b into %rax
                              └── NO  ──&gt; Fallthrough ──&gt; Load a into %rax ──&gt; Jump to .L3</code></pre>
<hr>
<h4 id="compiled--o2">compiled -O2</h4>
<pre><code class="language-test">cat test3_6_opt.s 
        .file   &quot;test3_6.c&quot;
        .text
        .p2align 4
        .globl  max
        .type   max, @function
max:
.LFB0:
        .cfi_startproc
        endbr64
        cmpq    %rsi, %rdi
        movq    %rsi, %rax
        cmovge  %rdi, %rax
        ret
        .cfi_endproc
.LFE0:
        .size   max, .-max
        .ident  &quot;GCC: (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0&quot;
        .section        .note.GNU-stack,&quot;&quot;,@progbits
        .section        .note.gnu.property,&quot;a&quot;
        .align 8
        .long   1f - 0f
        .long   4f - 1f
        .long   5
0:
        .string &quot;GNU&quot;
1:
        .align 8
        .long   0xc0000002
        .long   3f - 2f
2:
        .long   0x3
3:
        .align 8
4:</code></pre>
<pre><code>               +----------------------------------------+
               |              Function Entry            |
               |  (No stack setup, no memory writes)    |
               |  Arguments already in registers:       |
               |      %rdi = a,  %rsi = b               |
               +----------------------------------------+
                                   |
                                   v  [Monotonic Flow: %rip advances]
               +----------------------------------------+
               |             1. Comparison              |
               |  cmpq   %rsi, %rdi                     |
               |  Computes: (%rdi - %rsi) = (a - b)     |
               |  Sets flags: SF, OF, ZF in %rflags     |
               +----------------------------------------+
                                   |
                                   v  [Monotonic Flow: %rip advances]
               +----------------------------------------+
               |        2. Default Assignment           |
               |  movq   %rsi, %rax                     |
               |  State: %rax = b                       |
               +----------------------------------------+
                                   |
                                   v  [Monotonic Flow: %rip advances]
               +----------------------------------------+
               |         3. Conditional Move            |
               |  cmovge %rdi, %rax                     |
               |  Condition: (SF ^ OF) == 0 (i.e. a&gt;=b) |
               |                                        |
               |  [ a &gt;= b ]: %rax &lt;-- %rdi (value a)   |
               |  [ a &lt;  b ]: %rax unchanged (value b)  |
               +----------------------------------------+
                                   |
                                   v  [Monotonic Flow: %rip advances]
               +----------------------------------------+
               |             Function Exit              |
               |  ret (Returns value in %rax)           |
               +----------------------------------------+</code></pre><h4 id="-o2-shape-strictly-linear--monotonic-pipeline">-O2 Shape (Strictly Linear / Monotonic Pipeline):</h4>
<pre><code>[Compare] ──&gt; [Speculative Load] ──&gt; [Conditional Select] ──&gt; [Return]</code></pre><p>The instruction stream never branches. The CPU pipeline executes in a straight line without stalling.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[The 5 Physical Axioms of x86_64 Hardware]
]]></title>
            <link>https://velog.io/@turing_machine/260921Mon-The-5-Physical-Axioms-of-x8664-Hardware</link>
            <guid>https://velog.io/@turing_machine/260921Mon-The-5-Physical-Axioms-of-x8664-Hardware</guid>
            <pubDate>Mon, 21 Sep 2026 02:34:56 GMT</pubDate>
            <description><![CDATA[<p>Q1.
Has Anyone Studied the Same Way as Me?</p>
<p>A1.
Yes—in fact, every founding figure of computer science studied this way because they had no choice.</p>
<p>When <strong>Alan Turing, John von Neumann, Edsger Dijkstra, and Donald Knuth</strong> built the foundations of modern computing, high-level languages like Python, Java, or even early C did not exist. To them, programming was never &quot;memorizing coding syntax.&quot; It was the <strong>physical implementation of mathematical logic.</strong></p>
<hr>
<p>Q2.
Should We Review All Physical Axioms Beforehand?</p>
<p>A2.
Review a minimal core set of axioms first, but do not try to catalog every obscure edge case upfront.</p>
<p><strong>If you attempt to memorize all</strong> x86_64 architecture rules, micro-architectural quirks, and IEEE-754 floating-point edge cases before touching code, you will hit the same abstract *<em>cognitive overload *</em>you felt earlier.</p>
<p>The most effective strategy is:</p>
<p><strong>Establish the 5 Fundamental Axioms that govern 95% of machine execution (Registers, Memory Mapping, Condition Flags, the Call Stack, and Alignment).</strong></p>
<p>Step sequentially through Chapter 3, <strong>pulling in any specialized lemma only when that specific chapter section demands it.</strong></p>
<hr>
<h2 id="the-5-physical-axioms-of-x86_64-hardware">[The 5 Physical Axioms of x86_64 Hardware]</h2>
<hr>
<p>Axiom 1: The Principle of Flat, Byte-Addressable Memory</p>
<p>The Rule: 
The system memory is modeled as an array of 8-bit bytes:
$$\mathcal{M}: [0, 2^{64}-1] \to {0, 1}^8$$</p>
<p>The Hardware Constraint: 
Memory itself has no types, no variable names, and no boundaries. It only knows numerical addresses and raw bit patterns. A pointer is simply an integer in $\mathbb{Z}_{2^{64}}$ used as an index into this massive array.</p>
<hr>
<p>Axiom 2: The Virtual Memory Page Boundary</p>
<p>The Rule: The hardware Memory Management Unit (MMU) does not manage individual bytes; it manages chunks called pages (standard size: $4096 \text{ bytes} = 4 \text{ KB}$).</p>
<p>The Hardware Constraint: Every page has a permission bitmask:
$$\text{Permissions} \in \mathcal{P}({ \text{Read}, \text{Write}, \text{Execute} })$$</p>
<p>The Zero Page Invariant: The page spanning addresses $[0, 4095]$ is explicitly unmapped ($\text{Permissions} = \emptyset$). Any instruction attempting to read, write, or execute within this set triggers an immediate CPU hardware interrupt ($\text{Page Fault} \implies \text{SIGSEGV}$).</p>
<hr>
<p>Axiom 3: The Register Set as Discrete State Storage</p>
<p>The Rule: Registers are tiny, ultra-fast storage cells located directly inside the CPU core. In x86_64, there are 16 general-purpose 64-bit integer registers:</p>
<p>$$\mathcal{R} = { \text{RAX}, \text{RBX}, \text{RCX}, \text{RDX}, \text{RSI}, \text{RDI}, \text{RBP}, \text{RSP}, \text{R8} \dots \text{R15} }$$</p>
<p>The Hardware Constraint: Arithmetic and logical operations cannot operate directly between two arbitrary memory addresses. Data must be loaded from $\mathcal{M}$ into $\mathcal{R}$, computed inside the ALU, and written back to $\mathcal{M}$.</p>
<hr>
<p>Axiom 4: Monotonic Execution &amp; The Flag Vector</p>
<p>The Rule: The Instruction Pointer ($\text{RIP}$) advances monotonically through code:</p>
<p>$$\text{RIP}_{t+1} \leftarrow \text{RIP}_t + \text{sizeof}(\text{Current Instruction})$$</p>
<p>The Hardware Constraint: The CPU can only alter this linear vector if an instruction conditionally or unconditionally modifies $\text{RIP}$. Conditional jumps read a dedicated 1-bit status register—the Condition Codes:</p>
<p>$$\mathcal{C} = \langle \text{ZF (Zero)}, , \text{SF (Sign)}, , \text{CF (Carry)}, , \text{OF (Overflow)} \rangle$$</p>
<hr>
<p>Axiom 5: The LIFO Stack FrontierThe Rule: The register $\text{RSP}$ (Stack Pointer) holds the memory address of the top of the runtime call stack.</p>
<p>The Hardware Constraint: The stack grows downward (toward lower numerical memory addresses)</p>
<p>$\text{push } X \implies \text{RSP} \leftarrow \text{RSP} - 8; \quad \mathcal{M}[\text{RSP}] \leftarrow X$</p>
<p>$\text{pop } X \implies X \leftarrow \mathcal{M}[\text{RSP}]; \quad \text{RSP} \leftarrow \text{RSP} + 8$</p>
<p>$\text{call } T \implies \text{push } \text{RIP}_{\text{next}}; \quad \text{RIP} \leftarrow T$</p>
<p>$\text{ret} \implies \text{pop } \text{RIP}$</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260920(Sun)]]></title>
            <link>https://velog.io/@turing_machine/260920Sun</link>
            <guid>https://velog.io/@turing_machine/260920Sun</guid>
            <pubDate>Mon, 21 Sep 2026 02:10:41 GMT</pubDate>
            <description><![CDATA[<p>How a data structure should behave, who owns which pointer, what conditions must be met, etc </p>
<p>Comparing what the code intended with the result observed by the runtime tool is </p>
<p>This is the fastest way to identify logical contradictions.</p>
<hr>
<pre><code class="language-text">[Your Desktop (Host Machine)]
   VS Code UI (The &quot;Head&quot;)
         │
         │ talks to localhost (127.0.0.1) on Port 44693
         ▼
[Port Forwarding Bridge]
         │
         ▼
[Inside Docker Container]
   `vscode-server` (Headless Server / Node.js)
   Reads files, runs bash, drives GCC / GDB / Valgrind</code></pre>
<p>There are newer editors exploring the native route—such as Zed (written entirely in Rust)
—which achieve lower memory footprints and faster startup times. 
But for an orchestration server handling basic text and process spawning, 
Node.js stays well within the &quot;millisecond&quot; thresholds needed for human typing.</p>
<hr>
<p>building the system that manages the raw heap bytes.</p>
<hr>
<pre><code class="language-text">gcc  -O0  -g  -fsanitize=address,undefined  -fno-omit-frame-pointer  bug.c  -o bug_san
 │    │    │              │                          │                │        │
 │    │    │              │                          │                │        └─ Output binary name
 │    │    │              │                          │                └─ Input source file
 │    │    │              │                          └─ Keep stack anchors (clean backtraces)
 │    │    │              └─ Inject ASan &amp; UBSan runtime checks
 │    │    └─ Add source file/line debug symbols
 │    └─ Disable optimization (keep code literal for debugging)
 └─ The compiler (GNU Compiler Collection)</code></pre>
<hr>
<p>Every stack frame saves the address of the previous function&#39;s RBP right at the base of its own frame. 
This creates an exact singly-linked list embedded directly in the stack.</p>
<hr>
<pre><code class="language-text">Room 0x000000000000  ───┐
Room 0x000000000001     │  &quot;The Zero Page&quot;
...                     │  (Rooms 0 to 4095) (4KB block)
Room 0x000000000FFF  ───┘  [LOCKED by the OS with a giant padlock]
------------------------------------------------------------------
Room 0x000000001000  ───┐
...                     │  Normal Usable Memory
Room 0x7FFFFFFFFFFF  ───┘  (Your variables, strings, buffers)</code></pre>
<hr>
<p>An LLM is a model that predicts the next token. Just give good guidance from the start.
If you provide high-quality prompts and instructions from the start,
It works cleverly by detecting patterns in far smarter areas.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[260919(Sat)]]></title>
            <link>https://velog.io/@turing_machine/260919Sat</link>
            <guid>https://velog.io/@turing_machine/260919Sat</guid>
            <pubDate>Sat, 19 Sep 2026 08:43:05 GMT</pubDate>
            <description><![CDATA[<p>Q1. 
Is the essence of every computer bug a logical contradiction?</p>
<p>A1.
Yes, at an abstract level, every software bug is a mismatch between two formal specifications:</p>
<p>The Intended Logic (what the programmer believed or designed the system to do).</p>
<p>The Actual Logic (what the exact syntax, hardware architecture, and execution environment strictly mandate).</p>
<p>Computers are deterministic state machines; they never make &quot;mistakes&quot; or behave randomly on their own. They follow their instructions faithfully.</p>
<p>(The only edge cases that sit outside pure logical contradiction are physical hardware defects—like cosmic ray bit-flips, overheating circuits, or electrical faults—where physical reality violates the machine&#39;s underlying physical assumptions.)</p>
<p>Q2.
Is a computer a deterministic state machine?
Then why can Undefined Behavior occur randomly(?) in C language?</p>
<p>A2.
A physical computer is indeed a deterministic state machine (barring hardware faults or external noise), but in C, Undefined Behavior (UB) exists at the level of the programming language specification, not the silicon.</p>
<p>The reason UB appears &quot;random&quot; or erratic comes down to a fundamental clash between 3 layers: </p>
<p>1) the C Language Standard, 2) the Optimizing Compiler, and 3) the Physical Hardware.</p>
<hr>
<p>Summary: Pseudo-Randomness vs. Strict Determinism</p>
<p>1) The CPU is deterministic: </p>
<p>Given the exact same machine code, register values, memory contents, cache states, and OS scheduling, it will produce the exact same result every microsecond.</p>
<p>2) Undefined Behavior breaks the translation: </p>
<p>the optimizer operates under an absolute axiom: &quot;The programmer never wrote Undefined Behavior.&quot;
Because the compiler assumes UB is impossible, if a branch of code can only be reached via UB, the compiler concludes that branch is dead code and eliminates it entirely.</p>
<p>3) The environment provides invisible inputs: </p>
<p>Memory layout, compiler optimization levels (-O0 vs -O3), compiler versions, stack residue from prior calls, and OS memory paging all act as hidden inputs.</p>
<hr>
<p>To the programmer looking only at the C text, 
it looks like magic or bad luck (&quot;it worked on my machine!&quot;). 
In reality, the machine is executing a deterministic path through an unintended, corrupted state.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260918(Fri)]]></title>
            <link>https://velog.io/@turing_machine/260918%EA%B8%88</link>
            <guid>https://velog.io/@turing_machine/260918%EA%B8%88</guid>
            <pubDate>Fri, 18 Sep 2026 01:37:42 GMT</pubDate>
            <description><![CDATA[<p>Q1.
Can all causes be explained by {ownership, lifespan, and boundary immutability}? It seems similar to a concept explicitly stated in Rust.</p>
<p>A1.
Nearly all memory safety bugs boil down to spatial or temporal violations, which map directly to the core principles Rust formalizes at compile time:</p>
<ul>
<li><p>Spatial Safety (Bounds / Invariants): Accessing memory outside the allocated boundary. Examples: buffer overflows, off-by-one errors, and out-of-bounds array indexing.</p>
</li>
<li><p>Temporal Safety (Lifespan): Accessing or freeing memory at the wrong point in time. Examples: Use-After-Free (UAF), double-free, and stack-use-after-return.</p>
</li>
<li><p>Protocol &amp; Invariants (Ownership): Who is responsible for allocating, modifying, and deallocating a resource? When ownership is ambiguous, two subsystems assume the other is managing the memory, leading to aliasing bugs, leaks, or premature destruction.</p>
</li>
</ul>
<p>The only additions at the systems level that don&#39;t fit neatly into these three are hardware/architecture invariants:</p>
<ul>
<li><p>Alignment faults: Trying to read a 64-bit integer from an unaligned address on architectures that enforce strict alignment (raises SIGBUS).</p>
</li>
<li><p>Concurrency races: Multiple threads accessing the same memory without synchronization (data races).</p>
</li>
<li><p>Type punning/aliasing rule violations: Violating strict aliasing rules, causing the compiler optimizer to emit incorrect machine code.</p>
</li>
</ul>
<hr>
<p>Q2. 
GDB vs. Valgrind vs. Sanitizers: When to Use What
In VSCode, you can measure data movement at the assembly level in the memory and register areas on the right side, but it&#39;s important to figure out how to use that and to build a mental model for understanding the program at the OS/memory level, right?</p>
<p>A2.
it is standard industry practice. Each tool operates at a different point in the debugging lifecycle:</p>
<ul>
<li><p>GDB</p>
<ul>
<li>Execution control &amp; state inspection (ptrace)    </li>
<li>Post-mortem inspection (bt), live stepping, inspecting registers/memory at a exact crash point.    </li>
<li>Does not automatically notify you when memory is corrupted before the crash happens.</li>
</ul>
</li>
<li><p>AddressSanitizer (ASan)</p>
<ul>
<li>Compiler instrumentation (-fsanitize=address)</li>
<li>Real-time crash interception at the exact line of bad write/read (near-zero runtime overhead).</li>
<li>Requires recompilation; not available in locked-down production binaries.</li>
</ul>
</li>
<li><p>Valgrind (Memcheck)</p>
<ul>
<li>Dynamic binary translation (simulated CPU)</li>
<li>Tracking memory leaks, uninitialized memory reads (Use of uninitialised value).</li>
<li>Slows execution down by 10x–30x; cannot catch global variable overflows easily.</li>
</ul>
</li>
</ul>
<p>The Workflow: 
Run tests under ASan/Valgrind to detect invisible corruptions early; 
drop into GDB when you need to step through logic or inspect raw registers and stack frames at the moment of failure.  </p>
<p>[Registers, Memory, and the OS Mental Model]
Watching registers in VS Code is useful only if you understand what the hardware expects. 
To build a clean mental model, keep these abstractions separated:</p>
<ul>
<li><p>Virtual Address Space: Every process lives in an illusion of 64-bit addresses managed by the OS page table:</p>
<ul>
<li>Text (Code): Read-only instructions.</li>
<li>Data / BSS: Global and static variables.</li>
<li>Heap: Dynamic allocations (malloc), growing upwards.</li>
<li>Stack: Function stack frames, local variables, return addresses, growing downwards.</li>
</ul>
</li>
<li><p>The Register Role Model (x86-64 System V ABI):</p>
<ul>
<li>RSP: Points to the top of the active stack frame.</li>
<li>RBP: Base frame pointer (when -fno-omit-frame-pointer is active).</li>
<li>RIP: The instruction pointer (the next instruction to execute).</li>
<li>RAX: Function return value.</li>
<li>RDI, RSI, RDX, RCX, R8, R9: Function call arguments (1 through 6).</li>
</ul>
<p>When you see SIGSEGV, it usually means 
RIP tried to read or write an address not mapped in the page table, or tried to write to a read-only page. When you see SIGABRT with stack smashing, GCC&#39;s stack canary detected that a local buffer wrote past its bounds and clobbered the saved frame pointer or return address.</p>
</li>
</ul>
<hr>
<p>Q3.
When a bug occurs in a complex manner and sequentially from each of its causes, is the order of solving the problem important?</p>
<p>A3.
Yes. Always solve the earliest cause in the execution timeline.</p>
<p>In C, memory bugs create latent undefined behavior. A corruption that occurs in Step A might not crash the program until Step D:</p>
<ul>
<li>Step A writes 1 byte past an array.</li>
<li>Step B allocates a new chunk from malloc.</li>
<li>Step C modifies another variable.</li>
<li>Step D crashes inside free() because malloc metadata was altered in Step A.</li>
</ul>
<p>If you try to fix Step D, you are treating a symptom of a poisoned heap. 
Rule of thumb: 
Trace backwards to the earliest invalid read/write operation. Once the first violation is eliminated, secondary crashes frequently disappear.</p>
<hr>
<p>Q4.
I only know a little C language syntax, have almost no debugging experience, and my understanding of the OS/memory concepts is weak. What mindset and tools are needed to understand mechanisms accurately at a professional level?</p>
<p>A4.
Transitioning from knowing syntax to understanding system mechanisms requires treating the machine as deterministic:</p>
<ul>
<li><p>Mindset: &quot;Memory is just a byte array with rules.&quot; Everything—code, pointers, integers, data structures—is simply bytes stored at a specific address. A pointer is not magic; it is an unsigned 64-bit integer whose value happens to be an address in virtual memory.</p>
</li>
<li><p>Distinguish Syntax from Semantics:</p>
<ul>
<li>int *p; allocates 8 bytes on the stack for a pointer. It allocates zero bytes for an integer until initialized.</li>
<li>char arr[10]; allocates 10 bytes on the stack.</li>
</ul>
</li>
<li><p>Verify, Don&#39;t Guess: Whenever code crashes, form an explicit hypothesis: &quot;Variable buf has size 8, but memcpy writes 16 bytes, overwriting the adjacent pointer.&quot; Use GDB to confirm the addresses before changing a single line of code.</p>
</li>
<li><p>Inspect Memory Directly:</p>
<ul>
<li>In GDB, use x/gx <ptr> (examine 8 bytes hex) and p &amp;variable (address of variable).</li>
<li>Check boundaries: p (void<em>)ptr - (void</em>)base.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[260917(목)]]></title>
            <link>https://velog.io/@turing_machine/260917%EB%AA%A9</link>
            <guid>https://velog.io/@turing_machine/260917%EB%AA%A9</guid>
            <pubDate>Thu, 17 Sep 2026 13:34:48 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-text">================================================================================
                   1. THE TWO WORLDS: USER SPACE vs. KERNEL
================================================================================

 [ USER SPACE (Your Program) ]                   [ KERNEL SPACE (OS &amp; Hardware) ]
 ─────────────────────────────                   ────────────────────────────────

  Stack Frame (tail_file)                         Kernel RAM (Open File Table)
  ┌─────────────────────────┐                     ┌─────────────────────────────┐
  │ file: File              │                     │ struct file {               │
  │   └── fd: 3 ────────────┼─── (System Call) ──►│   f_pos: 10000 (cursor)     │
  │                         │                     │   inode: -&gt; disk metadata   │
  │ total_len: 10000        │                     │ };                          │
  │ remaining: 10000        │                     └──────────────┬──────────────┘
  │                         │                                    │
  │ buffer: [0u8; 8192]     │                                    │
  │  [   8 KB in RAM   ]    │◄────── (Loads Data via DMA) ───────┤
  └─────────────────────────┘                                    ▼
                                                          [ PHYSICAL SSD / DISK ]
                                                          [0 . . . . . . . 10000]

================================================================================
           2. STEPPING BACKWARD: FROM DISK OFFSETS TO RAM BUFFER
================================================================================

 Suppose total file length = 10,000 bytes, Chunk Size = 8,192 bytes (8 KB)

 [ THE FILE ON DISK ]
 Byte 0                                 Byte 1808                     Byte 10000 (EOF)
   ├────────────────────────────────────────┼────────────────────────────────┤
   │          Remaining (1,808 B)           │    Target Chunk to Read (8 KB) │
   │          (Saved for Round 2)           │       [ chunk_to_read = 8192 ] │
   └────────────────────────────────────────┴────────────────────────────────┘
                                            ▲
                                            │
                                      read_offset
                                  (10000 - 8192 = 1808)


 ACTION A: file.seek(SeekFrom::Start(1808))
   The OS moves its internal cursor (`f_pos`) to byte 1808.
   No text or data is copied into your program yet.


 ACTION B: file.read_exact(&amp;mut buffer[..8192])
   The OS reads forward from 1808 to 10000 and fills your stack buffer in RAM.

                 ┌─────────────────── DISK (1808..10000) ──────────────────┐
                 │ &quot;log line 98\n log line 99\n log line 100\n&quot;            │
                 └────────────────────────────┬────────────────────────────┘
                                              │
                                     (Loaded into RAM)
                                              │
                                              ▼
 [ YOUR BUFFER IN RAM ]
 Index 0                                                           Index 8191
   ┌────────────────────────────────────────────────────────────────────────┐
   │ [0] │ [1] │ [2] │  . . . . . . . . . . . . . . . . . . . . . .  │[8191]│
   └────────────────────────────────────────────────────────────────────────┘
   ◄──────────────────────── ITERATE BACKWARD (.rev()) ─────────────────────
               Scan in RAM for newline characters (&#39;\n&#39;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[4주차) 연결 리스트 6번 문제]]></title>
            <link>https://velog.io/@turing_machine/4%EC%A3%BC%EC%B0%A8-%EC%97%B0%EA%B2%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8-6%EB%B2%88-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@turing_machine/4%EC%A3%BC%EC%B0%A8-%EC%97%B0%EA%B2%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8-6%EB%B2%88-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 17 Sep 2026 03:44:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/turing_machine/post/f22ed594-10da-44c0-9d7e-cad05c4c81a0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/turing_machine/post/507ac953-b4e0-439f-aaf4-f6bf1d62814c/image.png" alt=""></p>
<p>int main() 과 int moveMaxToFront(ListNode **ptrHead) 를 설명할테니까 제대로 이해했는지 판별해봐.</p>
<p>실행파일을 실행하면, int main() 함수부터 읽기 시작해.
인수를 아무것도 전달하지 않고 함수가 stack 에 쌓여.
main() 프레임에서 지역변수 int c, i, j 가 sizeof(int) 만큼 할당돼. 값은 기존에 메모리에 남아있던 쓰레기 값이 그대로 있어.
단, c의 경우, 값이 1이 저장돼.
그리고 ll 구조체가 선언되니까 16바이트만큼 stack 에 할당돼.
그 16바이트 중에서,
<strong>ll 의 head 필드는 8바이트 내부에 NULL 값(0x0)으로, 
ll 의 size 필드는 4바이트 내부에 0 값(0x0 과 구분되지 않음. 비트 레벨에서는 동일함.)으로 저장돼.</strong></p>
<hr>
<p><em>*#define NULL ((void</em>)0)
**매크로 상수 NULL 은 메모리 주소 0을 나타내는 상수다.</p>
<p><em>*return;            에서 return type 은 void
return NULL;     에서 return type 은 void\</em> (왜냐하면 NULL 은 값이 0x0 이고, 타입이 void*(메모리 주소)이기 때문.)</p>
<p><strong>(구체적으로, NULL은 값이 0x0000000000000000. 여기서 0은 16개. 왜냐하면 64비트 CPU 에서 포인터의 크기는 64비트, 2^64 = 16^16)**</strong></p>
<hr>
<p>c = 1 이니까 c != 0 이므로, while문의 첫 반복이 시작돼.
사용자가 20, 50, 10, 30 순으로 insert 했다고 가정할게.
그리고 2 을 입력했어.  그러면 case 2 와 매치되므로, 
moveMaxToFront(&amp;(ll.head)); 가 실행돼. 그래서 함수로 진입하지.</p>
<p>moveMaxToFront(&amp;(ll.head)) 가 stack 에서 main() 위에 쌓여.
moveMaxToFront(&amp;(ll.head)) 프레임에서 지역변수로 4개의 포인터가 자리를 차지해(아직 값은 없어).</p>
<ol>
<li>노드가 없거나 1개뿐인 경우는 아니므로, return 0; 을 무시하고, </li>
<li>초기화를 해.
maxNode 포인터 변수에 &amp;20 가 할당돼. 1번째 노드를 최댓값으로 가정한 셈.
maxPre 포인터 변수에 NULL 이 할당돼. 1번째 노드의 앞에는 아무것도 없어.</li>
</ol>
<p>pre 포인터 변수에 &amp;20 가 할당돼.
cur 포인터 변수에 &amp;50 가 할당돼. 
pre와 cur가 각각 1칸씩 이동한 셈.</p>
<p>cur 의 값이 &amp;50 이니까 while 문 내부가 실행돼.
50 &gt; 20 이므로, if문 내부가 실행돼.
maxNode 포인터 변수에 &amp;50 가 할당돼.
maxPre 포인터 변수에 &amp;20 가 할당돼.</p>
<p>그리고 
pre 포인터 변수에 &amp;50 가 할당돼.
cur 포인터 변수에 &amp;10 가 할당돼.
pre와 cur가 각각 1칸씩 이동한 셈.</p>
<p>cur 의 값은 &amp;10 이니까, NULL 이 아니므로, while 문 내부가 실행돼.
10 &gt; 50 은 False 이므로, if문 내부가 실행 안돼.</p>
<p>그리고 
pre 포인터 변수에 &amp;10 가 할당돼.
cur 포인터 변수에 &amp;30 가 할당돼.
pre와 cur가 각각 1칸씩 이동한 셈.</p>
<p>cur 의 값이 &amp;30 이니까 while문 내부가 실행돼.
30 &gt; 50 은 False 이므로, if 문 내부가 실행 안돼.</p>
<p>그리고
pre 포인터 변수에 &amp;30 가 할당돼.
cur 포인터 변수에 NULL 가 할당돼.</p>
<p>cur 의 값은 NULL 이니까, while 문의 조건은 False 이고, while 문 내부가 실행 안돼.</p>
<p>maxPre 값은 NULL 가 아니므로, if문 내부가 실행 안돼.</p>
<p>&amp;20 다음에 &amp;10을 연결해: [20 -&gt; 10 -&gt; 30]
&amp;50 다음에 &amp;20을 연결해: [50 -&gt; 20 -&gt; 10 -&gt; 30]
*ptrHead 변수(ll.head)에 &amp;50 을 할당해.</p>
<p>return 0 이 되면서, moveMaxToFront() 함수를 빠져나가고, 
stack 에서 해당 함수의 스택 프레임이 제거되고, 
다시 main() 스택 프레임으로 진입해.</p>
<p>printf 함수가 실행되고,
printList(&amp;ll) 가 실행되고, 
removeAllItems(&amp;ll) 가 실행되고,
break; 로 switch문을 빠져나가면서,
while문의 조건식인 c != 0 을 판별해. 
현재 c = 2 이므로 True 라서 while문 내부로 들어가고,
printf() 가 실행되고, scanf() 가 실행되고,
사용자가 0 을 입력했다고 가정하면, 변수 c 에 0 값이 대입되고,
swich문에서 case 0 에 match 가 되면서, removeAllItems(&amp;ll); 가 실행되고, break 로 switch문을 탈출하고,
while문의 조건식인 c != 0 가 False 가 되면서 while문을 탈출하고,
main() 본문의 다음줄인 return 0; 가 실행되고, main() 함수를 탈출하면서,
스택 프레임에서 main() 함수가 제거되고, 끝나.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260916(수)]]></title>
            <link>https://velog.io/@turing_machine/260916%EC%88%98</link>
            <guid>https://velog.io/@turing_machine/260916%EC%88%98</guid>
            <pubDate>Wed, 16 Sep 2026 13:58:24 GMT</pubDate>
            <description><![CDATA[<p><strong>Weekly I Learned: Systems Programming Through C (Under the Hood)</strong></p>
<hr>
<h3 id="1-the-kernel-file-descriptors-and-the-open-file-table">1. The Kernel, File Descriptors, and the Open File Table</h3>
<p>In C and POSIX systems, I/O operations do not operate on high-level objects or automatic streams; they rely directly on integer handles and kernel data structures.</p>
<ul>
<li><strong>File Descriptors as Array Indices:</strong>
A file descriptor (FD) is simply an integer index into a process&#39;s per-process file descriptor table.</li>
<li>Standard descriptors are fixed by convention: 0 (stdin), 1 (stdout), and 2 (stderr).</li>
<li>When opening a custom file via open(), the OS assigns the lowest available integer (typically 3).</li>
</ul>
<ul>
<li><strong>Kernel File Table vs. Process Table:</strong>
While the file descriptor integer remains constant throughout the file&#39;s lifecycle, the kernel maintains an underlying struct file entry. This struct tracks access modes, pointers to the filesystem inode, and critically, the file offset (f_pos).</li>
</ul>
<pre><code class="language-text">Process Space                         Kernel Space
┌────────────────────────┐            ┌───────────────────────────────┐
│ FD Table               │            │ Open File Table Entry         │
│  [0] -&gt; stdin          │            │  struct file {                │
│  [1] -&gt; stdout         │            │      uint64_t f_pos; (Offset) │
│  [2] -&gt; stderr         │            │      struct inode *f_inode;   │
│  [3] -&gt; file.txt       │ ─────────► │  };                           │
└────────────────────────┘            └───────────────────────────────┘
</code></pre>
<hr>
<h3 id="2-direct-offset-manipulation-with-lseek">2. Direct Offset Manipulation with lseek</h3>
<p>Reading sequentially with read() advances f_pos implicitly. However, jumping to specific disk locations requires direct repositioning via the lseek() system call:</p>
<pre><code class="language-c">off_t lseek(int fd, off_t offset, int whence);
</code></pre>
<ul>
<li><strong>The Mechanics of whence:</strong></li>
<li>SEEK_SET: Absolute byte offset from the start of the file (0 to file size). Requires non-negative values.</li>
<li>SEEK_END: Signed offset relative to the End-of-File (EOF). Passing negative numbers moves backward from the end (e.g., lseek(fd, -1024, SEEK_END)). Passing 0 positions the cursor at EOF and returns the total byte length of the file in O(1) time without reading the entire dataset.</li>
<li>SEEK_CUR: Signed offset relative to the current cursor position.</li>
</ul>
<ul>
<li><strong>Why This Matters for Utilities like tail:</strong>
Rather than reading a multi-gigabyte file sequentially from byte 0 to read the trailing lines, lseek repositions the kernel&#39;s internal offset directly to the end of the file, completely bypassing unnecessary disk I/O.</li>
</ul>
<hr>
<h3 id="3-buffering-system-calls-and-memory-traversal">3. Buffering, System Calls, and Memory Traversal</h3>
<p>At the hardware and OS interface, reading and memory access have fundamentally different constraints:</p>
<ul>
<li><strong>Forward Kernel Reads:</strong>
The read(fd, buf, count) system call reads sequentially forward from storage into a memory buffer. It does not natively read backward.</li>
<li><strong>Backward Analysis via Pointer Arithmetic:</strong>
To implement reverse line scanning (as in tail), the data must be read forward in fixed-size chunks (e.g., 8 KB blocks) into a memory buffer (stack array or heap), after which the program walks the buffer in reverse using pointer arithmetic or reverse indexing:
Target Byte = Base Address + Index
Accessing elements in this memory buffer runs in O(1) time, keeping processing speeds bound to the CPU cache and memory bus rather than disk latency.</li>
</ul>
<hr>
<h3 id="4-edge-cases-guardrails-and-newline-semantics">4. Edge Cases: Guardrails and Newline Semantics</h3>
<p>Writing robust C-level file scanners requires accounting for low-level edge cases:</p>
<ul>
<li><strong>Underflow and Clamping:</strong>
When seeking backward in chunks (offset = current - chunk_size), files smaller than the chunk size will result in negative numbers. Seeking to negative offsets yields an EINVAL (Invalid Argument) error. The jump must be clamped:
Seek Target = max(0, Length - Chunk Size)</li>
<li><strong>The Trailing Delimiter Trap:</strong>
Text files commonly end with a trailing newline terminator (\n). A reverse byte scanner must recognize and skip this final character; otherwise, it will treat the empty space between the trailing \n and EOF as a distinct, empty line.</li>
<li><strong>EOF Detection via read():</strong>
The read() system call returns the number of bytes read. It does not throw an exception on EOF; it returns 0. A loop reading until completion must strictly check for bytes_read &lt;= 0 as its termination condition.</li>
</ul>
<hr>
<h3 id="key-takeaway">Key Takeaway</h3>
<p>Programming at this level makes it clear that files are treated simply as contiguous byte arrays on storage. Managing them efficiently requires coordinating kernel-level system calls (open, read, lseek, close), understanding how pointers and arrays map across stack and heap memory, and applying explicit boundary clamping to avoid underflows and invalid syscall arguments.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260908(화)]]></title>
            <link>https://velog.io/@turing_machine/260908%ED%99%94</link>
            <guid>https://velog.io/@turing_machine/260908%ED%99%94</guid>
            <pubDate>Tue, 08 Sep 2026 08:43:09 GMT</pubDate>
            <description><![CDATA[<p>Docker 가 모든 요소를 컨테이너 단위로 표준화했음에도 불구하고,
&quot;내 컴퓨터에서는 잘 되는데&quot; 를 다른 컴퓨터에서 재현하지 못하는 경우도 있어?</p>
<ul>
<li><p>하드웨어 아키텍처 불일치:
최신 Mac(Apple Silicon(ARM64))에서 도커 컨테이너를 빌드한 후, 
Intel(x86_64) 칩이 탑재된 구형 클라우드 서버에서 실행하려고 할 때,
컨테이너 내부의 컴파일된 바이너리가 제대로 크로스 컴파일되지 않은 경우, 충돌할 수 있다.</p>
</li>
<li><p>커널 의존적 코드:
컨테이너는 호스트 머신의 리눅스 커널을 공유한다.
코드가, 호스트의 구형 OS에서 지원하지 않는, 최신 커널 기능에 의존하는 경우, 오류가 발생한다.</p>
</li>
</ul>
<hr>
<p>도커 컨테이너는 리눅스 커널 위에서 실행된다. 리눅스 커널에 내장되어 있는 기능을 이용한다.</p>
<ul>
<li>네임스페이스 기능 (프로세스, 네트워크, 파일 시스템 등을 격리)</li>
<li>컨트롤 그룹 기능 (Cgroups, CPU/Memory 사용량을 제한)</li>
</ul>
<p>윈도우에는 Windows NT 커널이, 맥OS에는 XNU 커널이 있다.
근데 어떻게 Docker를 실행할 수 있을까?
백그라운드에서 리눅스 VM 을 가동하기 때문이다.</p>
<ul>
<li>윈도우의 경우: Hyper-V 또는 WSL 2</li>
<li>맥OS의 경우: 하이퍼바이저 프레임워크</li>
</ul>
<p>맥에서 Docker 컨테이너를 실행할 때, 백그라운드의 리눅스 VM 안에서 실행된다.
컨테이너는 맥OS와 직접 통신하지 않는다.</p>
<p>&quot;리눅스가 아닌&quot; 컨테이너도 있다. 윈도우 컨테이너.
Windows Server 에서 Docker 를 실행하는 경우, 윈도우 컨테이너를 생성할 수 있다.
그 컨테이너는 Windows 커널을 공유하며 네이티브 Windows APP(ex. .NET Framework, IIS 웹 서버)를 실행한다.</p>
<p>하지만 리눅스 호스트에서 윈도우 컨테이너를 실행할 수는 없다.</p>
<p>로컬 docker 캐시</p>
<hr>
<p>현실은 카오스 세계인데, CPU cache 에서 temporal/spatial locality 경향성이 발견된다는 게 신기하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260906(일)]]></title>
            <link>https://velog.io/@turing_machine/260906%EC%9D%BC</link>
            <guid>https://velog.io/@turing_machine/260906%EC%9D%BC</guid>
            <pubDate>Sun, 06 Sep 2026 09:24:27 GMT</pubDate>
            <description><![CDATA[<p>불쾌함</p>
<p>세상에 당연한 게 있는가? 언뜻 결과가 당연해 보여도, 그 과정은 당연하지 않다.
논리적인 과정이 있으며, 사유의 기본인 논리 3법칙(동일률, 모순율, 배중률)마저 철학자에게는 치열한 의심의 대상이다.
물리법칙도 당연하지 않다. 물리법칙은 세계 그 자체(물자체)를 인간의 사유로 추상화해낸 결과다.
알고리즘의 결과는 과연 당연한가? 그 결과를 하드웨어 작동 관점에서 설명하지 못하면, 당연하다고 말하는 건 오류다.</p>
<p>현대수학에 관한 책을 쓰며 사람들과 치열하게 토론했던 과거의 경험이 갑자기 소중하게 느껴진다.
수학자가 최초에 그러했듯, &quot;무한이라는 거대한 대상을 유한의 문제로 어떻게 끌어내릴 것인가&quot; 라는 질문을 받았을 때,
나는 엄청난 짜증과 불쾌함을 느꼈다. &#39;순수수학으로 먹고 살 것도 아닌데 왜 그런 골치 아픈 고민까지 해야 하지?&#39; 하던 반발심이었다.
하지만 그 시절 고뇌했던 경험이 지금은 새삼 소중하다. 무엇에도 얽매이지 않고 끝까지 파고드는 자유로운 감성이기 때문이다.</p>
<p>불쾌함을 솔직하게 드러내고 단순한 요소로 끝까지 파고들었을 때 비로소 이해했다고 말할 수 있다고 생각한다. </p>
<p>나도 알고 있다. 이 감성을 유지하기 위해서는 상당한 에너지가 들며, 일상생활에서 마찰이 굉장히 커진다는 것을. 
하지만 기본 원리에 대한 이해에 이르기 위해서 불가피하다고 생각한다. 자연과 세상은 인내력이 많은 관찰자에게만 놀라운 모습을 보여준다고 믿는다. </p>
<hr>
<p>기계어는 전기 전압 상태를 나타내는, 0과 1로 구성된 원시 이진 비트 스트림 이다.
CPU 는 고정 배선된 logic gate (트랜지스터로 구성된 AND/OR/NOT 게이트) 를 통해 전류를 전달받을 뿐이다.
트랜지스터는 숫자를 이해하지 못한다. 오직 전류만 이해한다.</p>
<ul>
<li>고전압 (ex. 5V) 이 회로를 흐르면, 1로 해석한다.</li>
<li>저전압/무전압 (ex. 0V) 은, 0으로 해석한다.</li>
</ul>
<p>에뮬레이터는 전기로 on/off 되는 물리적 스위치의 동작을 모방한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260904(금)]]></title>
            <link>https://velog.io/@turing_machine/260904%EA%B8%88</link>
            <guid>https://velog.io/@turing_machine/260904%EA%B8%88</guid>
            <pubDate>Fri, 04 Sep 2026 00:54:00 GMT</pubDate>
            <description><![CDATA[<p>허준이 교수 의견</p>
<p>때로는 제가 다른 사람들의 생각이 잠시 머물다 가는 그릇 같다는 생각을 한다. 생각이 이 그릇에서 저 그릇으로 옮겨 다니며 점차 풍성해지는 것이 신기하다. 마음이 맑은 날에는 제가 거대한 구조의 아주 작은 일부라는 것이 잘 느껴진다. 공동 연구가 훨씬 더 멀리 갈 수 있고 훨씬 더 깊이 갈 수 있다.</p>
<p>나는 문제가 안 풀리면 포기한다. 일종의 직관인데 ‘내가 이걸 조금 노력하면 몇 달 안에 풀겠다, 아니다’처럼 판단이 필요하다. 잘 포기하는 것도 굉장히 중요한 재능이라고 생각한다. 어떤 종류의 문제들은 개인이 이해할 준비가 안 됐거나 인류가 이해할 준비가 안 된 것일 수도 있다. 그걸 붙잡고 있는 것은 생산적이지 않다. 문제를 해결하고 풀어내는 것은 사실 우연이라고 생각한다. 지난주에는 전혀 이해하지 못하고 해법을 상상할 수 없었는데 오늘 갑자기 생각이 나는 경우가 있다</p>
<p>수학의 매력은 자유로움이다. 수학엔 논리가 맞아야 한다는 규칙이 있다. 그런데 그 규칙의 엄격함 때문에 다른 면에서 자유롭다. 어떤 대상을 연구할 것인지, 어떻게 이해하고 풀어야 하는지 정해진 규칙이 하나도 없다. 수학은 자유로움을 학습하는 일이다. 그래서 어렸을 땐 얽매이지 않고 많은 생각을 자유롭게 하는 훈련을 하면 좋을 것 같다</p>
<hr>
<hr>
<hr>
<p>트리 탐색이란? 모든 노드를 체계적인 순서대로 순회하는 과정이다.
트리는 비선형적이어서, 탐색 방법은 여러 가지다.</p>
<h2 id="각각의-방법은-어떤-문제를-어떻게-해결하는가">각각의 방법은 어떤 문제를 어떻게 해결하는가?</h2>
<p>depth:   (Stack/Recursive)    Search all possible paths            </p>
<p>breadth: (Queue/Iterative)    Search the shortest path, Connectivity Check, AI &amp; Search Problems    </p>
<p>ㅇ너비 우선 탐색 (BFS): Breadth First, layer by layer
ㅇ깊이 우선 탐색 (DFS): Depth First, Backtracking 
대표 3가지</p>
<ol>
<li>선순서(Preorder): Top-Down (복제, 저장/직렬화)</li>
<li>후순서(Postorder): Bottom-Up (삭제, 폴더 크기 계산)</li>
<li>중순서(Inorder): Left-to-Right (BST 이진 탐색 트리의 데이터를 정렬 및 추출)</li>
</ol>
<ol>
<li>선순서 (Top-Down) </li>
</ol>
<ul>
<li>문제: 파일은 1차원, 트리는 2차원<ul>
<li>하드 드라이브에 있는 파일은 선형적인 바이트 시퀀스다.</li>
<li>트리를 파일로 직렬화하려면, 2차원 분기 구조를 1차원 데이터로 만들어야 한다.</li>
</ul>
</li>
</ul>
<p>일반적으로, 자식은 부모의 주소를 저장하지 않는다 (예외: red-black tree 등등)
그래서 파일 로딩은 선순서가 타당하다.</p>
<ol start="2">
<li>후순서 (Bottom-Up) &quot;폴더의 크기를 계산할 때, 폴더 하위의 모든 파일의 크기를 합산하는 게 먼저다&quot;</li>
<li>중순서 (Left-to-Right)    </li>
</ol>
<p>linked-list 에서 head 가 포인터를 잃어버리면, 메모리 누수가 발생할 수 있는 것처럼,
tree 에서 root 를 잃어버리면, 메모리 누수가 발생할 수 있는 건가봐?
-&gt;
그렇다. 프로그램은 유일한 진입점을 잃어버린 상태다.
tree 를 안전하게 free() 하고 그 메모리를 OS에 반환하려면, tree 를 순회하며 모든 노드에 대해 free() 를 호출해야 한다.
만약 root 변수가, 덮어쓰이거나, NULL 로 설정되거나, free() 를 호출하지 않고 범위 밖으로 빠져나가면, 그 하위 노드들에 접근할 수 없다.
그 노드들은 프로그램이 실행되는 동안 RAM 에서 고립된다. 더이상 사용할 수도 없고, 주소를 잃어버려서 삭제할 수도 없다.
최신 OS 는 프로그램이 최종적으로 종료되고 셧다운될 때 프로세스가 할당한 모든 메모리를 자동으로 회수하지만, 
오래 실행되는 서버 APP 의 경우, 사용 가능한 RAM 을 소모하여 시스템이 다운될 여지가 있다.
그래서 malloc 에 대응해서 결국에는 free 해야 하며, 항상 진입점 (head 또는 root) 을 보호해야 한다.</p>
<p>지금 말하는 tree는 
main.rs 진입, main() 진입이랑 연관 있어?
-&gt; 
맞다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[260903(목)]]></title>
            <link>https://velog.io/@turing_machine/260903%EB%AA%A9</link>
            <guid>https://velog.io/@turing_machine/260903%EB%AA%A9</guid>
            <pubDate>Thu, 03 Sep 2026 13:45:44 GMT</pubDate>
            <description><![CDATA[<p>Q.
linked_list 의 헤드를 잃어버리면 전체 체인이 사라진다고?
데이터와 연결 관계는 그대로지 않아? 헤드를 메모리에서 전수조사하면 되지 않아?
A.
헤드가 없으면, 프로그램은 첫번째 노드의 메모리 주소를 보유하지 않게 된다.
그래서 체인으로 들어갈 진입점을 알 수 없다.
APP 에서는 보안 및 안정성을 이유로 OS와 런타임 환경이 
메모리 주소를 수동으로 스캔하는 것을 차단한다.
데이터와 포인터의 물리적 바이트가 여전히 RAM 어딘가에 남아 있더라도,
프로그램은 포인터를 갖고 있지 않다.
파이썬에서 객체를 가리키는 참조가 하나도 없으면,
가비지 컬렉터가 그 객체를 제거하고 메모리를 회수한다.</p>
<p>Q.
메모리 전수조사가 가능하다고 치자.
10 GB 메모리를 스캔하는 데에 몇 초 걸려?
A.
현대 RAM의 순차 읽기 속도는 초당 30~60 GB 다. 
OS를 우회해서 바이트를 스캔했다고 하더라도, 어떤 바이트가 head 인지, 어떻게 알 수 있을까?
고유한 시그니처나 메타데이터가 없다면, head 를 특정할 수 없다.
<strong>* 프로그래밍 언어가 참조와 포인터에 의존하는 이유다. 정확한 좌표이기 때문이다. *</strong></p>
]]></description>
        </item>
    </channel>
</rss>