<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>CodingNabi 님의 블로그</title>
    <link>https://codingnabi.tistory.com/</link>
    <description>CodingNabi 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 6 Aug 2026 00:57:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>CodingNabi</managingEditor>
    <image>
      <title>CodingNabi 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8892427/attach/1b749da1199643998cb151d31dee6f52</url>
      <link>https://codingnabi.tistory.com</link>
    </image>
    <item>
      <title>[C++] STL vector - size/capacity부터 iterator invalidation까지</title>
      <link>https://codingnabi.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. STL이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL (Standard Template Library)은 프로그래밍에 필요한 자료구조와 알고리즘을 템플릿 형태로 제공하는 C++ 표준 라이브러리다.&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;컨테이너 (Container)&lt;/b&gt;: 데이터를 저장하는 자료구조 (vector, list, map 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반복자 (Iterator)&lt;/b&gt;: 컨테이너의 원소를 순회하기 위한, 포인터와 유사한 개념의 인터페이스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알고리즘 (Algorithm)&lt;/b&gt;: sort, find 등 컨테이너에 독립적으로 동작하는 함수들&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 배열의 한계와 vector의 등장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 스타일 배열은 크기가 고정이라는 한계가 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int MAX_SIZE = 10;
int arr[MAX_SIZE] = {};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한계를 보완한 것이 vector, 즉 &lt;b&gt;동적 배열&lt;/b&gt;이다. vector는 크기가 가변적이라는 점만 빼면 배열과 동일하게 동작한다. &lt;b&gt;원소가 하나의 연속된 메모리 블록에 저장&lt;/b&gt;된다는 특징도 배열과 같다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. size와 capacity&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector를 이해하는 핵심은 &lt;b&gt;size&lt;/b&gt;와 &lt;b&gt;capacity&lt;/b&gt;를 구분하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;size()&lt;/td&gt;
&lt;td&gt;실제로 사용 중인 데이터의 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;capacity()&lt;/td&gt;
&lt;td&gt;재할당 없이 담을 수 있는 최대 개수 (여유분 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector가 내부적으로 동작하는 원리는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;여유분을 두고 메모리를 할당한다.&lt;/li&gt;
&lt;li&gt;여유분까지 꽉 차면 메모리를 증설(재할당)한다.&lt;/li&gt;
&lt;li&gt;기존 데이터보다 큰 새 메모리 블록을 동적 할당하고, 기존 데이터를 복사(또는 이동)한 뒤 기존 메모리를 해제한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;capacity 관련 함수&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;reserve(n)&lt;/b&gt;: capacity만 n으로 확보한다. size는 그대로이며, 원소를 생성하지 않는다. 순수하게 메모리만 미리 확보하는 용도.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;resize(n)&lt;/b&gt;: capacity와 함께 &lt;b&gt;size도 n으로 변경&lt;/b&gt;된다. 새로 생기는 원소는 기본값으로 초기화(value-initialize)된다는 점이 reserve와의 결정적 차이.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;clear()&lt;/b&gt;: 원소를 모두 소멸시켜 size를 0으로 만든다. &lt;b&gt;capacity는 변하지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ resize로 공간을 미리 잡아두면, 이미 size에 포함된 상태이므로 push_back이 아니라 operator[]로 접근해야 한다. (push_back은 항상 size 뒤에 추가하기 때문)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reserve를 미리 해두는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;capacity가 변할 때마다(재할당) 기존 원소를 복사 또는 이동하는 비용이 발생한다. 최종적으로 필요한 크기를 미리 안다면 reserve로 재할당 횟수 자체를 줄여 비용을 아낄 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;증설 배수 (Growth Factor)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리가 꽉 찼을 때 얼마나 증설할지는 &lt;b&gt;표준에 정해져 있지 않고 구현체마다 다르다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSVC: 보통 &lt;b&gt;1.5배&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;GCC libstdc++: 보통 &lt;b&gt;2배&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험 코드로 직접 확인해볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;vector&amp;lt;int&amp;gt; s;
for (int i = 0; i &amp;lt; 1001; i++) {
    s.push_back(100);
    cout &amp;lt;&amp;lt; s.size() &amp;lt;&amp;lt; &quot; | &quot; &amp;lt;&amp;lt; s.capacity() &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의사항 정리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;재할당(capacity 증가)이 발생하면 기존의 iterator/pointer/reference는 전부 무효화&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;재할당 시 원소가 복사되느냐 이동되느냐는 &lt;b&gt;원소 타입의 이동 생성자가 noexcept인지&lt;/b&gt;에 따라 갈린다. noexcept 이동 생성자가 있으면 move, 없으면 예외 안전성 때문에 copy로 처리될 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Iterator (반복자)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iterator는 포인터와 유사한 개념으로, 컨테이너의 원소를 가리키는 역할을 한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;vector&amp;lt;int&amp;gt; v(10);
for (vector&amp;lt;int&amp;gt;::size_type i = 0; i &amp;lt; v.size(); i++)
    v[i] = i;

vector&amp;lt;int&amp;gt;::iterator itBegin = v.begin(); // v[0]을 가리킴
vector&amp;lt;int&amp;gt;::iterator itEnd = v.end();     // 마지막 원소의 &quot;다음&quot; 주소 (쓰레기값)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순회 방법:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 정방향
for (vector&amp;lt;int&amp;gt;::iterator it = v.begin(); it != v.end(); ++it) {
    cout &amp;lt;&amp;lt; (*it) &amp;lt;&amp;lt; endl;
}

// 역방향
for (vector&amp;lt;int&amp;gt;::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    cout &amp;lt;&amp;lt; (*it) &amp;lt;&amp;lt; endl;
}
&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 중간 삽입/삭제 vs 처음&amp;middot;끝 삽입/삭제 vs 임의 접근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector를 실무에서 쓸 때 가장 중요한 성능 특성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산 성능&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;임의 접근 (operator[])&lt;/td&gt;
&lt;td&gt;O(1) &amp;mdash; 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;끝에서 삽입/삭제 (push_back/pop_back)&lt;/td&gt;
&lt;td&gt;O(1) (amortized) &amp;mdash; 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중간/처음 삽입/삭제 (insert/erase)&lt;/td&gt;
&lt;td&gt;O(n) &amp;mdash; 나쁨&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;pre class=&quot;json&quot;&gt;&lt;code&gt;[0][1][2][3][4][ ][ ]   // 인덱스 2에 5를 삽입하고 싶은 상황
[0][1][ ][2][3][4][ ]   // 2,3,4를 한 칸씩 뒤로 밀고
[0][1][5][2][3][4][ ]   // 빈 공간에 삽입
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제도 마찬가지로, 삭제한 자리를 뒤 원소들로 당겨와 연속성을 유지해야 하므로 O(n) 비용이 든다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;vector&amp;lt;int&amp;gt;::iterator insertIt = v.insert(v.begin() + 2, 5);
vector&amp;lt;int&amp;gt;::iterator eraseIt1 = v.erase(v.begin() + 2);
vector&amp;lt;int&amp;gt;::iterator eraseIt2 = v.erase(v.begin() + 2, v.begin() + 4); // [2,4) 범위 삭제, 4는 미포함
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. erase 후 iterator 무효화 - 조건부 삭제 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;erase는 실행된 순간 해당 iterator를 무효화&lt;/b&gt;시킨다. 삭제 후 그 iterator를 그대로 ++ 하면 이미 유효하지 않은 iterator를 참조하게 되어 크래시로 이어진다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;// 잘못된 코드
for (vector&amp;lt;int&amp;gt;::iterator it = v.begin(); it != v.end(); ++it) {
    if (*it == 3) {
        v.erase(it); // erase 후 it은 무효! 다음 루프의 ++it에서 문제 발생
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 패턴&lt;/b&gt;은 erase의 반환값으로 iterator를 갱신하고, for문의 ++it는 제거하여 삭제하지 않은 경우에만 증가시키는 것이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;for (vector&amp;lt;int&amp;gt;::iterator it = v.begin(); it != v.end(); /* ++it 제거 */) {
    int data = *it;
    if (data == 3) {
        it = v.erase(it); // erase가 반환하는, 삭제된 자리를 가리키는 새 iterator로 갱신
    } else {
        ++it;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ v.clear()를 호출하면 모든 iterator가 무효화되므로, clear 이후에는 반드시 루프를 break해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/18</guid>
      <comments>https://codingnabi.tistory.com/18#entry18comment</comments>
      <pubDate>Sun, 2 Aug 2026 22:54:23 +0900</pubDate>
    </item>
    <item>
      <title>const int* vs int* const 차이</title>
      <link>https://codingnabi.tistory.com/16</link>
      <description>&lt;p data-sourcepos=&quot;3:1-3:70;38-107&quot; data-ke-size=&quot;size16&quot;&gt;C/C++ 공부하다 보면 반드시 한 번은 헷갈리는 지점이 const와 포인터의 조합이다. 오늘 정리한 내용을 기록해둔다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;5:1-5:19;109-127&quot; data-ke-size=&quot;size26&quot;&gt;포인터가 들고 있는 두 가지&lt;/h2&gt;
&lt;p data-sourcepos=&quot;7:1-7:85;129-213&quot; data-ke-size=&quot;size16&quot;&gt;포인터 변수는 메모리 공간을 하나 차지하면서, 그 안에 &lt;b&gt;주소값&lt;/b&gt;을 저장하고 있다. 그래서 포인터에는 &quot;고정할 수 있는 대상&quot;이 두 가지 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;9:1-10:28;215-282&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;9:1-9:40;215-254&quot;&gt;포인터가 &lt;b&gt;가리키는 데이터&lt;/b&gt; (주소를 따라갔을 때 나오는 값)&lt;/li&gt;
&lt;li data-sourcepos=&quot;10:1-10:28;255-282&quot;&gt;포인터 &lt;b&gt;자기 자신이 담고 있는 주소값&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-sourcepos=&quot;12:1-12:43;284-326&quot; data-ke-size=&quot;size16&quot;&gt;const를 어디에 붙이느냐에 따라 이 둘 중 무엇을 고정할지가 갈린다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;14:1-19:4;328-445&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;int a = 10, b = 20;

const int* p1 = &amp;amp;a;   // (1) 가리키는 데이터가 const
int* const p2 = &amp;amp;a;   // (2) 포인터 자체가 const&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-sourcepos=&quot;21:1-21:39;447-485&quot; data-ke-size=&quot;size26&quot;&gt;(1) const int* p1 &amp;mdash; 가리키는 값을 못 바꾼다&lt;/h2&gt;
&lt;p data-sourcepos=&quot;23:1-23:65;487-551&quot; data-ke-size=&quot;size16&quot;&gt;이 포인터를 통해서는 가리키는 대상의 값을 변경할 수 없다. 대신 포인터가 다른 곳을 가리키게 하는 것은 자유롭다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;25:1-28:4;553-633&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;*p1 = 30;   // 에러! 가리키는 값 변경 시도
p1 = &amp;amp;b;    // OK! 포인터가 다른 곳을 가리키는 건 자유&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-sourcepos=&quot;30:1-30:39;635-673&quot; data-ke-size=&quot;size26&quot;&gt;(2) int* const p2 &amp;mdash; 포인터 자체를 못 바꾼다&lt;/h2&gt;
&lt;p data-sourcepos=&quot;32:1-32:54;675-728&quot; data-ke-size=&quot;size16&quot;&gt;가리키는 값은 자유롭게 바꿀 수 있지만, 포인터 변수 자체(담고 있는 주소값)는 바꿀 수 없다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;34:1-37:4;730-809&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;*p2 = 30;   // OK! 가리키는 값 변경은 자유
p2 = &amp;amp;b;    // 에러! 포인터 자체(주소값)를 바꾸려 함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;39:1-39:157;811-967&quot; data-ke-size=&quot;size16&quot;&gt;즉 p2는 한번 &amp;amp;a로 초기화되면 &lt;b&gt;평생 a만 가리켜야 하는 포인터&lt;/b&gt;가 된다. const int x = 10;이라고 선언하면 x에 다른 값을 대입할 수 없는 것과 완전히 같은 원리인데, 다만 p2가 담고 있는 &quot;값&quot;이 다름 아닌 &quot;주소&quot;라는 점이 다르다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;41:1-41:10;969-978&quot; data-ke-size=&quot;size26&quot;&gt;판별하는 팁&lt;/h2&gt;
&lt;p data-sourcepos=&quot;43:1-43:38;980-1017&quot; data-ke-size=&quot;size16&quot;&gt;const의 위치가 * 기준으로 어디에 있는지를 보면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;45:1-46:60;1019-1126&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;45:1-45:48;1019-1066&quot;&gt;const가 *보다 &lt;b&gt;왼쪽&lt;/b&gt;에 있으면 &amp;rarr; 가리키는 대상이 const&lt;/li&gt;
&lt;li data-sourcepos=&quot;46:1-46:60;1067-1126&quot;&gt;const가 *보다 &lt;b&gt;오른쪽&lt;/b&gt;(변수 이름 바로 앞)에 있으면 &amp;rarr; 포인터 자체가 const&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;48:1-48:164;1128-1291&quot; data-ke-size=&quot;size16&quot;&gt;참고로 int const * p3처럼 const가 int 뒤, * 앞에 오는 표기도 있는데, 이 경우도 const가 *보다 왼쪽에 있으므로 (1)과 동일하게 &quot;가리키는 대상이 const&quot;인 케이스다. const int*와 int const*는 완전히 같은 의미다.&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/16</guid>
      <comments>https://codingnabi.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 26 Jul 2026 04:42:39 +0900</pubDate>
    </item>
    <item>
      <title>malloc의 정렬(alignment)과 힙 오버플로우</title>
      <link>https://codingnabi.tistory.com/15</link>
      <description>&lt;p data-sourcepos=&quot;3:1-3:14;34-47&quot; data-ke-size=&quot;size16&quot;&gt;들어가기에 앞서 malloc에 대해 짧게 설명하자면, malloc은 힙(heap)이라는 메모리 영역에서 &lt;b&gt;동적으로&lt;/b&gt; 메모리를 할당받는 C 표준 라이브러리 함수다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;11:1-11:20;168-187&quot; data-ke-size=&quot;size16&quot;&gt;스택 변수와 다르게, 힙 메모리는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;12:1-14:120;188-416&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;12:1-12:56;188-243&quot;&gt;프로그램이 &lt;b&gt;런타임에 크기를 결정&lt;/b&gt;할 수 있음 (컴파일 타임에 크기가 고정되는 배열과 다름)&lt;/li&gt;
&lt;li data-sourcepos=&quot;13:1-13:53;244-296&quot;&gt;명시적으로&lt;b&gt; free()를 호출하기 전까지 계속 살아있음&lt;/b&gt; (함수가 끝나도 사라지지 않음)&lt;/li&gt;
&lt;li data-sourcepos=&quot;14:1-14:120;297-416&quot;&gt;&lt;b&gt;힙 관리자(heap manager)&lt;/b&gt;라는 별도의 소프트웨어가 이 영역을 추적하고 관리함 (OS가 직접 관리하는 게 아니라, CRT/힙 라이브러리가 OS로부터 큰 덩어리를 받아와서 잘게 쪼개 나눠주는 방식)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라느 특징을 가진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;malloc 내부 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;malloc(12)를 호출하면 힙 관리자는 단순히 &quot;12바이트를 뚝 떼어서 준다&quot;가 아니라, 그 블록을 관리하기 위한 &lt;b&gt;메타데이터&lt;/b&gt;를 함께 만든다. 대략적인 구조는 이렇다 (구현마다 세부는 다름)&lt;/p&gt;
&lt;pre id=&quot;code_1783743436984&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[헤더: 블록 크기, 사용중/free 여부, 이전&amp;middot;다음 블록 링크 등 관리정보]
[정렬용 패딩 (필요 시)]
[실제 데이터 영역: 내가 요청한 12바이트]  &amp;larr; p가 가리키는 지점
[정렬용 패딩 (필요 시, 다음 블록 시작 위치 맞추기 위함)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-sourcepos=&quot;3:1-3:14;34-47&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;27:1-27:164;708-871&quot;&gt;&lt;b&gt;헤더&lt;/b&gt;: 힙 관리자가 이 블록의 크기, 상태(사용중/free), free list 연결 정보 등을 기록해두는 영역. free(p)를 호출하면 힙 관리자는 p를 기준으로 앞쪽에 있는 이 헤더를 읽어서 &quot;아, 이 블록은 몇 바이트짜리구나&quot; 판단하고 free list에 돌려놓는다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;28:1-28:63;872-934&quot;&gt;&lt;b&gt;데이터 영역&lt;/b&gt;: 실제로 내가 쓸 수 있는 12바이트. p는 정확히 이 영역의 시작 주소를 가리킨다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;29:1-29:47;935-981&quot;&gt;&lt;b&gt;패딩&lt;/b&gt;: &lt;b&gt;정렬 요구사항&lt;/b&gt;을 맞추기 위해 붙는 여유 공간 (아래에서 자세히).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;3:1-3:14;34-47&quot; data-ke-size=&quot;size26&quot;&gt;정렬은 항상 보장될까?&lt;/h2&gt;
&lt;p data-sourcepos=&quot;5:1-5:119;49-167&quot; data-ke-size=&quot;size16&quot;&gt;C 표준은 malloc이 반환하는 주소가 &lt;b&gt;어떤 타입으로 캐스팅해도 안전한 정렬&lt;/b&gt;을 만족하도록 요구한다. x64에서는 보통 16바이트 정렬이다. 즉, malloc이 반환하는 &lt;b&gt;주소값 자체&lt;/b&gt;가 항상 16의 배수가 되도록 힙 관리자가 보장해준다는 뜻이다. 이건&lt;b&gt;&amp;nbsp;&quot;요청 크기&quot;까지 항상 정렬 단위로 올리는 건 아니다. &lt;/b&gt;힙 관리자 구현 방식은 크게 두 가지로 나뉜다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;11:1-11:65;234-298&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;11:1-11:65;234-298&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방식 A: Size class(bin) 기반&lt;/b&gt; &amp;mdash; glibc malloc, Windows 힙 관리자 등 대부분&lt;/p&gt;
&lt;div data-sourcepos=&quot;12:1-17:4;299-437&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;작은 요청은 미리 정해진 &quot;크기 등급&quot;에 배정됨: 16, 24, 32, 48, 64, 96, 128, ...

malloc(12) &amp;rarr; 가장 작은 등급(예: 16 또는 24)에 배정
malloc(20) &amp;rarr; 다음 등급(예: 32)에 배정&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;18:1-18:121;438-558&quot; data-ke-size=&quot;size16&quot;&gt;정렬을 위해 올린다기보다, 메모리 단편화 방지와 관리 효율을 위해 미리 정해둔 &quot;그릇 크기&quot;에 담는 것. malloc(12)와 malloc(15)가 같은 등급에 들어가 _msize 결과가 동일할 수 있음.&lt;/p&gt;
&lt;p data-sourcepos=&quot;20:1-20:28;560-587&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;20:1-20:28;560-587&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방식 B: 헤더 오버헤드가 정렬 패딩 역할&lt;/b&gt;&lt;/p&gt;
&lt;div data-sourcepos=&quot;21:1-23:4;588-632&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;[헤더 8~16바이트][요청한 데이터 영역][정렬 맞추기용 패딩]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;24:1-24:52;633-684&quot; data-ke-size=&quot;size16&quot;&gt;블록마다 개별적으로 전체 크기가 정렬 단위(8/16)의 배수가 되도록 패딩을 끼워넣는 방식.&lt;/p&gt;
&lt;p data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size26&quot;&gt;_msize가 알려주는 건 &quot;물리적 크기&quot;가 아니다 &lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;57:1-57:83;2012-2094&quot; data-ke-size=&quot;size16&quot;&gt;_msize는 Windows CRT가 제공하는 비표준 함수로, &quot;이 포인터가 가리키는 힙 블록이 얼마나 크냐&quot;를 알려주는 용도로 흔히 소개된다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;59:1-62:4;2096-2158&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;char* p = (char*)malloc(12);
printf(&quot;%d&quot;, _msize(p));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;64:1-64:65;2160-2224&quot; data-ke-size=&quot;size16&quot;&gt;이걸 실제로 Release 모드에서 돌려보면 &lt;b&gt;12가 그대로 나온다.&lt;/b&gt; (Debug 모드에서도 12가 나온다.)&lt;/p&gt;
&lt;p data-sourcepos=&quot;66:1-66:49;2226-2274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;66:1-66:49;2226-2274&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 물리적으로는 더 크게 잡았을 수 있는데 _msize는 12를 돌려주는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;68:1-68:39;2276-2314&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;68:1-68:39;2276-2314&quot; data-ke-size=&quot;size16&quot;&gt;Windows 힙 관리자는 블록마다 다음 두 가지를 별도로 추적한다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;70:1-71:59;2316-2435&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;70:1-70:61;2316-2376&quot;&gt;실제 물리적으로 확보한 블록 크기 (정렬/size class 때문에 12보다 클 수 있음, 예: 16)&lt;/li&gt;
&lt;li data-sourcepos=&quot;71:1-71:59;2377-2435&quot;&gt;UnusedBytes &amp;mdash; &quot;이 블록에서 실제로 안 쓰이는 뒷부분이 몇 바이트인지&quot;를 정확히 기록&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-sourcepos=&quot;73:1-73:57;2437-2493&quot; data-ke-size=&quot;size16&quot;&gt;그리고 _msize(내부적으로 HeapSize API 호출)는 다음과 같이 계산해서 반환한다:&lt;/p&gt;
&lt;div data-sourcepos=&quot;75:1-79:4;2495-2596&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;usable_size = 물리적_확보_크기 - UnusedBytes
            = 16 - 4
            = 12   &amp;larr; 요청한 크기 그대로 나옴&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size16&quot;&gt;_msize는 물리적 블록 크기가 아니라, 힙 관리자가 별도로 계산해서 알려주는 &lt;b&gt;&quot;사용 가능한(usable) 크기&quot;&lt;/b&gt;를 반환한다. 이 값은 대체로 요청한 크기와 동일하게 나오며, 내부 패딩량을 반영하지 않는다. 즉, _msize로는 &quot;실제 물리적으로 몇 바이트가 확보되어 있는지&quot; 알아낼 방법이 없다. 이건 힙 구현이 완전히 캡슐화해서 감춰버린 정보다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;26:1-26:84;686-769&quot; data-ke-size=&quot;size26&quot;&gt;만약 12바이트를 할당받고 13바이트를 쓴다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으론&amp;nbsp;&lt;b&gt; &quot;12바이트는 정리되고 1바이트만 쓰레기값으로 남는다&quot;&lt;/b&gt;라고 생각할 수 있겠지만, 앞서 말했듯&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;malloc(12)를 호출하면 힙 관리자는 단순히 &quot;12바이트를 뚝 떼어서 준다&quot;가 아니라, 그 블록을 관리하기 위한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;메타데이터&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 함께 만든다.&lt;span&gt;따라서 넘어간 바이트가 어디를 침범했냐에 따라 결과가 달라진다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;넘어간 위치&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;free(p) 시점&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;나중 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;정렬/size-class 여유 공간 내부 (내 블록 소유)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;문제없음&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;우연히 안전 &amp;mdash; 재현 안 되는 시한폭탄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;옆 블록 헤더 (메타데이터)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;문제없이 통과 가능&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;옆 블록 malloc/free 시 크래시&amp;middot;힙 손상 에러&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;옆 블록 데이터 (사용 중)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;문제없음&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;그 데이터를 쓰는 코드에서 값 오염 발견 &amp;mdash; 조용한 논리 버그&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 경우든 오버플로우가 발생한 순간에는 아무 에러도 나지 않는다. 버그를 심는 시점과 증상이 터지는 시점이 완전히 분리되어 디버깅이 매우 어렵다. 앞서 본 것처럼 _msize로도 이 여유 공간의 존재 여부를 미리 알 방법이 없으니, &quot;여유 공간이 있으니 조금 넘쳐도 괜찮겠지&quot;라는 가정 자체가 애초에 성립하지 않는다.&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/15</guid>
      <comments>https://codingnabi.tistory.com/15#entry15comment</comments>
      <pubDate>Sat, 11 Jul 2026 13:27:21 +0900</pubDate>
    </item>
    <item>
      <title>strlen, strcpy, strcmp / memcpy, memcmp 구현</title>
      <link>https://codingnabi.tistory.com/14</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;strlen&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;size_t strlen(const char* str);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인자&lt;/b&gt;: str &amp;mdash; 길이를 잴 널 종단 문자열의 시작 주소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: size_t &amp;mdash; \0 직전까지의 문자 개수 (\0 자체는 카운트 안 함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: str부터 한 칸씩 전진하며 \0을 만날 때까지 카운트. \0을 찾아야 끝나는 구조라 시간복잡도 O(n). 순수 바이너리 데이터에 쓰면 안 됨(중간에 우연히 0바이트가 있으면 거기서 멈춰버림).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;strcpy&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;char* strcpy(char* dest, const char* src);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인자&lt;/b&gt;: dest &amp;mdash; 복사받을 목적지 버퍼(반드시 src 길이+1 이상 크기여야 함) / src &amp;mdash; 복사할 원본 널 종단 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: char* &amp;mdash; dest의 시작 주소 (체이닝 목적)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: src를 처음부터 \0까지 한 바이트씩 dest에 복사, \0도 포함해서 복사함. dest 크기 체크를 안 해서 버퍼 오버플로우 위험이 있는 대표적 함수(strcpy_s, strncpy가 대안). src와 dest가 겹치면 undefined behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;strcmp&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;int strcmp(const char* s1, const char* s2);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인자&lt;/b&gt;: s1, s2 &amp;mdash; 비교할 두 널 종단 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: int &amp;mdash; s1 &amp;lt; s2면 음수, 같으면 0, s1 &amp;gt; s2면 양수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 앞에서부터 한 바이트씩 비교하다가 다른 문자를 만나거나 둘 중 하나가 \0에 도달하면 종료. 바이트는 unsigned char로 해석해서 비교(부호 문제 방지). strncmp(s1, s2, n)은 최대 비교 길이 n이 추가된 버전.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;memcpy&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;void* memcpy(void* dest, const void* src, size_t n);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인자&lt;/b&gt;: dest &amp;mdash; 목적지 버퍼 / src &amp;mdash; 원본 버퍼 / n &amp;mdash; 복사할 바이트 수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: void* &amp;mdash; dest의 시작 주소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: \0이나 데이터 의미와 무관하게 무조건 n바이트를 그대로 복사. 구조체, 배열, 임의 바이너리 데이터에 다 사용 가능. dest와 src가 겹치면 undefined behavior &amp;mdash; 겹칠 가능성이 있으면 memmove 사용해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;memcmp&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;int memcmp(const void* s1, const void* s2, size_t n);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인자&lt;/b&gt;: s1, s2 &amp;mdash; 비교할 두 버퍼 / n &amp;mdash; 비교할 바이트 수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: int &amp;mdash; 첫 차이 지점에서 (unsigned char)s1[i] - (unsigned char)s2[i] 부호, 끝까지 같으면 0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: \0을 특별 취급하지 않고 무조건 n바이트를 끝까지(또는 다른 지점까지) 비교. 구조체 비교, 해시값 비교, 바이너리 패킷 비교 등에 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 구분 기준&lt;/h3&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;계열&lt;/td&gt;
&lt;td&gt;str 계열&lt;/td&gt;
&lt;td&gt;mem 계열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대상&lt;/td&gt;
&lt;td&gt;텍스트(문자열), \0 종단 전제&lt;/td&gt;
&lt;td&gt;순수 바이트 블록, 타입/의미 무관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;길이 파악&lt;/td&gt;
&lt;td&gt;직접 \0 찾아가며 판단&lt;/td&gt;
&lt;td&gt;인자로 받은 n 그대로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\0 만나면&lt;/td&gt;
&lt;td&gt;거기서 비교/복사 종료&lt;/td&gt;
&lt;td&gt;무시하고 계속 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 용도&lt;/td&gt;
&lt;td&gt;문자열 처리&lt;/td&gt;
&lt;td&gt;구조체, 바이너리 패킷, 해시값 비교/복사&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;size_t my_strlen(const char* buf) {
	const char* b_temp = buf;
	while(*b_temp != '\0') {
		b_temp++;
	}
	return b_temp - buf;
}

char* my_Strcpy(char* dest, const char* src) {
	char* d_temp = dest;
	while (*src != '\0') {
		*dest = *src;
		dest++;
		src++;
	}
	*dest = '\0';
	return d_temp;
}


int my_strcmp(const char* buf1,const char* buf2, size_t t) {
	const unsigned char* b1_temp = (const unsigned char*)buf1;
	const unsigned char* b2_temp = (const unsigned char*)buf2;

	for (size_t i = 0; i &amp;lt; t; i++) {
		if (b1_temp[i] != b2_temp[i]) {
			return  b1_temp[i] - b2_temp[i];
		}
		if (b1_temp[i] == '\0') {
			return 0;
		}
	}
	return 0;
}

void* my_memcpy(void* dest, void* src, size_t t) {
	//파라미터가 void*형 변수이기 때문에 캐스팅을 하려 1바이트씩 복사.
	unsigned char* d_temp = (unsigned char*)dest;
	const unsigned char* s_temp = (const unsigned char*)src;
	for (size_t i = 0; i &amp;lt; t; i++) {
		d_temp[i] = s_temp[i];
	}
	return dest;
}

int my_memcmp(void* buf1, void* buf2, size_t t) {
	const unsigned char* b1_temp = (const unsigned char*)buf1;
	const unsigned char* b2_temp = (const unsigned char*)buf2;

	for (size_t i = 0; i &amp;lt; t; i++) {
		if (b1_temp[i] != b2_temp[i]) {
			return  b1_temp[i] - b2_temp[i];
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/14</guid>
      <comments>https://codingnabi.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 11 Jul 2026 04:15:10 +0900</pubDate>
    </item>
    <item>
      <title>스파이럴(달팽이) 배열 구현하기 - 방향배열(dx, dy) 방식</title>
      <link>https://codingnabi.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;시리즈 마지막 방식이다. 코드가 짧고, 방향이 더 늘어나도(대각선 8방향 등) 배열 하나만 늘리면 확장되는 방식이다. 해당 방식은 방향이 필요한 모든 알고리즘에 통용적으로 사용이 가능하며, 게임 알고리즘에서도 종종 사용된다 &amp;mdash; 예를 들어 캐릭터의 8방향 이동 처리, 타일 기반 맵에서의 인접 칸 탐색(BFS/DFS), 몬스터의 순찰 경로 계산 같은 곳에서 같은 dx[], dy[] 패턴을 그대로 가져다 쓸 수 있다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;5:1-5:9;113-121&quot; data-ke-size=&quot;size26&quot;&gt;방식 설명&lt;/h2&gt;
&lt;p data-sourcepos=&quot;7:1-7:33;123-155&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 &quot;방향 자체를 데이터로 만든다&quot;는 것이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;7:1-7:33;123-155&quot; data-ke-size=&quot;size16&quot;&gt;오른쪽&amp;middot;아래&amp;middot;왼쪽&amp;middot;위, 이 4가지 이동을 if-else나 case로 나누는 대신, dx[], dy[]라는 배열에 미리 저장해두고, dir 값 하나로 &quot;지금 배열의 몇 번째 방향을 쓰는 중인지&quot;만 관리한다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;38:1-38:186;1094-1279&quot; data-ke-size=&quot;size16&quot;&gt;dx[dir], dy[dir]이 &quot;지금 방향으로 한 칸 이동하면 x, y가 각각 얼마나 바뀌는지&quot;를 나타낸다. dir이 0이면 (dx,dy)=(1,0)이라 오른쪽으로, dir이 1이면 (0,1)이라 아래로 이동한다. 막히면(dir+1) % 4로 배열의 다음 칸을 가리키게 해서 방향을 90도 꺾는다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;40:1-40:122;1281-1402&quot; data-ke-size=&quot;size16&quot;&gt;switch 방식과 결정적으로 다른 점은, switch는 한 방향으로 갈 수 있는 데까지 for문으로 한 번에 쭉 이동하지만, 이 방식은 딱 한 칸씩만 이동하고 매번 &quot;다음 칸이 유효한가&quot;를 검사한다는 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;void fillSpiralByDirectionVector(int arr[SIZE][SIZE]) {
	int x = 0;
	int y = 0;
	int value = 0;
	int dx[] = { 1, 0, -1, 0 };
	int dy[] = { 0, 1, 0, -1 };
	int dir = 0;
	while (value != SIZE * SIZE) {
		arr[y][x] = ++value;

		int nextX = x + dx[dir];
		int nextY = y + dy[dir];

		if (nextX &amp;lt; 0 || nextX &amp;gt;= SIZE || nextY &amp;lt; 0 || nextY &amp;gt;= SIZE || arr[nextY][nextX] != 0) {
			dir = (dir + 1) % 4;
			nextX = x + dx[dir];
			nextY = y + dy[dir];
		}
		x = nextX;
		y = nextY;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그림&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nurBC/dJMcagzni3J/8Y0iHVYq29odr5vAjkBDe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nurBC/dJMcagzni3J/8Y0iHVYq29odr5vAjkBDe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nurBC/dJMcagzni3J/8Y0iHVYq29odr5vAjkBDe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnurBC%2FdJMcagzni3J%2F8Y0iHVYq29odr5vAjkBDe1%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;1424&quot; height=&quot;760&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과정&lt;/h2&gt;
&lt;p data-sourcepos=&quot;44:1-44:34;1414-1447&quot; data-ke-size=&quot;size16&quot;&gt;dir 값이 어떤 이동량을 뜻하는지부터 정리하면 이렇다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;46:1-46:104;1449-1552&quot; data-ke-size=&quot;size16&quot;&gt;방향이 막힐 때마다 dir이 0&amp;rarr;1&amp;rarr;2&amp;rarr;3&amp;rarr;0 순서로 순환하며, 각 값이 dx[], dy[]의 대응 인덱스로 실제 이동량을 결정한다. 이 순환 구조가 아래 그림에 나와 있다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;48:1-48:111;1554-1664&quot; data-ke-size=&quot;size16&quot;&gt;실제 값이 채워지는 순서(1~25)는 앞선 두 방식과 완전히 동일하다 &amp;mdash; 어떤 방식을 쓰든 스파이럴이라는 문제 자체의 답은 하나뿐이기 때문이다. 차이는 오직 &quot;어떻게 다음 칸을 계산하는가&quot;에 있다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;50:1-50:58;1666-1723&quot; data-ke-size=&quot;size16&quot;&gt;(0,0)에서 시작해 value=25가 될 때까지 매 스텝 다음 칸을 검사하는 흐름을 요약하면:&lt;/p&gt;
&lt;div data-sourcepos=&quot;52:1-64:4;1725-2092&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;(0,0)=1 &amp;rarr; 다음칸(1,0) 유효 &amp;rarr; 이동
(1,0)=2 &amp;rarr; 다음칸(2,0) 유효 &amp;rarr; 이동
...
(4,0)=5 &amp;rarr; 다음칸(5,0) 범위 밖! &amp;rarr; dir=0&amp;rarr;1(DOWN)로 전환 &amp;rarr; 다음칸(4,1) 유효 &amp;rarr; 이동
(4,1)=6 &amp;rarr; 다음칸(4,2) 유효 &amp;rarr; 이동
...
(4,4)=9 &amp;rarr; 다음칸(4,5) 범위 밖! &amp;rarr; dir=1&amp;rarr;2(LEFT)로 전환 &amp;rarr; 다음칸(3,4) 유효 &amp;rarr; 이동
...
(0,4)=13 &amp;rarr; 다음칸(-1,4) 범위 밖! &amp;rarr; dir=2&amp;rarr;3(UP)로 전환 &amp;rarr; 다음칸(0,3) 유효 &amp;rarr; 이동
...
(0,1)=16 &amp;rarr; 다음칸(0,0) 이미 채워짐(1)! &amp;rarr; dir=3&amp;rarr;0(RIGHT)로 전환 &amp;rarr; 다음칸(1,1) 유효 &amp;rarr; 이동&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;66:1-66:105;2094-2198&quot; data-ke-size=&quot;size16&quot;&gt;switch 방식이 &quot;이 방향으로 갈 수 있는 만큼 for문으로 몰아서 채우고 case를 넘긴다&quot;였다면, 이 방식은 &quot;한 걸음씩 걷다가 막히면 그 자리에서 바로 꺾는다&quot;는 감각에 가깝다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;68:1-68:9;2200-2208&quot; data-ke-size=&quot;size26&quot;&gt;특징 분석&lt;/h2&gt;
&lt;p data-sourcepos=&quot;70:1-70:7;2210-2216&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;72:1-74:84;2218-2473&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;72:1-72:60;2218-2277&quot;&gt;&lt;b&gt;코드가 짧다 :&lt;/b&gt;&amp;nbsp;방향 로직 전체가 dx[], dy[], dir 세 줄로 압축된다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;73:1-73:112;2278-2389&quot;&gt;&lt;b&gt;방향이 늘어나도 확장이 쉽다 :&lt;/b&gt;&amp;nbsp;대각선을 포함한 8방향으로 바꾸고 싶으면 dx[], dy[]에 값 4개만 더 추가하면 된다. switch 방식이라면 case를 4개 더 만들어야 한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;74:1-74:84;2390-2473&quot;&gt;&lt;b&gt;while 하나로 전체 로직이 끝난다 :&lt;/b&gt;&amp;nbsp;별도의 for문 중첩이나 case 분기 없이, 매 스텝 &quot;이동 가능한지 검사 &amp;rarr; 이동&quot;만 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;76:1-76:7;2475-2481&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;78:1-80:127;2483-2865&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;78:1-78:146;2483-2628&quot;&gt;&lt;b&gt;매 칸마다 조건 검사가 실행된다 :&lt;/b&gt;&amp;nbsp;switch나 경계값 방식은 방향이 안 바뀌는 구간에서는 검사 없이 쭉 진행하지만, 이 방식은 25칸 전부에서 &quot;다음 칸이 유효한가&quot;를 매번 확인한다. (다만 이 정도 크기에서는 성능 차이가 체감될 수준은 아니다.)&lt;/li&gt;
&lt;li data-sourcepos=&quot;79:1-79:110;2629-2738&quot;&gt;&lt;b&gt;배열 값에 의존한다 :&lt;/b&gt;&amp;nbsp;switch 방식과 마찬가지로 arr[nextY][nextX] != 0으로 판단하기 때문에, 경계값 방식이 가진 &quot;배열 값과 무관하게 안전하다&quot;는 장점은 없다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;80:1-80:127;2739-2865&quot;&gt;&lt;b&gt;처음 보는 사람은 이해하는 데 시간이 걸린다 :&lt;/b&gt;&amp;nbsp;dx[], dy[], dir이 무엇을 의미하는지 알아야 코드가 읽힌다. 코드량은 가장 적지만 압축도가 높아서, 가독성만 놓고 보면 switch 방식보다 낮다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-sourcepos=&quot;82:1-82:17;2867-2883&quot; data-ke-size=&quot;size26&quot;&gt;네 가지 방식 최종 비교&lt;/h2&gt;
&lt;div data-sourcepos=&quot;84:1-93:41;2885-3322&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;switch&lt;/td&gt;
&lt;td&gt;대칭 축소(nLength)&lt;/td&gt;
&lt;td&gt;경계값(Boundary)&lt;/td&gt;
&lt;td&gt;방향배열(dx,dy)&lt;span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 길이&lt;/td&gt;
&lt;td&gt;김&lt;/td&gt;
&lt;td&gt;&lt;b&gt;가장 짧음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;짧음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배열 값 의존&lt;/td&gt;
&lt;td&gt;의존&lt;/td&gt;
&lt;td&gt;의존 안 함(인덱스만)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;의존 안 함&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;의존&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;for문 인덱스 보정&lt;/td&gt;
&lt;td&gt;필요&lt;/td&gt;
&lt;td&gt;필요 없음&lt;/td&gt;
&lt;td&gt;필요 없음&lt;/td&gt;
&lt;td&gt;필요 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;짝수 크기 지원&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;td&gt;미지원&lt;/td&gt;
&lt;td&gt;&lt;b&gt;지원&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;직사각형(N&amp;times;M) 확장&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;td&gt;&lt;b&gt;가능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;방향 개수 확장(8방향 등)&lt;/td&gt;
&lt;td&gt;어려움(case 추가)&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;td&gt;&lt;b&gt;쉬움&lt;/b&gt;(배열에 값 추가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가독성&lt;/td&gt;
&lt;td&gt;&lt;b&gt;가장 좋음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;좋음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코딩테스트 표준성&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;&lt;b&gt;높음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;중간~높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 data-sourcepos=&quot;95:1-95:10;3324-3333&quot; data-ke-size=&quot;size26&quot;&gt;시리즈 정리&lt;/h2&gt;
&lt;p data-sourcepos=&quot;97:1-97:77;3335-3411&quot; data-ke-size=&quot;size16&quot;&gt;같은 문제라도 &quot;무엇을 기준으로 다음 칸을 판단하는가&quot;에 따라 코드의 모양이 완전히 달라진다는 걸 이 네 방식을 통해 확인할 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;99:1-102:79;3413-3723&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;99:1-99:83;3413-3495&quot;&gt;&lt;b&gt;처음 배우는 단계&lt;/b&gt;라면 switch 방식으로 시작하는 게 맞다. 방향 전환이라는 개념 자체를 코드 구조로 직접 눈에 보이게 익힐 수 있다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;100:1-100:89;3496-3584&quot;&gt;&lt;b&gt;코딩테스트를 준비&lt;/b&gt;한다면 경계값 방식을 표준으로 외워두는 게 가장 범용적이다. 크기&amp;middot;모양에 안 흔들리고, 실제로 이 유형 문제의 정석 풀이로 통한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;101:1-101:60;3585-3644&quot;&gt;&lt;b&gt;방향이 복잡해지거나(8방향, 불규칙 패턴) 코드량을 최소화&lt;/b&gt;하고 싶다면 방향배열 방식이 유리하다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;102:1-102:79;3645-3723&quot;&gt;&lt;b&gt;문제가 홀수 정사각형으로 명확히 고정&lt;/b&gt;되어 있고 짧은 코드가 최우선이라면 대칭 축소 방식도 나쁘지 않지만, 범용성은 가장 떨어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;104:1-104:103;3725-3827&quot; data-ke-size=&quot;size16&quot;&gt;결국 &quot;더 좋은 코드&quot;는 상황에 따라 다르다. 지금 풀고 있는 문제의 제약(크기가 고정인지, 확장 가능성이 있는지, 누가 이 코드를 다시 읽을지)에 맞춰 방식을 고르는 게 핵심이다.&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/13</guid>
      <comments>https://codingnabi.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 9 Jul 2026 00:00:35 +0900</pubDate>
    </item>
    <item>
      <title>스파이럴(달팽이) 배열 구현하기 - 경계값(Boundary) 방식</title>
      <link>https://codingnabi.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 스파이럴 배열 문제의 &quot;정석&quot; 풀이로 통하는 경계값 방식을 정리한다. 코딩테스트에서 스파이럴/나선 행렬 문제가 나오면 가장 먼저 떠올려야 하는 접근이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방식 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 앞선 두 방식과 또 다르다. 배열 값이 채워졌는지(0인지) 보는 대신, 네 방향의 한계선(top, bottom, left, right)을 숫자로 직접 관리한다. 한 방향을 채우면 그 방향의 경계선을 안쪽으로 한칸 좁힌다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;50:1-50:206;1200-1405&quot; data-ke-size=&quot;size16&quot;&gt;switch 방식이 &quot;배열 값이 0인지&quot;로 다음 칸을 판단했다면, 이 방식은 배열 값을 전혀 들여다보지 않고 오직 숫자 네 개(top, bottom, left, right)만으로 어디까지 채웠는지 판단한다. 그래서 한 바퀴가 끝날 때마다 ++top, --right, --bottom, ++left로 테두리가 정확히 한 칸씩 안쪽으로 좁혀진다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;52:1-52:184;1407-1590&quot; data-ke-size=&quot;size16&quot;&gt;if (top &amp;lt;= bottom)과 if (left &amp;lt;= right) 체크가 있는 이유는, 홀수 크기 배열에서 정가운데 행/열을 두 번 채우는 실수를 막기 위해서다. 예를 들어 5x5의 마지막 바퀴는 정중앙 칸 하나뿐인데, 이 체크가 없으면 그 칸을 아래쪽 행 채우기와 왼쪽 열 채우기에서 중복으로 건드리게 된다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;void fillSpiralByBoundary(int arr[SIZE][SIZE]) {
	int value = 0;

	int top = 0;
	int bottom = SIZE - 1;
	int left = 0;
	int right = SIZE - 1;

	while (top &amp;lt;= bottom &amp;amp;&amp;amp; left &amp;lt;= right) {
		for (int x = left; x &amp;lt;= right; x++) {
			arr[top][x] = ++value;
		}
		++top;

		for (int y = top; y &amp;lt;= bottom; y++) {
			arr[y][right] = ++value;
		}
		--right;

		if (top &amp;lt;= bottom) {
			for (int x = right; x &amp;gt;= left; x--) {
				arr[bottom][x] = ++value;
			}
			--bottom;
		}

		if (left &amp;lt;= right) {
			for (int y = bottom; y &amp;gt;= top; y--) {
				arr[y][left] = ++value;
			}
			++left;
		}
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그림&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt1xsa/dJMcahZkVRY/dKIG06NnxFpUZ8sbR2GUNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt1xsa/dJMcahZkVRY/dKIG06NnxFpUZ8sbR2GUNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt1xsa/dJMcahZkVRY/dKIG06NnxFpUZ8sbR2GUNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt1xsa%2FdJMcahZkVRY%2FdKIG06NnxFpUZ8sbR2GUNK%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;1424&quot; height=&quot;1334&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과정&lt;/h2&gt;
&lt;p data-sourcepos=&quot;56:1-56:62;1602-1663&quot; data-ke-size=&quot;size16&quot;&gt;5x5 기준으로 top, bottom, left, right가 어떻게 좁혀지는지 단계별로 추적하면 이렇다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;58:1-82:4;1665-2600&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;[1바퀴] top=0, bottom=4, left=0, right=4

위쪽 행 (x: 0&amp;rarr;4)   : arr[0][0..4] = 1,2,3,4,5        &amp;rarr; ++top (top=1)
오른쪽 열 (y: 1&amp;rarr;4) : arr[1..4][4] = 6,7,8,9           &amp;rarr; --right (right=3)
아래쪽 행 (x: 3&amp;rarr;0) : arr[4][3..0] = 10,11,12,13       &amp;rarr; --bottom (bottom=3)
왼쪽 열 (y: 3&amp;rarr;1)   : arr[3..1][0] = 14,15,16          &amp;rarr; ++left (left=1)

[2바퀴] top=1, bottom=3, left=1, right=3

위쪽 행 (x: 1&amp;rarr;3)   : arr[1][1..3] = 17,18,19          &amp;rarr; ++top (top=2)
오른쪽 열 (y: 2&amp;rarr;3) : arr[2..3][3] = 20,21              &amp;rarr; --right (right=2)
아래쪽 행 (x: 2&amp;rarr;1) : arr[3][2..1] = 22,23              &amp;rarr; --bottom (bottom=2)
왼쪽 열 (y: 2&amp;rarr;2)   : arr[2][1] = 24                    &amp;rarr; ++left (left=2)

[3바퀴] top=2, bottom=2, left=2, right=2

위쪽 행 (x: 2&amp;rarr;2)   : arr[2][2] = 25                    &amp;rarr; ++top (top=3)
오른쪽 열           : top(3) &amp;gt; bottom(2)라 for문 자체가 안 돎
--right (right=1)
if (top&amp;lt;=bottom) &amp;rarr; 3&amp;lt;=2 거짓 &amp;rarr; 아래쪽 행 스킵
if (left&amp;lt;=right) &amp;rarr; 2&amp;lt;=1 거짓 &amp;rarr; 왼쪽 열 스킵

while (top&amp;lt;=bottom) &amp;rarr; 3&amp;lt;=2 거짓 &amp;rarr; 종료&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;84:1-84:93;2602-2694&quot; data-ke-size=&quot;size16&quot;&gt;결과는 switch 방식, 답지 방식과 완전히 동일한 스파이럴이다. 다만 이번엔 한 바퀴(ring) 단위로 안쪽까지 몇 겹인지를 기준으로 색을 나눠 봤다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;86:1-86:101;2696-2796&quot; data-ke-size=&quot;size16&quot;&gt;바깥쪽 테두리(1바퀴, 값 1~16)가 가장 넓고, 그 안쪽 테두리(2바퀴, 값 17~24)가 한 겹 좁고, 정중앙(3바퀴, 값 25)이 마지막이다. 5x5는 딱 3겹으로 끝난다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;88:1-88:9;2798-2806&quot; data-ke-size=&quot;size26&quot;&gt;특징 분석&lt;/h2&gt;
&lt;p data-sourcepos=&quot;90:1-90:7;2808-2814&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;90:1-90:7;2808-2814&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;92:1-92:140;2816-2955&quot;&gt;&lt;b&gt;배열 값에 전혀 의존하지 않는다 :&lt;/b&gt;&amp;nbsp;switch 방식이나 방향배열 방식은 &quot;0인지 아닌지&quot;로 다음 칸을 판단하지만, 이 방식은 오직 경계값 숫자만으로 판단한다. 그래서 배열에 어떤 값이 들어가든(심지어 0이 포함된 값이든) 로직이 안전하다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;93:1-93:152;2956-3107&quot;&gt;&lt;b&gt;for문 인덱스 보정이 필요 없다 :&lt;/b&gt;&amp;nbsp;switch 방식은 for문이 끝난 뒤 --x, ++y로 되돌리는 보정이 필요했지만, 이 방식은 for (x = left; x &amp;lt;= right; x++)처럼 범위(&amp;lt;=)가 정확히 맞아떨어져서 별도 보정이 없다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;94:1-94:132;3108-3239&quot;&gt;&lt;b&gt;홀수/짝수 크기 모두 자연스럽게 지원한다 :&lt;/b&gt;&amp;nbsp;if (top &amp;lt;= bottom), if (left &amp;lt;= right) 체크가 중앙 칸 중복 채움을 막아주기 때문에, SIZE가 짝수든 홀수든 코드 수정 없이 그대로 동작한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;95:1-95:124;3240-3363&quot;&gt;&lt;b&gt;직사각형(N&amp;times;M)으로 쉽게 확장된다 :&lt;/b&gt;&amp;nbsp;top/bottom과 left/right이 애초에 가로세로를 독립적으로 관리하므로, 초기값만 가로/세로 크기에 맞게 따로 잡으면 정사각형이 아니어도 그대로 동작한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;96:1-96:72;3364-3435&quot;&gt;&lt;b&gt;코딩테스트의 표준 풀이다 :&lt;/b&gt;&amp;nbsp;스파이럴/나선 행렬 문제가 나왔을 때 면접관이나 채점 기준이 기대하는 방식이 보통 이거다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;98:1-98:7;3437-3443&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;100:1-101:97;3445-3645&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;100:1-100:104;3445-3548&quot;&gt;경계 변수 4개(top, bottom, left, right)를 동시에 관리해야 해서, 처음 볼 때는 switch 방식보다 변수 흐름 파악에 시간이 좀 더 걸릴 수 있다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;101:1-101:97;3549-3645&quot;&gt;if (top &amp;lt;= bottom), if (left &amp;lt;= right) 체크의 의미(중앙 칸 중복 방지)를 모르면, 이 두 줄이 왜 있는지 이해하기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-sourcepos=&quot;115:1-115:6;3999-4004&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-sourcepos=&quot;117:1-117:168;4006-4173&quot; data-ke-size=&quot;size16&quot;&gt;경계값 방식은 switch 방식의 장점(배열 크기&amp;middot;모양에 안 흔들리는 확장성)과 대칭 축소 방식의 장점(인덱스 보정 없이 깔끔함)을 동시에 가져간 형태에 가깝다. 그래서 코딩테스트나 실무에서 &quot;스파이럴 행렬을 채워라&quot; 같은 문제가 나오면, 이 경계값 방식을 표준 풀이로 익혀두는 게 가장 범용적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/12</guid>
      <comments>https://codingnabi.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 8 Jul 2026 23:50:24 +0900</pubDate>
    </item>
    <item>
      <title>스파이럴(달팽이) 배열 구현하기 - 대칭 축소(nLength) 방식</title>
      <link>https://codingnabi.tistory.com/11</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 설명한 문제의 교재 답지에 있던 방식이다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제 정의&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5x5 정수 배열을 아래처럼 시계방향 나선형으로 1부터 25까지 채우는 게 목표다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot; style=&quot;color: #000000;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1   2   3   4    5
16  17  18  19   6
15  24  25  20   7
14  23  22  21   8
13  12  11  10   9&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방식 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 한 바퀴를 위쪽/오른쪽 + 아래쪽/왼쪽 두 구간으로 쪼개서, 같은 부호(nDirecton)로 x와 y를 순서대로 움직인다. 한라운드가 끝나면 부호를 뒤집고, 다음 루프는 테두리 한 칸 안쪽(nLength -= 2)에서 반복한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nLength는5x5배열 기준 9에서 시작해 2씩 줄어들며 9,7,5,3,1, 다섯 라운드를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안쪽 for문은 매 바퀴 nLength를 반복하는데, 앞쪽 절반(위 or 아래)은 i &amp;lt; nLength/2 +1&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;void fillSpiralBySymmetry(int arr[SIZE][SIZE]) {
	int x = -1;
	int y = 0;
	int nCounter = 0;
	int i = 0;
	int j = 0;
	int nLength = 9;
	int nDirection = 1;

	for (nLength; nLength &amp;gt; 0; nLength -= 2) {
		for (i = 0; i &amp;lt; nLength; ++i) {
			if (i &amp;lt; nLength / 2 + 1)
				x += nDirection;
			else
				y += nDirection;
			arr[y][x] = ++nCounter;
		}

		nDirection = -nDirection;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-sourcepos=&quot;35:1-35:9;1019-1027&quot;&gt;그림&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgLNI/dJMcacX3RLQ/lUiVB5khOE7wRrFIfUDjQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgLNI/dJMcacX3RLQ/lUiVB5khOE7wRrFIfUDjQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgLNI/dJMcacX3RLQ/lUiVB5khOE7wRrFIfUDjQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgLNI%2FdJMcacX3RLQ%2FlUiVB5khOE7wRrFIfUDjQ1%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;1424&quot; height=&quot;700&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;35:1-35:9;1019-1027&quot; data-ke-size=&quot;size26&quot;&gt;과정&lt;/h2&gt;
&lt;p data-sourcepos=&quot;37:1-37:60;1029-1088&quot; data-ke-size=&quot;size16&quot;&gt;nLength = 9 첫 바퀴부터 손으로 추적하면 이렇다. (half = nLength/2 + 1)&lt;/p&gt;
&lt;div data-sourcepos=&quot;39:1-46:4;1090-1290&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;nLength=9, nDirection=+1, half=5, 시작 x=-1, y=0

i=0~4 (x 이동, i&amp;lt;5)   : x=0,1,2,3,4 &amp;rarr; arr[0][0..4] = 1,2,3,4,5
i=5~8 (y 이동, i&amp;gt;=5)  : y=1,2,3,4   &amp;rarr; arr[1..4][4] = 6,7,8,9

바퀴 종료 후 nDirection = -1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sourcepos=&quot;48:1-55:4;1292-1486&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;nLength=7, nDirection=-1, half=4, 시작 x=4, y=4

i=0~3 (x 이동)  : x=3,2,1,0     &amp;rarr; arr[4][3..0] = 10,11,12,13
i=4~6 (y 이동)  : y=3,2,1       &amp;rarr; arr[3..1][0] = 14,15,16

바퀴 종료 후 nDirection = +1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sourcepos=&quot;57:1-64:4;1488-1676&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;nLength=5, nDirection=+1, half=3, 시작 x=0, y=1

i=0~2 (x 이동)  : x=1,2,3       &amp;rarr; arr[1][1..3] = 17,18,19
i=3~4 (y 이동)  : y=2,3         &amp;rarr; arr[2..3][3] = 20,21

바퀴 종료 후 nDirection = -1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sourcepos=&quot;66:1-73:4;1678-1857&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;nLength=3, nDirection=-1, half=2, 시작 x=3, y=3

i=0~1 (x 이동)  : x=2,1         &amp;rarr; arr[3][2..1] = 22,23
i=2 (y 이동)    : y=2           &amp;rarr; arr[2][1] = 24

바퀴 종료 후 nDirection = +1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sourcepos=&quot;75:1-79:4;1859-1960&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;nLength=1, nDirection=+1, half=1, 시작 x=1, y=2

i=0 (x 이동)    : x=2           &amp;rarr; arr[2][2] = 25&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;81:1-81:48;1962-2009&quot; data-ke-size=&quot;size16&quot;&gt;25에서 정확히 끝나고, 결과는 switch 방식과 완전히 동일한 스파이럴이 나온다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;83:1-83:50;2011-2060&quot; data-ke-size=&quot;size16&quot;&gt;바퀴가 진행되며 nLength가 어떻게 안쪽으로 좁혀지는지는 아래 그림으로 표현했다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;85:1-85:115;2062-2176&quot; data-ke-size=&quot;size16&quot;&gt;각 사각형 테두리가 한 바퀴(nLength 값 하나)에 해당하고, 바깥쪽부터 안쪽으로 9 &amp;rarr; 7 &amp;rarr; 5 &amp;rarr; 3 &amp;rarr; 1 순서로 좁혀 들어간다. 색이 짙어질수록 나중 바퀴, 즉 배열 중심에 가까운 바퀴다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;87:1-87:9;2178-2186&quot; data-ke-size=&quot;size26&quot;&gt;특징 분석&lt;/h2&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-코드가 압도적으로 짧다 :&lt;/b&gt; switch 방식은 case 4개 + 인덱스 보정이 필요했지만, 이 방식은 이중 for문 하나로 전체 로직이 끝난다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-바퀴 수만큼만 바깥 루프가 돈다 :&lt;/b&gt;&amp;nbsp;switch 방식은 while 하나 안에서 25번(칸 수만큼) 전체 루프를 도는 반면, 이 방식은 바깥 for문이 5번(바퀴 수만큼)만 돈다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-홀수 정사각형 전용이다 :&amp;nbsp;&lt;/b&gt;nLength = 2*SIZE - 1로 시작해서 2씩 줄어드는 구조 자체가 홀수 크기를 전제로 짜여 있다. SIZE가 짝수면 nLength가 0에서 딱 안 떨어지거나 마지막 바퀴 처리가 어긋나서, 이 로직 자체를 다시 설계해야 한다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-직사각형(N&amp;times;M)으로 확장이 안 된다 :&lt;/b&gt;&amp;nbsp;nLength 하나로 가로/세로를 동시에 표현하는 구조라서, 가로세로 크기가 다른 배열에는 이 방식이 아예 성립하지 않는다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-가독성이 낮다 :&lt;/b&gt;&amp;nbsp;i &amp;lt; nLength/2 + 1이라는 조건만 보고 &quot;아, 절반까지는 x를 움직이고 나머지는 y를 움직이는구나&quot;를 바로 알아채기 어렵다. 처음 보는 사람은 손으로 트레이스를 돌려봐야 이해된다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;89:1-89:7;2188-2194&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-변형이 어렵다 :&lt;/b&gt; 예를 들어 반시계방향으로 바꾸거나 사각형이 아닌 패턴으로 확장하려면, 이 압축된 구조 자체를 뜯어고쳐야 한다. switch 방식처럼 case 순서만 바꾸는 식의 손쉬운 변형이 안 된다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;113:1-113:6;3267-3272&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-sourcepos=&quot;115:1-115:239;3274-3512&quot; data-ke-size=&quot;size16&quot;&gt;대칭 축소 방식은 &quot;짧고 영리한 코드&quot;의 전형이다. 문제를 홀수 정사각형이라는 좁은 범위로 한정하는 대신, 그 안에서 대칭성을 최대한 활용해 코드량을 확 줄인 것이다.&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/11</guid>
      <comments>https://codingnabi.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 8 Jul 2026 23:29:58 +0900</pubDate>
    </item>
    <item>
      <title>스파이럴(달팽이) 배열 구현하기 - switch/case 방향 전환 방식</title>
      <link>https://codingnabi.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C로 5x5 스파이럴 배열을 채우는 문제를 여러 방식으로 구현해보면서, 각 방식의 원리와 장단점을 정리해본다. 첫 번째로 다룰 방식은 switch문으로 방향을 관리하는 방식이다.(처음 문제를 보고 내가 푼 방식이라 많이 와일드 하다. 나머지 3개는 교재참고, 다른 자료를 참고한 방식이다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5x5 정수 배열을 아래처럼 시계방향 나선형으로 1부터 25까지 채우는 게 목표다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1   2   3   4    5
16  17  18  19   6
15  24  25  20   7
14  23  22  21   8
13  12  11  10   9&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방식 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 간단하다. 한 방향으로 갈 수 있는 데까지 가고, 막히면(이미 채워진 칸을 만나면) 다음 방향으로 90도 회전한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향은 4가지뿐이므로 enu으로 정의하고, switch로 지금 어느 방향으로 가는 중인지를 분기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 방향은 for 문 하나로 표현되고, 방향이 바뀔때마다 changeFlag를 다음 방향으로 갱신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 채워진 초깃값이 0이라는 점을 이용해 arr[y][x] == 0으로 값을 채울지, 아니면 루프를 종료하고 다음 분기로 넘어갈지 판단한다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;void fillSpiralBySwitch(int arr[SIZE][SIZE]) {
	enum {
		GORIGHT,
		DOWN,
		GOLEFT,
		UP
	};

	int changeFlag = GORIGHT;
	int value = 0;
	int x = 0;
	int y = 0;
	while (value &amp;lt; SIZE * SIZE) {
		switch (changeFlag) {
		case GORIGHT: {
			for (x; x &amp;lt; SIZE &amp;amp;&amp;amp; y &amp;gt;= 0 &amp;amp;&amp;amp; y &amp;lt; SIZE; x++) {
				if (arr[y][x] == 0)
					arr[y][x] = ++value;
				else
					break;
			}
			--x;
			++y;
			changeFlag = DOWN;
			break;
		}
		case DOWN: {
			for (y; x &amp;gt;= 0 &amp;amp;&amp;amp; x &amp;lt; SIZE &amp;amp;&amp;amp; y &amp;lt; SIZE; y++) {
				if (arr[y][x] == 0)
					arr[y][x] = ++value;
				else
					break;
			}
			--y;
			--x;
			changeFlag = GOLEFT;
			break;
		}
		case GOLEFT: {
			for (x; x &amp;gt;= 0 &amp;amp;&amp;amp; y &amp;gt;= 0 &amp;amp;&amp;amp; y &amp;lt; SIZE; x--) {
				if (arr[y][x] == 0)
					arr[y][x] = ++value;
				else
					break;
			}
			++x;
			--y;
			changeFlag = UP;
			break;
		}
		case UP: {
			for (y; y &amp;gt;= 0 &amp;amp;&amp;amp; x &amp;gt;= 0 &amp;amp;&amp;amp; x &amp;lt; SIZE; y--) {
				if (arr[y][x] == 0)
					arr[y][x] = ++value;
				else
					break;
			}
			++y;
			++x;
			changeFlag = GORIGHT;
			break;
		}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그림&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPwMR/dJMcagzni3A/m8Bv14RMyMJrShIdV566B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPwMR/dJMcagzni3A/m8Bv14RMyMJrShIdV566B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPwMR/dJMcagzni3A/m8Bv14RMyMJrShIdV566B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPwMR%2FdJMcagzni3A%2Fm8Bv14RMyMJrShIdV566B1%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;1424&quot; height=&quot;1326&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계&amp;nbsp;GORIGHT&amp;nbsp;:&amp;nbsp;(0,0)&amp;nbsp;&amp;rarr;&amp;nbsp;(1,0)&amp;nbsp;&amp;rarr;&amp;nbsp;(2,0)&amp;nbsp;&amp;rarr;&amp;nbsp;(3,0)&amp;nbsp;&amp;rarr;&amp;nbsp;(4,0)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[1~5&amp;nbsp;채움] &lt;br /&gt;&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;&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;for&amp;nbsp;종료&amp;nbsp;시&amp;nbsp;x=5,&amp;nbsp;보정&amp;nbsp;--x&amp;nbsp;&amp;rarr;&amp;nbsp;x=4 &lt;br /&gt;&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;&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;++y&amp;nbsp;&amp;rarr;&amp;nbsp;y=1 &lt;br /&gt;&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;&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;changeFlag&amp;nbsp;=&amp;nbsp;DOWN &lt;br /&gt;&lt;br /&gt;2단계&amp;nbsp;DOWN&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;(4,1)&amp;nbsp;&amp;rarr;&amp;nbsp;(4,2)&amp;nbsp;&amp;rarr;&amp;nbsp;(4,3)&amp;nbsp;&amp;rarr;&amp;nbsp;(4,4)&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;[6~9&amp;nbsp;채움] &lt;br /&gt;&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;&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;for&amp;nbsp;종료&amp;nbsp;시&amp;nbsp;y=5,&amp;nbsp;보정&amp;nbsp;--y&amp;nbsp;&amp;rarr;&amp;nbsp;y=4 &lt;br /&gt;&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;&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;--x&amp;nbsp;&amp;rarr;&amp;nbsp;x=3 &lt;br /&gt;&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;&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;changeFlag&amp;nbsp;=&amp;nbsp;GOLEFT &lt;br /&gt;&lt;br /&gt;3단계&amp;nbsp;GOLEFT&amp;nbsp;&amp;nbsp;:&amp;nbsp;(3,4)&amp;nbsp;&amp;rarr;&amp;nbsp;(2,4)&amp;nbsp;&amp;rarr;&amp;nbsp;(1,4)&amp;nbsp;&amp;rarr;&amp;nbsp;(0,4)&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;[10~13&amp;nbsp;채움] &lt;br /&gt;&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;&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;for&amp;nbsp;종료&amp;nbsp;시&amp;nbsp;x=-1,&amp;nbsp;보정&amp;nbsp;++x&amp;nbsp;&amp;rarr;&amp;nbsp;x=0 &lt;br /&gt;&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;&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;--y&amp;nbsp;&amp;rarr;&amp;nbsp;y=3 &lt;br /&gt;&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;&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;changeFlag&amp;nbsp;=&amp;nbsp;UP &lt;br /&gt;&lt;br /&gt;4단계&amp;nbsp;UP&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;(0,3)&amp;nbsp;&amp;rarr;&amp;nbsp;(0,2)&amp;nbsp;&amp;rarr;&amp;nbsp;(0,1)&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;[14~16&amp;nbsp;채움] &lt;br /&gt;&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;(0,0)은&amp;nbsp;이미&amp;nbsp;채워져&amp;nbsp;있어&amp;nbsp;break &lt;br /&gt;&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;&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;for&amp;nbsp;종료&amp;nbsp;시&amp;nbsp;y=0,&amp;nbsp;보정&amp;nbsp;++y&amp;nbsp;&amp;rarr;&amp;nbsp;y=1 &lt;br /&gt;&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;&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;++x&amp;nbsp;&amp;rarr;&amp;nbsp;x=1 &lt;br /&gt;&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;&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;changeFlag&amp;nbsp;=&amp;nbsp;GORIGHT &lt;br /&gt;&lt;br /&gt;5단계&amp;nbsp;GORIGHT&amp;nbsp;:&amp;nbsp;(1,1)&amp;nbsp;&amp;rarr;&amp;nbsp;(2,1)&amp;nbsp;&amp;rarr;&amp;nbsp;(3,1)&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;[17~19&amp;nbsp;채움] &lt;br /&gt;&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;(4,1)은&amp;nbsp;이미&amp;nbsp;채워져&amp;nbsp;있어&amp;nbsp;break &lt;br /&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 가독성이 나쁘지 않다 :&lt;/b&gt; 어느 방향으로 가는지 코드 구조에 드러난다. case만 따라가다 보면 로직 전체를 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 디버깅 용이 :&lt;/b&gt; 방향별로 로직이 분리되어 있어, 특정 방향에서 생기는 문제가 생기면 그 case만 보면 된다.&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;- 직사각형 확장이 비교적 쉽다 :&lt;/b&gt; x, y의 이동 범위가 이미 분리되어 있어서 가로세로 크기를 다르게 둬도 구조 자체는 크게 바뀌지 않는다.&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;- 코드가 길다 :&amp;nbsp;&lt;/b&gt;방향이 4개면 case도 4개, 로직도 4개가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- for문에 인덱스 보정이 필요하다 :&amp;nbsp;&lt;/b&gt;각 방향의 for문이 조건을 만족하지 못하는 시점(예: x == SIZE)에 끝나기 때문에, case 끝마다 x,y값을 보정하는 보정 코드가 따라붙는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 배열 값에 의존한다 :&amp;nbsp;&lt;/b&gt;&quot;0이면 채워지지 않은 칸&quot;이라는 전제로 동작하기 때문에, 채우는 값 자체가 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;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/10</guid>
      <comments>https://codingnabi.tistory.com/10#entry10comment</comments>
      <pubDate>Wed, 8 Jul 2026 19:39:20 +0900</pubDate>
    </item>
    <item>
      <title>선택정렬</title>
      <link>https://codingnabi.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;선택정렬의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 최솟값 선택 기반 정렬&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;바깥 루프가 n번 돌고, 안쪽 루프(최솟값 탐색)는 바깥 루프가 진행될수록 반복 횟수가 점점 줄어들어(n,n-1,n-2,...,1) 총 비교 횟수는 버블 정렬과 똑같이 등차수열의 합 공식에 의해 n(n-1)/2번이 된다. 빅오 표기법으로는 O(n&amp;sup2;)이다. 다만 버블정렬과 결정적으로 다른 점은, 배열이 이미 정렬되어 있어도, 안쪽 루프를 끝까지 다 돌아야 최솟값 위치를 확신할 수 있어서 최선의 경우에도 O(n&amp;sup2;)로 줄어들지 않는다는 것이다.&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;추가 배열이나 메모리 공간이 거의 필요없다. temp변수 하나로 교환만 하면 되니까 공간복잡도는 O(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;5. 실전성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전성은 버블정렬보단 낫지만, 여전히 낮다. 같은 O(n&amp;sup2;) 계열에선 삽입 정렬이 대부분의 경우 더 빠르지만, 선택 정렬은 교환(swap) 횟수가 라운드당 1번, 전체 최대로도 n-1번으로 압도적으로 적다는 장점이 있다. 그러나 큰 데이터셋에는 여전히 O(n log n)인 다른 정렬법을 사용한다.&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;6. 플래그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-newtext-seq=&quot;2&quot;&gt;앞서 나는 &lt;/span&gt;&lt;span data-newtext-seq=&quot;8&quot;&gt;버블정렬에서 플래그를 이용하여 루프 &lt;/span&gt;&lt;span data-newtext-seq=&quot;28&quot;&gt;횟수를 줄이는 방식을 &lt;/span&gt;&lt;span data-newtext-seq=&quot;40&quot;&gt;사용했다. 그러나 선택정렬에서는 &lt;/span&gt;&lt;span data-newtext-seq=&quot;58&quot;&gt;이 방식을 사용할 수 &lt;/span&gt;&lt;span data-newtext-seq=&quot;70&quot;&gt;없다. 그 이유는 &quot;정렬이 &lt;/span&gt;&lt;span data-newtext-seq=&quot;85&quot;&gt;끝났다는 확신&quot;을 얻는 방식 &lt;/span&gt;&lt;span data-newtext-seq=&quot;101&quot;&gt;자체가 다르기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-newtext-seq=&quot;119&quot;&gt;버블정렬은 한 바퀴 돌면서 &lt;/span&gt;&lt;span data-newtext-seq=&quot;134&quot;&gt;인접한 칸끼리 비교하며 &lt;/span&gt;&lt;span data-newtext-seq=&quot;147&quot;&gt;스왑한다. 따라서 한 바퀴에서 스왑이 &lt;/span&gt;&lt;span data-newtext-seq=&quot;168&quot;&gt;한 번도 발생하지 않았다는 &lt;/span&gt;&lt;span data-newtext-seq=&quot;183&quot;&gt;것은, 그 자체로 이미 배열이 &lt;/span&gt;&lt;span data-newtext-seq=&quot;200&quot;&gt;정렬되어 있다는 것을 증명해준다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span data-newtext-seq=&quot;220&quot;&gt;반면 선택정렬은 &lt;/span&gt;&lt;span data-newtext-seq=&quot;232&quot;&gt;i번째부터 끝까지 중에서 최솟값의 &lt;/span&gt;&lt;span data-newtext-seq=&quot;251&quot;&gt;위치를 찾는 과정이다. 이 &lt;/span&gt;&lt;span data-newtext-seq=&quot;266&quot;&gt;최솟값이 진짜 최솟값인지 &lt;/span&gt;&lt;span data-newtext-seq=&quot;280&quot;&gt;확신하려면, 남은 원소를 전부 다 &lt;/span&gt;&lt;span data-newtext-seq=&quot;299&quot;&gt;비교해봐야만 한다. 배열이 이미 정렬되어 &lt;/span&gt;&lt;span data-newtext-seq=&quot;322&quot;&gt;있어도 선택정렬 입장에서는 그 &lt;/span&gt;&lt;span data-newtext-seq=&quot;339&quot;&gt;사실을 모르기 때문에, 매 &lt;/span&gt;&lt;span data-newtext-seq=&quot;354&quot;&gt;라운드마다 마지막 칸까지 비교를 &lt;/span&gt;&lt;span data-newtext-seq=&quot;372&quot;&gt;마쳐야만 &quot;이게 최솟값이 맞다&quot;는 &lt;/span&gt;&lt;span data-newtext-seq=&quot;391&quot;&gt;확신을 얻을 수 있다. 그래서 &lt;/span&gt;&lt;span data-newtext-seq=&quot;408&quot;&gt;이전 라운드에서 값이 &lt;/span&gt;&lt;span data-newtext-seq=&quot;420&quot;&gt;갱신되지 않았다는 사실이, 다음 &lt;/span&gt;&lt;span data-newtext-seq=&quot;438&quot;&gt;라운드의 비교를 생략할 근거가 &lt;/span&gt;&lt;span data-newtext-seq=&quot;455&quot;&gt;되지 못한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정렬 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KRQ83/dJMcahrzlr6/raRin1kkRWKwXx0k8tZIB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KRQ83/dJMcahrzlr6/raRin1kkRWKwXx0k8tZIB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KRQ83/dJMcahrzlr6/raRin1kkRWKwXx0k8tZIB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKRQ83%2FdJMcahrzlr6%2FraRin1kkRWKwXx0k8tZIB1%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;1442&quot; height=&quot;980&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작성한 코드&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main() {
	int aList[5] = { 30, 40, 10, 50, 20 };
	int aListSize = sizeof(aList) / sizeof(int);
	int rotationCount = 0;

	for (int i = 0; i &amp;lt; aListSize; i++) {
		int temp = 0;
		int nMinIndex = i;
		for (int j = i; j &amp;lt; aListSize; j++) {
			if (aList[nMinIndex] &amp;gt; aList[j]) {
				nMinIndex = j; 
			}
		}
		temp = aList[i];
		aList[i] = aList[nMinIndex];
		aList[nMinIndex] = temp;
		++rotationCount;
	}

	for (int i = 0; i &amp;lt; aListSize; i++) {
		printf(&quot;%d\t&quot;, aList[i]);
	}
	printf(&quot;\nRotation Count : %d&quot;, rotationCount);

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHQ0Y1/dJMcafmZidx/NU4ERCOgvcJY5gFUfl1ss1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHQ0Y1/dJMcafmZidx/NU4ERCOgvcJY5gFUfl1ss1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHQ0Y1/dJMcafmZidx/NU4ERCOgvcJY5gFUfl1ss1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHQ0Y1%2FdJMcafmZidx%2FNU4ERCOgvcJY5gFUfl1ss1%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;591&quot; height=&quot;67&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/9</guid>
      <comments>https://codingnabi.tistory.com/9#entry9comment</comments>
      <pubDate>Tue, 7 Jul 2026 18:05:55 +0900</pubDate>
    </item>
    <item>
      <title>우리는 왜 UTF-8을 써야 하는가</title>
      <link>https://codingnabi.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*아래 글은 Claude AI가 저와 대화하면서 나눈 내용을 정리한 것입니다. 결론은 제가 정리하였고, 최대한 공식 문서만을 반영하려고 하였습니다. 참고 자료는 글의 마지막에 정리하여 두었습니다. 부족한 부분이 있다면 지적해주시면 감사하겠습니다.&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;Windows에서 C/C++로 콘솔 프로그램을 짜본 사람이라면 한 번쯤 겪어봤을 것이다. 분명 소스 코드에는 멀쩡하게 한글을 입력했는데, 컴파일 시점에 알 수 없는 경고가 뜨거나, 실행했을 때 콘솔 화면에 글자가 물음표나 깨진 문자로 출력되는 현상 말이다. 이 글에서는 이 현상이 왜 발생하는지, 그리고 표준 문서와 실제 업계가 어떤 방향을 가리키고 있는지를 정리한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 인코딩 방식 정리: UTF-8, BOM, CP949, UTF-16&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자를 컴퓨터에 저장하려면 결국 &quot;이 글자를 몇 바이트로, 어떤 규칙으로 표현할 것인가&quot;를 정해야 한다. 이 규칙을 인코딩(encoding)이라고 부른다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UTF-8&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드 문자를 1바이트에서 4바이트 사이의 가변 길이로 표현하는 인코딩이다. 영문/숫자(ASCII 범위)는 1바이트로 그대로 표현되고, 한글 같은 non-ASCII 문자는 3바이트로 표현된다. 예를 들어 &quot;안&quot;이라는 글자는 UTF-8에서 EC 95 88 세 바이트로 저장된다. 현재 웹, 대부분의 운영체제, 대부분의 프로그래밍 언어에서 사실상 표준으로 자리 잡은 인코딩이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BOM (Byte Order Mark)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BOM은 파일 맨 앞에 붙는 특수한 바이트 시퀀스로, UTF-8의 경우 EF BB BF 세 바이트가 여기에 해당한다. 이 바이트를 유니코드로 디코딩하면 U+FEFF(ZERO WIDTH NO-BREAK SPACE)라는 문자 코드가 되는데, 이 문자를 파일 맨 앞에 실제 텍스트의 일부로 남겨두지 않고 &quot;이 파일이 무슨 인코딩인지 알려주는 신호&quot;로만 쓰겠다는 것이 BOM의 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BOM의 원래 목적은 UTF-16처럼 한 글자를 여러 바이트로 표현하면서 바이트 순서(리틀엔디안/빅엔디안)가 모호해질 수 있는 인코딩에서, 그 순서를 알려주는 것이다. UTF-8은 한 바이트씩 순서대로 쌓아나가는 방식이라 바이트 순서 문제 자체가 없다. 그래서 유니코드 표준 원문(Unicode Standard, 2.6절 &quot;Encoding Schemes&quot;)은 다음과 같이 명시한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Use of a BOM is neither required nor recommended for UTF-8&quot; (BOM 사용은 UTF-8에서 필수도 아니고 권장되지도 않는다)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 문장의 해석을 두고는 유니코드 컨소시엄 내부에서도 이견이 있었다. 2021년 유니코드 기술위원회에 제출된 문서(L2/21-038)는, 이 문장이 &quot;BOM 사용에 반대하는 권고&quot;로 읽히기도 하고 &quot;찬성도 반대도 아닌 무입장&quot;으로 읽히기도 한다는 점을 스스로 지적하며 문구의 모호함을 인정한 바 있다. 즉 &quot;BOM을 쓰지 마라&quot;는 강한 금지라기보다는, &quot;굳이 필요하지 않으니 기본값으로 삼지는 말라&quot;는 정도의 방향성으로 이해하는 편이 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편, 이 문제를 실무적으로 다뤄온 한 개발자의 글(Manski's Dev Log, &quot;UTF-8 and BOM&quot;)은 다음과 같은 절충안을 제시한다. 평상시 파일은 BOM 없는 UTF-8을 기본으로 하되, 최종 사용자에게 노출되는 텍스트가 포함된 코드 파일이나 리소스 파일에 한해서는 인코딩 오류(이상한 문자 출력)를 막기 위해 BOM이 있는 UTF-8로 저장하라는 것이다. 이 절충안은 뒤에서 다룰 우리의 실제 상황(콘솔에 한글을 직접 출력하는 소스 파일)에 정확히 들어맞는 조언이기도 하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CP949 (한국어 Windows 기본 코드페이지)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CP949는 한글을 2바이트로 표현하는 완성형 인코딩으로, EUC-KR을 확장한 한국어 Windows의 기본 코드페이지다. 예를 들어 &quot;안&quot;이라는 글자는 CP949에서 B0 C8 두 바이트로 저장된다. UTF-8과는 완전히 다른 바이트 체계이며, 한국어 Windows에서만 통용되는 지역화된 인코딩이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UTF-16&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 글자를 기본적으로 2바이트로 표현하는 인코딩으로, Windows 내부(NT 커널)와 C#의 string, C++의 wchar_t 등에서 사용된다. UTF-8과는 바이트 구조 자체가 다르기 때문에, 두 인코딩 사이를 오갈 때는 반드시 변환 과정을 거쳐야 한다. BOM이 있다고 해서 UTF-8과 UTF-16이 서로 호환되는 것은 아니다. BOM은 그저 &quot;이 파일이 셋 중 무엇인지&quot;를 알려주는 라벨일 뿐, 인코딩 간 변환을 대신해주지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 어디서, 왜 글자가 깨지는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글이 깨지는 현상은 하나의 원인이 아니라, 아래와 같은 파이프라인의 여러 단계에서 발생할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[.cpp 소스 파일]  &amp;rarr;  [컴파일러가 소스를 읽음]  &amp;rarr;  [실행 파일(.exe) 생성]  &amp;rarr;  [콘솔이 출력을 해석]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 소스 파일을 컴파일러가 잘못 해석하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSVC 컴파일러는 소스 파일 맨 앞에 BOM이 있는지를 확인해, BOM이 있으면 해당 인코딩으로 정확히 읽고, 없으면 시스템 코드페이지(한국어 Windows에서는 CP949)로 간주해버린다. 이는 Microsoft 공식 문서에도 명시되어 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;By default, Visual Studio detects a byte-order mark... If no byte-order mark is found, it assumes that the source file is encoded in the current user code page, unless you've specified a code page by using /utf-8 or the /source-charset option.&quot; (출처: Microsoft Learn, /utf-8 컴파일러 옵션 문서)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 실제 파일이 BOM 없는 UTF-8로 저장되어 있다면, 컴파일러는 이를 CP949로 잘못 해석한다. 이때 한글을 표현하는 UTF-8 멀티바이트 시퀀스가 우연히 개행 문자와 비슷한 바이트 조합으로 해석되면서, 문자열 리터럴이 예상치 못한 곳에서 끊기는 등 실제 컴파일 오류(C2001, C2143 등)로 이어지기도 한다. 이 경고는 C4819(&quot;현재 코드 페이지에서 표시할 수 없는 문자&quot;)로 나타나는데, 이는 경고일 뿐이라 빌드 자체는 통과하는 경우가 많다. 다만 빌드가 통과했다는 것이 인코딩이 올바르다는 뜻은 아니며, 실제로는 손상된 문자열이 실행 파일에 그대로 저장된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 실행 파일에 문자열이 저장되는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 소스를 정확히 읽었다 하더라도, 그 문자열을 실행 파일 안에 저장할 때 어떤 인코딩으로 저장할지(실행 문자집합, execution charset)는 별개의 문제다. MSVC는 별도 옵션이 없으면 기본적으로 narrow 문자열(char*)을 시스템 코드페이지로 변환해 저장한다. 이는 1990년대 Windows API가 설계될 때부터 이어진 하위 호환성 정책이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 콘솔이 출력을 해석하는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔(cmd.exe)은 자신만의 기본 코드페이지를 갖고 있으며, 한국어 Windows에서는 이 값이 기본적으로 CP949다. 실행 파일 안에 저장된 문자열의 인코딩과 콘솔이 그것을 해석하는 방식이 일치해야만 화면에 정상적으로 출력된다. 즉 다음 네 가지 조합 중 인코딩이 서로 일치하는 경우에만 정상 출력이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 파일 안 문자열 콘솔의 해석 방식 결과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CP949&lt;/td&gt;
&lt;td&gt;CP949&lt;/td&gt;
&lt;td&gt;정상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;정상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CP949&lt;/td&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;깨짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;CP949&lt;/td&gt;
&lt;td&gt;깨짐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 실무에서 쓰여 온 BOM, /utf-8 컴파일러 옵션, SetConsoleOutputCP, 시스템 로캘의 UTF-8 설정은 모두 이 &quot;인코딩&quot;과 &quot;디코딩&quot; 단계를 서로 일치시키기 위한 각기 다른 수단일 뿐이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 표준 문서와 업계의 실제 관행&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유니코드 표준과 C++ 표준위원회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드 표준은 UTF-8을 텍스트 교환의 사실상 표준으로 삼으면서도, BOM에 대해서는 앞서 살펴본 것처럼 다소 모호한 태도(&quot;필수도 권장도 아니다&quot;)를 취하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 표준위원회(WG21)가 제출한 UTF-8 소스 인코딩 지원 제안서(P2295R3)는 이 지점을 조금 더 명확히 한다. 이 문서는 BOM이 파일에 존재하더라도 그것을 실제 텍스트 콘텐츠로 취급해서는 안 되며(무시되어야 하며), 다만 MSVC처럼 BOM을 UTF-8 감지 수단으로 활용하는 구현 방식도 유효하다고 인정한다. 여기서 &quot;무시되어야 한다&quot;는 것은 &quot;BOM을 쓰지 말라&quot;는 뜻이 아니라, &quot;파일을 읽는 도구가 BOM을 감지에는 활용하되, 그 바이트 자체를 실제 콘텐츠의 일부(눈에 보이지 않는 유령 문자)로 남겨서는 안 된다&quot;는, 도구 구현자를 향한 요구사항에 가깝다. 표준위원회는 BOM 사용 자체를 강제하지도 금지하지도 않는 입장을 취하되, 모든 C++ 컴파일러가 UTF-8 소스 파일을 최소한 지원해야 한다는 방향으로 논의를 진행하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대형 오픈소스 프로젝트의 실제 관행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구글 C++ 스타일 가이드&lt;/b&gt;(공식 문서, google.github.io/styleguide): 소스 코드에 non-ASCII 문자가 필요한 경우 UTF-8 형식을 사용하도록 명시하고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Clang 컴파일러의 동작 방식&lt;/b&gt;: Clang은 소스 파일을 항상 UTF-8로 가정하며, BOM 유무와 무관하게 이 원칙을 고수하는 것으로 알려져 있다. 이 설명은 Clang 공식 문서가 아니라, 이를 분석한 제3자 기술 블로그(Nutrient.io, &quot;String Literals, Character Encodings, and Multiplatform C++&quot;)를 참고했다. 실행 문자집합 변환 역시 UTF-8을 기준으로 처리되어, MSVC와 같은 레거시 코드페이지 변환 단계가 없다는 점도 이 글에서 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개별 프로젝트의 실무 절충안&lt;/b&gt;: 한 개발자가 자신의 프로젝트에 채택한 정책(Manski's Dev Log, &quot;UTF-8 and BOM&quot;)을 보면, 기본은 BOM 없는 UTF-8로 하되, 컴파일러/인터프리터가 BOM 없이도 UTF-8을 기본으로 가정하는 환경에서는 이 원칙이 무리 없이 성립하고, 최종 사용자에게 직접 노출되는 텍스트가 포함된 파일에는 예외적으로 BOM을 허용해 인코딩 오류를 막는 방식을 취하고 있다. 이는 LLVM이나 특정 대형 프로젝트의 공식 정책이 아니라 한 개인 프로젝트의 사례이지만, 실무에서 통용되는 절충 논리를 잘 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Microsoft 공식 문서의 입장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft는 /utf-8 컴파일러 옵션을 공식 문서에서 안내하며, 기본적으로 BOM이 없으면 소스 파일을 현재 사용자 코드 페이지로 가정한다는 점, 그리고 /utf-8 옵션으로 이를 UTF-8로 고정할 수 있다는 점을 명시하고 있다(Microsoft Learn, /utf-8(원본 및 실행 문자 집합을 UTF-8로 설정) 문서). 아울러 Windows 10 버전 1903부터는 애플리케이션 매니페스트에 activeCodePage를 UTF-8로 지정해, 프로세스 전체의 기본 코드페이지 자체를 UTF-8로 고정하는 방법도 공식적으로 제공하고 있다(Microsoft Learn, &quot;Windows 앱에서 UTF-8 코드 페이지 사용&quot; 문서).&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 문서와 업계 사례를 종합하면 방향은 비교적 뚜렷하다. 인코딩은 UTF-8을 쓰는 것이 사실상 이견 없는 정석이며, BOM에 대해서는 유니코드 표준 자체가 명확히 &quot;쓰지 말라&quot;고 강하게 못박고 있다기보다는 &quot;굳이 필요하지 않다&quot;는 쪽에 무게를 두고 있고, 실무에서는 도구의 특성에 따라 예외적으로 BOM을 허용하는 절충안이 흔히 쓰인다. Windows/MSVC 환경에서 BOM이 여전히 실질적으로 쓰이는 것은, 표준을 어기는 것이라기보다 MSVC라는 특정 도구가 BOM 없이는 UTF-8을 기본으로 가정하지 않는다는 특수한 사정에서 비롯된 실용적 선택에 가깝다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 그래서 우리는 어떻게 설정해야 하는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 오늘날 권장되는 조합은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;소스 파일:     BOM 없는 UTF-8로 저장 (또는 BOM 있는 UTF-8도 무방)
프로젝트 설정:  /utf-8 컴파일러 옵션 추가
콘솔 출력이 필요한 경우: SetConsoleOutputCP(CP_UTF8) 추가
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Visual Studio 설정 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 프로젝트에 /utf-8 옵션 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;프로젝트 우클릭 &amp;rarr; 속성 &amp;rarr; 구성 속성 &amp;rarr; C/C++ &amp;rarr; 명령줄 &amp;rarr; 추가 옵션
&amp;rarr; /utf-8 입력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션 하나로 소스 문자집합과 실행 문자집합이 모두 UTF-8로 고정된다. BOM 유무와 무관하게 소스를 정확히 UTF-8로 인식하게 되므로, 파일마다 저장 방식을 신경 쓸 필요가 없어진다. 즉 /utf-8을 걸어두면, MSVC 역시 &quot;BOM 없이도 UTF-8을 기본으로 가정하는&quot; 환경이 되어, 앞서 살펴본 절충안의 전제 조건을 스스로 만족시키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 콘솔 출력 코드 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;Windows.h&amp;gt;

int main() {
    SetConsoleOutputCP(CP_UTF8);
    printf(&quot;안녕하세요.&quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/utf-8을 적용하면 실행 파일 안 문자열이 UTF-8로 유지되는데, 콘솔의 기본 코드페이지는 여전히 CP949이므로 이 한 줄이 없으면 콘솔에서 다시 깨진다. SetConsoleOutputCP는 해당 콘솔 창 전체의 설정을 바꾸는 것이므로, 필요하다면 프로그램 종료 전에 GetConsoleOutputCP()로 저장해둔 원래 값으로 복구해주는 것이 매너 있는 방식이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼에도 BOM이 여전히 유효한 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/utf-8 옵션을 걸 수 없거나, 프로젝트 전역 설정을 건드리기 어려운 상황(예: 파일 하나만 급하게 고쳐야 하는 경우)이라면, 앞서 소개한 절충안처럼 최종 사용자에게 직접 보이는 텍스트가 담긴 파일에 한해 BOM을 붙이는 것도 실무적으로 유효한 선택이다. 다만 이 경우 실행 파일 안 문자열이 시스템 코드페이지(CP949)로 변환되어 저장된다는 점을 유의해야 한다. 콘솔 출력만 놓고 보면 문제가 없지만, 네트워크로 들어오는 UTF-8 데이터(예: 채팅 메시지)와 소스에 적어놓은 한글 리터럴을 비교하는 로직에서는 두 값이 서로 다른 바이트가 되어 비교 자체가 실패한다. 콘솔에 보기 좋게 출력되는 것과 실제 데이터 처리 로직이 올바르게 동작하는 것은 별개의 문제이며, 후자를 보장하려면 BOM만으로는 부족하고 반드시 /utf-8이 필요하다.&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;p data-ke-size=&quot;size16&quot;&gt;결국 이 모든 논의를 한 문장으로 요약하자면 글자가 깨지는 것은 &quot;인코딩&quot;과 &quot;디코딩&quot;이라는 두 단계가 서로 어긋났기 때문이며, 이를 막는 가장 확실한 방법은 소스 파일부터 콘솔 출력까지 모든 계층을 UTF-8 하나로 통일하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이런 상황이 &quot;BOM을 절대 쓰지 말라&quot;는 의미는 아니다. 표준이 가리키는 방향은 &quot;새 파일을 만들 때는 BOM 없는 UTF-8을 기본으로 하되, BOM이 붙은 파일을 만나더라도 도구가 오작동 없이 처리할 수 있어야 한다&quot;는 것이고, 개인 학습이나 콘솔 출력 정도의 목적이라면 BOM을 쓰는 것도 여전히 실용적인 선택이다. 다만 네트워크로 들어오는 UTF-8 데이터와 소스 코드의 문자열을 직접 비교해야 하는 순간부터는, BOM만으로는 부족하고 반드시 /utf-8처럼 실행 문자집합까지 UTF-8로 고정하는 설정이 필요해진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google C++ Style Guide &amp;mdash; &lt;a href=&quot;https://google.github.io/styleguide/cppguide.html&quot;&gt;https://google.github.io/styleguide/cppguide.html&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 자료의 Non-ASCII부분에서 참고하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ISO/IEC JTC1/SC22/WG21, P2295R3 &quot;Support for UTF-8 as a portable source file encoding&quot; &amp;mdash; &lt;a href=&quot;https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2295r3.pdf&quot;&gt;https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2295r3.pdf&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PDF 안에서 &quot;BOM&quot; 검색 -&amp;gt; 해당 부분 참고&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Unicode Technical Committee, L2/21-038 &quot;Clarify guidance for use of a BOM as a UTF-8 encoding signature&quot; &amp;mdash; &lt;a href=&quot;https://www.unicode.org/L2/L2021/21038-bom-guidance.pdf&quot;&gt;https://www.unicode.org/L2/L2021/21038-bom-guidance.pdf&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PDF 첫 페이지 도입부,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;Unicode 13, in the 'Byte Order' subsection of section 원문 인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia, &quot;Byte order mark&quot; &amp;mdash; &lt;a href=&quot;https://en.wikipedia.org/wiki/Byte_order_mark&quot;&gt;https://en.wikipedia.org/wiki/Byte_order_mark&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서 내 &quot;UTF-8&quot; 소제목에서 참&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Learn, /utf-8(원본 및 실행 문자 집합을 UTF-8로 설정) &amp;mdash; &lt;a href=&quot;https://learn.microsoft.com/ko-kr/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8&quot;&gt;https://learn.microsoft.com/ko-kr/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 상단, &quot;설명(Remarks)&quot; 또는 본문 도입부에서 참고&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Learn, &quot;Windows 앱에서 UTF-8 코드 페이지 사용&quot; &amp;mdash; &lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/apps/design/globalizing/use-utf8-code-page&quot;&gt;https://learn.microsoft.com/ko-kr/windows/apps/design/globalizing/use-utf8-code-page&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UTF-8 코드 페이지&quot; 관련 첫 단락 부분 참&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Manski's Dev Log, &quot;UTF-8 and BOM&quot; (개인 프로젝트 사례) &amp;mdash; &lt;a href=&quot;https://manski.net/articles/utf8-bom&quot;&gt;https://manski.net/articles/utf8-bom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nutrient.io, &quot;String Literals, Character Encodings, and Multiplatform C++&quot; (제3자 기술 블로그) &amp;mdash; &lt;a href=&quot;https://www.nutrient.io/blog/string-literals-character-encodings-and-multiplatform-cpp/&quot;&gt;https://www.nutrient.io/blog/string-literals-character-encodings-and-multiplatform-cpp/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;msvc 인코딩 옵션 이해 (GitHub Gist) &amp;mdash;&lt;span&gt; &lt;a href=&quot;https://gist.github.com/a9746b9ff99a76582c29ca6f956b219b&quot;&gt;https://gist.github.com/a9746b9ff99a76582c29ca6f956b219b&lt;/a&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부</category>
      <author>CodingNabi</author>
      <guid isPermaLink="true">https://codingnabi.tistory.com/8</guid>
      <comments>https://codingnabi.tistory.com/8#entry8comment</comments>
      <pubDate>Tue, 7 Jul 2026 15:20:48 +0900</pubDate>
    </item>
  </channel>
</rss>