<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pine.log</title>
        <link>https://velog.io/</link>
        <description>공부정리용</description>
        <lastBuildDate>Sun, 27 Mar 2022 07:15:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pine.log</title>
            <url>https://images.velog.io/images/mi_pine/profile/2b5ab6df-7fe8-4b4b-9546-1136c836b385/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pine.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mi_pine" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[📘 유체 시뮬레이션(2) - FLIP]]></title>
            <link>https://velog.io/@mi_pine/%EC%9C%A0%EC%B2%B4-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%982-FLIP</link>
            <guid>https://velog.io/@mi_pine/%EC%9C%A0%EC%B2%B4-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%982-FLIP</guid>
            <pubDate>Sun, 27 Mar 2022 07:15:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/mi_pine/post/9ef67ffc-2fd3-4377-9b12-fab279b9fc80/%EC%9C%A0%EC%B2%B4%20%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98.JPG" alt=""></p>
<p>이전 포스트에서 나비에-스토크스 방정식을 이산화 하는 방법에 대해 알아보았다. </p>
<p><a href="https://velog.io/@mi_pine/%EC%9C%A0%EC%B2%B4-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%981-Navier-Stokes-%EB%B0%A9%EC%A0%95%EC%8B%9D-%EC%9D%B4%EC%82%B0%ED%99%94">📘 유체 시뮬레이션(1) - Navier-Stokes 방정식</a></p>
<p>압력 투영(pressure projection)은 그리드를 이용하여 문제를 해결하지만, 이류(advection)는 압력을 이용한 해결이 어려워 그리드 내 입자를 이용하여 풀어야 했다. </p>
<p>그렇기 때문에 파티클에 대한 물리량을 그리드로 바꾸고 저장하는 과정과, 반대로 그리드의 물리량을 파티클에 저장하는 과정이 필요하다. </p>
<p>이 과정은 크게 PIC(Particle-In-Cell)과, FLIP(Fluid-Implicit-Particle) 두가지 방법으로 나뉜다. </p>
<p>이 중 특히 FLIP에 대하여 자세히 알아보고자 한다. </p>
<p>Ships, Splashes, and Waves on a Vast Ocean 논문에서는 전반적인 FLIP과정에서 Animating Sand as a Fluid 논문에 제시된 방법을 따랐다고 되어 있다. </p>
<p>따라서 Animating Sand as a Fluid 에서 사용한 방법을 기반으로 알아보았다. </p>
<br> 

<blockquote>
<p><strong>FLIP pseudocode</strong></p>
</blockquote>
<p>1) 입자 값 q_p를 그리드 값 q_i,j,k로 바꾼다. 
2) 그리드 값 q_i,j,k를 그리드에 저장한다. 
3) 압력 투영, 외부 힘 등을 계산하여 그 결과인 q(new)_i,j,k를 얻는다. 
4) 각각의 입자에 대해 그리드에서 변경된 값 (delta)q_i,j,k = q(new)_i,j,k - q_i,j,k를 보간하여 입자의 값에 추가한다.
5) 그리드 속도장 내 입자들에 이류를 적용한다. </p>
<p>(q: 물리량. 속도에 대해 다루고 있으므로 아래에서는 q를 u로 바꿔서 서술하였다) </p>
<p><img src="https://images.velog.io/images/mi_pine/post/f7287106-62aa-4a00-81b6-34b1ebb77c58/image.png" alt=""></p>
<p>각 단계들을 좀 더 자세히 살펴보자. </p>
<br>

<p>** 1,2) Particle to Grid - 입자의 값 q_p를 그리드 값 q_i,j,k로 바꾸고 저장 ** </p>
<p><img src="https://images.velog.io/images/mi_pine/post/1f907a39-13cb-432f-b8a1-f77bd7f978a4/image.png" alt=""></p>
<p>논문에서는 2x2x2 크기의 그리드 하나에 8개의 입자를 담았다. </p>
<p>그런다음 각 그리드 포인트는 근처 입자들의 가중 평균 값을 가지게 된다. 
여기서 &#39;근처&#39;의 기준은 그리드 점을 중심으로 한 그리드 셀 폭의 2배인 큐브에 포함되는 것으로 정의하였다. </p>
<p>이걸 수식으로 나타내면 아래와 같을 것이다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/25dbabe0-6c27-49a3-97e5-e2de10a2071e/image.png" alt="">
(점 A에 저장되는 속도의 값) </p>
<p>가중치는 standard trilinear weighting을 이용하였다고 되어 있다. </p>
<br> 

<p>** 3) 압력 투영을 계산하여 그 결과인 q(new)_i,j,k를 얻는다. ** </p>
<p>먼저 중력에 대한 힘을 계산해야 한다. 
이건 간단하다. y성분의 속도를 g*timestep 만큼 증가시키면 된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/16808e81-8173-4ba9-a0c6-f0ce3f37b7f8/image.png" alt=""></p>
<p>그런 다음 압력 투영을 적용해야 한다. 이전 글에서 압력을 구하는 방정식인 포아송 방정식을 이산화 하고 이를 행렬 곱으로 표현하였다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/6f1bd414-baf5-4012-845a-697cf8faa67e/image.png" alt=""></p>
<p>이렇게 표현된 식을 더 단순화 하여 Ap = d의 형태로 표현할 수 있다. 따라서 Ap = d로 부터 압력을 얻어야 하며 이 때 conjugate gradient method를 이용한다. </p>
<p>그리고 여기서 구해진 압력을 나비에-스토크스 방정식의 pressure projection과 관련된 수식에 넣으면 업데이트 된 물리량(여기선 속도)를 얻을 수 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/06100101-414a-4844-9e4c-e7154757a61a/image.png" alt=""></p>
<p>이렇게 해서 중력과 압력 투영이 적용된 새로운 물리량을 얻었다. PIC에서는 이 값을 그대로 기존 값과 바꾸지만 FLIP에서는 기존 값과 변경된 값의 차이를 이용한다. </p>
<br> 

<p>** 4) 각각의 입자에 대해 그리드에서 변경된 값 (delta)q_i,j,k = q(new)_i,j,k - q_i,j,k를 보간하여 입자의 값에 추가한다. **</p>
<p><img src="https://images.velog.io/images/mi_pine/post/eeba965a-4823-404b-a6eb-780cff5b0849/image.png" alt=""></p>
<p>이렇게 새로운 속도에서 기존 속도를 뺀 값을 구한 다음, 그 값을 보간하여 기존 입자의 값에 추가해준다. </p>
<p>이를 수식으로 나타내면 아래와 같다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/392b9a9b-3760-48b4-acc1-c4670ce3418e/image.png" alt=""></p>
<p>위 수식에서 2D의 상황이라 가정하고 A,B,C,D 4개만 적었는데 실제로 우리는 3D를 다루니 4개가 아닌 8개의 그리드 지점에서 기록된 속도 변화를 삼선형 보간(trilinear interpolate)해야 된다. </p>
<br>

<p>** 5) 그리드 속도장 내 입자들에 이류를 적용한다. ** </p>
<p>이제 중력과 압력 투영이 적용된 값을 입자에 업데이트 했으니, 입자에 이류를 적용하면 된다. </p>
<p>해당 논문에서는 CFL 조건에 의해 제한된 5개의 서브스텝들이 있는 RK2 ODE 솔버를 이용하였다고 되어 있다. </p>
<p>CFL은 적절한 time step을 구하는 것에 대한 조건으로 이를 적용하여 각 서브스텝에서 입자가 1개의 그리드 셀 이상을 움직이지 못하도록 한다. </p>
<p>RK2 ODE 솔버는 2차 Runge-Kutta 방법이다. </p>
<p>이전 포스트에서 이류를 해결하기 위해 semi-lagrangian 방법을 이용해서 입자의 현재 위치 Xg에서 과거 위치 Xp를 구할 때 아래와 같은 간단한 방법을 이용했었다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/534c14b0-5266-4d78-8a03-02e2d3291104/image.png" alt=""></p>
<p>여기서 좀 더 발전한 방법이 2차 Runge-Kutta 방법이다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/f0f315c2-650a-4be7-beac-71059aa9b596/image.png" alt=""></p>
<p>이런식으로 하나의 time step에서 두번에 걸쳐 Xp 값을 구하는 것이다. 1/2 timestep에서의 중간 위치를 구하고, 그 위치를 이용하여 최종적으로 Xp를 얻는 방법이다. </p>
<p>이제 정말로 Xp를 구했다. 입자가 시작된 위치 Xp를 알았으니 그 위치에서의 물리량만 알면 이류 문제가 해결된다. </p>
<p>그런데 과거의 위치 Xp가 현재 표현되고 있는 그리드에 없을 가능성이 있다. 그럴 경우 인근 그리드 포인트에서 보간하여 적절한 근사치를 얻을 수 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/fdcadb51-2ce8-400f-8386-2645726abdca/image.png" alt=""></p>
<p>여기서도 마찬가지로 삼선형 보간을 이용할 수 있다. </p>
<p>이렇게 하여 전반적인 FLIP이 적용되는 과정에 대해 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 유체 시뮬레이션(1) - Navier-Stokes 방정식]]></title>
            <link>https://velog.io/@mi_pine/%EC%9C%A0%EC%B2%B4-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%981-Navier-Stokes-%EB%B0%A9%EC%A0%95%EC%8B%9D-%EC%9D%B4%EC%82%B0%ED%99%94</link>
            <guid>https://velog.io/@mi_pine/%EC%9C%A0%EC%B2%B4-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%981-Navier-Stokes-%EB%B0%A9%EC%A0%95%EC%8B%9D-%EC%9D%B4%EC%82%B0%ED%99%94</guid>
            <pubDate>Sat, 26 Mar 2022 14:07:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/mi_pine/post/c6580b77-2c76-4a35-a757-b09963beb713/%EC%9C%A0%EC%B2%B4%20%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98.JPG" alt=""></p>
<p>Ships, Splashes, and Waves on a Vast Ocean 논문을 이해하는 것을 목표로 Robert Bridson 의 Fluid Simulation for Computer Graphics 등을 기반으로 정리한 내용입니다. </p>
<br> 

<blockquote>
<h3 id="1-navier-stokes-방정식">1. Navier-Stokes 방정식</h3>
</blockquote>
<p>유체의 움직임을 제어하는 기본방정식은 나비에-스토크스 방정식이다. 
<img src="https://images.velog.io/images/mi_pine/post/681baa36-4599-4f18-878b-2e9abc3d0ea9/image.png" alt=""></p>
<p>위 식에서 u가 유체의 속도장을 의미한다. 참고로 마지막 항은 점성에 대한 것인데, 점성이 없는 물에 대해 다룰 것이므로 해당 항은 고려하지 않는다. 
<img src="https://images.velog.io/images/mi_pine/post/ea4592f4-14f8-45ef-9c4f-49f5d798b39e/image.png" alt=""></p>
<p>위 식이 나비에-스토크스 방정식이고, 아래 식은 유체가 비압축성이 되도록 하는 식이다. 즉 두 식을 이용하여 비압축성 유체의 움직임을 표현할 수 있다. </p>
<ul>
<li>*<em>비압축성 유체란 ? *</em> 압축을 가하였을 때 부피가 변하지 않는 유체, 다른 물리량(압력, 온도)등의 변화가 밀도에 영향을 미치지 않는 유체를 말한다. 대부분의 액체는 비압축성이다. 
물이 꽉 차있는 페트병에 물을 압축시켜 더 채워넣을 수는 없다. 이게 비압축성을 의미한다. 그리고 기체는 압축성이다. </li>
</ul>
<br> 

<blockquote>
<h3 id="2-오일러eulerian-관점과-라그랑지안-lagrangian-관점">2. 오일러(Eulerian) 관점과 라그랑지안 (Lagrangian) 관점</h3>
</blockquote>
<p>연속체의 움직임에 대해 생각할 때, 운동을 추적하는 데는 두 가지 접근법, 라그랑지안 관점과 오일러 관점이 있다. </p>
<p>라그랑지안은 입자 하나하나를 따라가면서 이 입자가 어떻게 변하는지를 살펴보는 것이고, 오일러 관점에서는 입자를 추적하지 않고, 어느 고정된 &#39;위치&#39;를 기준으로 그 위치의 물리량이 어떻게 변하는지를 살펴본다. </p>
<p>예를 들어 온도 10도인 입자가 지나가고 온도 20도인 입자가 지나간다고 했을 때, 오일러 관점에서는 그 온도 10인 입자가 온도가 변하는지 안변하는지에는 관심이 없고 단지 여기 우리가 보고있는 위치의 온도가 10도에서 20도로 변했다 이런 관점으로 말하는 것이다. </p>
<p>라그랑지안 관점은 입자 시스템에, 오일러 관점은 고정된 그리드 시스템에 해당한다고 하기도 한다. </p>
<p>유체역학에서는 보통 오일러관점을 이용한다. </p>
<p>그런데 오일러 관점에서는 입자를 추적하지 않으니까 주어진 입자의 물리량이 얼마나 빠르게 변하는지, 예를 들어 가속도 같은 것들을 판단할 수 가 없다. </p>
<p>그래서 오일러 관점에서 물질을 추적하면서 미분하기 위해 등장한게 물질 미분(material derivate)이다. </p>
<p>어떤 물리량에 대한 수식(ex. q(t,x) - 시간 t, 위치 x에서 물리량 q의 값)를 그냥 통으로 미분해버리면 물질 미분식이 나온다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/4c227346-b42e-4b08-ac12-3852d873abb5/image.png" alt=""></p>
<p>그리고 이 물질 미분을 이용하여 아까 말한 나비에-스토크스 방정식을 표현하면, 오일러 관점에서의 비압축성 나비에-스토크스 방정식이 된다. 이게 논문에서의 주된 방정식이다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/494d9192-ae8c-45fb-9c68-72d65236e1dc/image.png" alt=""></p>
<br>

<blockquote>
<h3 id="3-numerical-simulation---split">3. Numerical Simulation - Split</h3>
</blockquote>
<p>이제 저 방정식으로 표현되는 유체를 컴퓨터로 시뮬레이션 하려면 이산화 과정이 필요하다. 이산화는 연속적인 수치를 이산적인, 불연속적인 수치로 변화시키는 것이다. </p>
<p>그러기 위해 먼저 복잡한 나비에-스토크스 방정식을 분할한다. </p>
<p>방정식을 3개로 나눈 다음, 어떤 항을 계산하고, 그 효과를 포함시키면서 갱신하고 이런식으로 진행된다. </p>
<p>나비에-스토크스 방정식을 아래처럼 3개로 쪼갠다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/b62ef492-71e6-4a79-9323-354bddb91127/image.png" alt=""></p>
<p>** 1. 이류(advection) : ** 이류란 물리량(농도, 온도 등)의 차이로 인해 유체가 흐르는 것을 말한다. 우리는 속도에 관심이 있으므로 물리량 q를 아래처럼 u로 놓았다. 
이류 항에서는 아래 간단한 이류 방정식을 풀면 된다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/8c5cc59f-8db0-4386-b23c-1eb5ae243a0f/image.png" alt=""></p>
<p>논문에서는 초기 속도를 u-, 이류 후 속도를 u, 압력 투영 후 속도를 u+라고 보았다. </p>
<p>즉, 위 식을 풀어 우리는 u-에서 u로 값을 업데이트 한다. </p>
<p>그리고 여기다가 중력을 고려하면 된다. 그게 두번째 항이다. </p>
<p>** 2. body force : ** 부피 전체에 걸쳐 작용하는 힘이다. 여기서는 중력만을 의미하는 것으로 보인다. </p>
<p>** 3. 압력 투영 및 비압축성 (pressure projection + incompressibility) : ** 우리는 비압축성 유체를 다루기 때문에 비압축성 제약을 강제해야 한다. 그리고 그걸 하게 되는 항이 이 3번째 항이다. 
압력이 부피가 축소되지 않도록 하는 일을 한다고 보기 때문에 여기서 이걸 고려한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/75486966-28be-45a7-80ce-e83ed03754e9/image.png" alt=""></p>
<p>여기서 일단 앞 식, 속도장을 시간에 대해 편미분 한 식을 이산화해보자. 
<img src="https://images.velog.io/images/mi_pine/post/42fe2079-abfc-4d31-892c-c619360811e8/image.png" alt=""></p>
<p>시간에 대한 미분식을 이렇게 이산화 하면 3번째 항은 아래와 같이 변한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/d0b85aba-f2c8-4953-9e60-adcae810bb26/image.png" alt=""></p>
<p>논문에서는 아래와 같이 pressure projection 후 속도 u+에 대한 식으로 표현하였다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/91e44906-f9c1-4ce1-b0d7-4b69551d97af/image.png" alt=""></p>
<p>여기서 우리가 모르는 것 즉 구해야되는 것은 압력 P이다. 논문에서는 이를 구하기 위해 포아송 방정식(Poisson&#39;s equation)을 이용한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/4bd21caa-d1b7-4331-a67b-605f5701afa4/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/mi_pine/post/9bce635a-8029-4c4f-a228-137c354cefdc/image.png" alt=""></p>
<p>이 식을 푸는 방법이 BEM과 FLIP에서 다르다. BEM은 아직 공부하지 않았으니 우선 FLIP에서 이를 푸는 방법만 알아보자. </p>
<br> 

<blockquote>
<h3 id="4-how-to-get-pressure">4. How to get Pressure</h3>
</blockquote>
<p>우선 기본적으로 포아송 방정식을 풀어 압력을 구하기 위해 유한차분(finite difference)을 이용한다. </p>
<p>유한차분이란 함숫값의 차를 이용해 미분계수를 근사하는 방법이다. 이렇게 말하면 잘 안와닿을 수 있는데, </p>
<p>아래와 같은 함수 f(x)가 있다고 가정해보자.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/3d7684ab-0745-42ff-8dc9-1a4d2a74429c/image.png" alt=""></p>
<p>그럼 이 함수에서 도함수 f&#39;(x)는 아래와 같이 근사할 수 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/8fa3f942-c2a5-48f7-9383-f455e12b17ba/image.png" alt=""></p>
<p>이게 유한차분이다. </p>
<p>이걸 하는 이유는 포아송 방정식을 이산화하기 위해서이다.</p>
<p>그리고 2D 이상에서 이 유한차분을 실행하는 방법이 &#39;그리드(Grid)&#39;이다. 공간을 그리드로 차분화(Discretization)하여, 아래 그림 처럼 x의 성분, y의 성분, 압력 등을 다 다른 위치에 저장한다. </p>
<p>압력을 그리드의 중앙에, x성분을 세로선의 중앙에 u로, y성분을 가로선의 중앙에 v로 저장한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/2f09a955-c059-47a8-b81d-19b421369746/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/mi_pine/post/b1605d18-c340-4715-8eb8-e247bcf90adf/image.png" alt=""></p>
<p>그럼 이걸 이용하여 아까 포아송 방정식의 첫번째 식의 우변(상수 제외)을 이산화해보자. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/d5cfffcf-f13f-4067-b19e-b3f6335f1f58/image.png" alt=""></p>
<p>첫번째 식은 그냥 공식이다. 
<img src="https://images.velog.io/images/mi_pine/post/74d8578f-fb38-46de-98bb-f5ffb6dfdf49/image.png" alt=""></p>
<p>그리고 이걸 그리드 격자를 기반으로 이산화 한 것이 두번째 줄의 식이다.</p>
<p>그리고 이 식은 행렬의 곱으로 표현할 수 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/3ebec719-1429-4960-96fe-bd6a59fdd464/image.png" alt=""></p>
<p>이 행렬곱을 보면 좌변이 div에 대한 식, 우변이 속도에 대한 식이라는 걸 알 수 있다. 
즉 div를 이산화한게 저 좌변의 식이라는 거 같은데, 이건 좀 더 알아봐야 될 거 같다. </p>
<p>이렇게 해서 우변을 이산화 했다! </p>
<p>그럼 이제 같은 방법으로 좌변에서 divP를 이산화해보자. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/d8e9340e-0207-4451-94f6-cab97aedd8b7/image.png" alt=""></p>
<p>마찬가지로 행렬 곱으로 나타내면 아래와 같다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/1d2e3dc9-df5c-4597-958f-4e436f0454b6/image.png" alt=""></p>
<p>이렇게 하여 압력과 관련된 포아송 방정식을 모두 이산화 하였고, 행렬 곱으로까지 나타내었다 !</p>
<p>정리해보면 아래와 같다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/1bc6e0a8-3f1c-4254-9c54-af27bc195d7c/image.png" alt=""></p>
<p>이렇게 해서 나비에-스토크스의 세번째 식, pressure projection이 얼추 해결되어 보인다. </p>
<p>그럼 이제 첫번째 식 advection을 이산화하는 방법에 대해 알아보자. </p>
<br>

<blockquote>
<h3 id="5-semi-lagrangian-advection">5. Semi-Lagrangian Advection</h3>
</blockquote>
<p>advection을 위에서 사용한 방법처럼 이산화하기는 어렵다. </p>
<p>대신 여기에서는 semi-Lagrangian method 라고 불리는 방법을 이용한다. </p>
<p>공간의 어떤 점 x에서 q의 새로운 값을 얻기 위해서는 개념적으로 x에 도달하는 입자를 찾아 q의 값을 구할 수 있다. </p>
<p>이 논리를 그리드에 적용한게 semi-Lagrangian method이다. </p>
<p>예시를 들어 자세히 살펴보면 다음과 같다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/3ea77ae1-cf02-496e-a220-4cd6681108ae/image.png" alt=""></p>
<p>그렇기 때문에 현재 어떤 그리드 포인트에서의 물리량을 알고 싶으면 속도장을 따라 역추적하면 된다. </p>
<p>그렇다면 Xp의 위치는 어떻게 알아낼까?</p>
<p>입자는 아래 방정식에 따라 ∆t의 시간동안 Xg까지 움직인다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/505d9310-8cb2-4a56-bb6e-589913139398/image.png" alt=""></p>
<p>따라서 Xg의 위치로 Xp의 위치를 구하면 아래와 같다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/1a4734a5-39d0-459a-92b7-b09b9ea684e7/image.png" alt=""></p>
<p>단순하게, u(Xg)는 속도에 관한 식이고, ∆t는 시간이니까 시간*속도해서 ∆t 시간동안 이동한 거리만큼을 빼준 것이다. </p>
<p>이런식으로 어떤 입자가 시작한 위치인 Xp를 구하고 나면, 이 Xp에서의 물리량 q가 현재 Xg에서의 물리량이 된다.</p>
<br>

<hr>
<p>이렇게 해서 유체 시뮬레이션의 기본이 되는 나비에-스토크스 방정식을 3개로 분할하고, 각각을 이산화하는 방법에 대해 알아보았다. </p>
<p>다음 글에서는 FLIP에 대해 더 알아보도록 하자! 🙂</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Phong Lighting]]></title>
            <link>https://velog.io/@mi_pine/Phong-Lighting</link>
            <guid>https://velog.io/@mi_pine/Phong-Lighting</guid>
            <pubDate>Mon, 28 Feb 2022 15:43:00 GMT</pubDate>
            <description><![CDATA[<p>퐁 모델은 물체 표면에서 감지되는 색상을 디퓨즈(Diffuse), 스페큘러(Specular), 앰비언트(Ambient), 발산광(Emissive Light) 이 4가지 항목으로 분리해서 처리한다. </p>
<h3 id="-1-diffuse-">** 1) Diffuse **</h3>
<p>디퓨즈는 난반사라고 보면 된다. 
난반사는 디퓨즈 표면이라 불리는 이상적인 표면으로 들어온 빛이 모든 방향으로, 같은 강도로 반사되는 것을 말한다.
물체 표면 위 점 p에 들어오는 빛의 양을 정의하려면 점 p의 노멀과 빛 벡터 l을 내적하면 된다. </p>
<p>** n·l = |n||l|cosθ **</p>
<p>근데 빛이 들어오는 입사각이 90도 보다 크면 이게 음수가 된다. 빛의 세기가 음수가 될 순 없으므로 이 경우 n·l 을 0으로 처리해준다.  </p>
<p>** max(n·l,0) **</p>
<p>위 식은 p에 들어오는 빛의 &#39;강도&#39;를 결정하는 식이다.</p>
<p>반사되는 빛의 색상은 다음과 같다. </p>
<p>** sd * md **</p>
<p>sd : 광원의 색상(RGB)
md : 물체의 디퓨즈 계수(diffuse reflectance)
(s : light source, m : material)</p>
<p>광도와 색상을 곱하면 퐁 모델의 디퓨즈 항이 완성된다. </p>
<p>** max(n·l,0) * (sd*md) **</p>
<br>


<h3 id="-2-specular-">** 2) Specular **</h3>
<p>스페큘러는 정반사다. 이는 물체 표면에 하이라이트를 만드는데 사용된다. </p>
<p>아까 디퓨즈에서 난반사는 모든 방향으로 동일하게 반사되므로 카메라의 위치를 고려할 필요가 없었다.
그러나 정반사의 경우 카메라의 위치에 따라 빛이 들어오고 안들어오고가 달라지기 때문에 이를 고려해주어야 한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/ea73c7ea-091c-4b26-87ef-1c8e0f5f335e/KakaoTalk_20220228_235135822.jpg" alt=""></p>
<p>빛 벡터 l이 입사각 = 반사각으로 반사되는데, 그 반사되는 빛이 r이고 p에서 카메라를 향하는 벡터가 v다.</p>
<p>우선 반사 벡터 r을 계산해보면
** r = 2n(n·l)-l ** 이다. </p>
<p>하이라이트를 볼 수 있는 영역은 r을 중심으로 한 원뿔 모양으로 묘사할 수있다. v가 원뿔 안에 놓이면 하이라이트를 볼 수 잇는 것이다. </p>
<p>원뿔 속에서 하이라이트가 감소되는 정도는 ** (r·v)^sh ** 이다. </p>
<p>sh : 매끈함의 정도. r과 v가 다른 경우 sh가 커질수록 하이라이트는 급격하게 감소한다. </p>
<p>퐁 모델의 스페큘러 항은 다음과 같다. </p>
<p><em>* (max(r·v, 0))^sh \</em> Ss * Ms **</p>
<p>Ss : 광원의 색상
Ms : 물체의 스페큘러 계수 </p>
<p>Ss는 일반적으로 Sd와 같은 같을 가지고 Ms는 물체 표면의 하이라이트가 광원의 색을 반영하기 위해 gray-scale로 표현된다. </p>
<br>


<h3 id="-3-ambient-">** 3) Ambient **</h3>
<p>공간 내 다양한 물체로부터 반사된 빛을 앰비언트 빛 이라고 부른다. 이는 간접 조명에 해당한다. 
앰비언트 반사는 아래와 같이 간단하게 정의된다.
<em>* Sa \</em> Ma ** </p>
<p>Sa : 앰비언트 빛의 색상(RGB)
Ma : 앰비언트 계수 </p>
<p>최종적으로 퐁 모델은 
앞서 다룬 3가지 항인 디퓨즈, 스패큘러, 앰비언트를 모두 더 해 아래와 같이 정의된다. </p>
<p><em>* max(n·l,0) * (sd*md) + (max(r·v, 0))^sh \</em> Ss * Ms + Sa * Ma **</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial7]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial7</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial7</guid>
            <pubDate>Fri, 11 Feb 2022 14:23:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729724(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729724(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h1 id="tutorial7--텍스처-매핑">Tutorial7 : 텍스처 매핑</h1>
<br>

<h2 id="📌-texture-mapping">📌 Texture Mapping</h2>
<p>텍스처 매핑은 2D 이미지를 3D 형상에 투영하는 것을 말한다. 그리기 위해서는 표면의 점들이 2D 이미지와 어떻게 대응되는지 명시해야 한다. </p>
<p>이번 튜토리얼의 예시에서는 정점에 의해 텍스처 좌표가 결정되며, 보간된다. </p>
<br>

<h2 id="📌-creating-a-shader-resource-from-the-texture-and-sampler-state">📌 Creating a Shader Resource from the Texture and Sampler State</h2>
<p>텍스처는 셰이더 리소스 뷰를 만드는 데 사용되는 2D 이미지이다.</p>
<pre><code class="language-c">hr = D3DX11CreateShaderResourceViewFromFile( g_pd3dDevice, L&quot;seafloor.dds&quot;, NULL, NULL, 
         &amp;g_pTextureRV, NULL );</code></pre>
<p>셰이더가 필터링(filtering), addressing 등의 방식을 제어하는 sampler state를 만들어야 한다. 텍스쳐 addressing은 UV좌표가 0과 1 사이가 아닌 다른 값이 되었을 때, 어떻게 표현될 것인 지를 결정하는 옵션이다. 
sampler state를 생성하기 위해 D3D11Device::CreateSamplerState()를 호출한다. </p>
<pre><code class="language-c">    // Create the sample state
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory( &amp;sampDesc, sizeof(sampDesc) );
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    hr = g_pd3dDevice-&gt;CreateSamplerState( &amp;sampDesc, &amp;g_pSamplerLinear );</code></pre>
<br>

<h2 id="📌-defining-the-coordinates-좌표계-정의하기">📌 Defining the Coordinates (좌표계 정의하기)</h2>
<p>이미지를 큐브에 매핑하기 전에 먼저 큐브의 각 정점에 텍스처 좌표를 정의해야 한다. 사용된 좌표계는 [0, 1]로 표준화 되었다. (텍스처의 왼쪽 상단 모서리는 (0,0), 오른쪽 하단 모서리는 (1,1))</p>
<p>먼저 정점을 정의할 때 텍스처 좌표를 포함시켜 주었다. </p>
<pre><code class="language-c">struct SimpleVertex
{
    XMFLOAT3 Pos;
    XMFLOAT2 Tex;
};</code></pre>
<p>그런 다음 셰이더에 input layout도 업데이트하여 포함시켜주었다. </p>
<pre><code class="language-c">// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { &quot;POSITION&quot;, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { &quot;TEXCOORD&quot;, 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};</code></pre>
<p>input layout이 변경되었으므로 해당 정점 셰이더 입력도 일치하도록 수정해야 한다. </p>
<pre><code class="language-c">struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
};</code></pre>
<p>마지막으로 정점에 대응되는 텍스처 좌표를 입력해준다. 큐브의 각 꼭지점은 텍스처의 모서리에 해당한다. 이렇게 하면 각 꼭짓점이 (0,0) (0,1) (1,0) 또는 (1,1)을 좌표로 갖는 단순 매핑이 만들어집니다. </p>
<pre><code class="language-c">// Create vertex buffer
SimpleVertex vertices[] =
{
    { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
    { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
    { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
};</code></pre>
<br>

<h2 id="📌-bind-texture-as-shader-resource-텍스처를-셰이더-리소스로-바인딩">📌 Bind Texture as Shader Resource (텍스처를 셰이더 리소스로 바인딩)</h2>
<p>텍스처와 sampler state는 이전 튜토리얼에서 살펴본 Constant와 같은 객체이다. 셰이더가 사용하기 전에 ID3D11DeviceContext::PSSetSamplers(), ID3D11DeviceContext::PSSetShaderResources() 등을 호출해주어야 한다. </p>
<pre><code class="language-c">    g_pImmediateContext-&gt;PSSetShaderResources( 0, 1, &amp;g_pTextureRV );
    g_pImmediateContext-&gt;PSSetSamplers( 0, 1, &amp;g_pSamplerLinear );</code></pre>
<p>이렇게 하면 셰이더에서 텍스처를 사용할 준비가 끝난다. </p>
<br>

<h2 id="📌-applying-the-texture-텍스처-적용하기">📌 Applying the Texture (텍스처 적용하기)</h2>
<p>텍스처를 매핑하기 위해 픽셀 셰이더에서 texture lookup function을 호출한다. 2D 텍스처를 조회한 다음 샘플 색상을 반환한다.</p>
<p>아래 픽셀 셰이더는 이 함수를 호출하여 메시 색상(또는 머터리얼 색상)으로 곱한 다음 최종 색상을 출력한다. </p>
<ul>
<li>txDiffuse : 우리가 리소스뷰 g_pTextureRV를 바인딩할 때 전달된 텍스처를 저장하는 객체</li>
</ul>
<pre><code class="language-c">// Pixel Shader
float4 PS( PS_INPUT input) : SV_Target
{
    return txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor;
}</code></pre>
<p>정점 셰이더를 통해 텍스처 좌표를 전달해야 한다.</p>
<pre><code class="language-c">// Vertex Shader
PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    output.Tex = input.Tex;

    return output;
}</code></pre>
<br>

<h2 id="📌-constant-buffers-상수-버퍼">📌 Constant Buffers (상수 버퍼)</h2>
<p>Direct3D 11에서 Constant Buffer를 사용하여 셰이더 상수(셰이더 변수)를 설정할 수 있다. Constant Buffer는 셰이더 상수를 업데이트하는데 필요한 bandwidth를 줄인다. </p>
<p>이전 튜토리얼에서는 하나의 Constant Buffer를 이용하여 필요한 모든 셰이더 상수를 보관했다. 그러나 Constant Buffer를 효율적으로 사용하는 가장 좋은 방법은 셰이더 변수를 업데이트 빈도에 따라 구성하는 것이다. 이를 통해 어플리케이션은 셰이더 상수를 업데이트 하는데 필요한 bandwidth를 최소화 할 수 있다. </p>
<p>예를 들어 이 튜토리얼에서는 상수를 세가지 구조로 그룹화 하였다.
하나는 모든 프레임에서 변경되는 변수, 다른 하나는 창 크기가 변경될 때만 변경되는 변수, 다른 하나는 처음 한 번 설정된 후 변경되지 않는 변수이다. </p>
<pre><code class="language-c">    cbuffer cbNeverChanges
    {
        matrix View;
    };

    cbuffer cbChangeOnResize
    {
        matrix Projection;
    };

    cbuffer cbChangesEveryFrame
    {
        matrix World;
        float4 vMeshColor;
    };</code></pre>
<p>Constant Buffer를 사용하려면 각 버퍼에 대해 ID3D11Buffer 객체를 생성해야 한다. 그런 다음 ID3D11DeviceContext::UpdateSubresource()를 호출하여 필요한 경우 다른 상수 버퍼에 영향을 미치지 않고 각 상수 버퍼를 업데이트 할 수 있다. </p>
<pre><code class="language-c">    //
    // Update variables that change once per frame
    //
    CBChangesEveryFrame cb;
    cb.mWorld = XMMatrixTranspose( g_World );
    cb.vMeshColor = g_vMeshColor;
    g_pImmediateContext-&gt;UpdateSubresource( g_pCBChangesEveryFrame, 0, NULL, &amp;cb, 0, 0 );</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial6]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial6</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial6</guid>
            <pubDate>Wed, 09 Feb 2022 15:12:48 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729723(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729723(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h1 id="tutorial6--라이팅">Tutorial6 : 라이팅</h1>
<p>램버튼 조명(lambertian lighting)을 적용해보자. </p>
<br>

<h2 id="📌-lighting">📌 Lighting</h2>
<p>램버트 조명은 빛으로부터의 거리에 관계없이 균일한 강도를 가진다. 빛이 표면에 닿으면 반사되는 빛의 양은 빛이 표면에 미치는 입사각에 따라 계산된다. </p>
<p>각도의 계산은 단순 내적으로 계산된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/75f6fc87-2811-4af4-b3da-43f89a916bc9/image.png" alt=""></p>
<p>이번 튜토리얼에서 사용된 빛은 directional lighting이다. 태양처럼 물체가 어디에 있든 빛이 비추는 방향이 동일하다고 보며, 개별 물체에 비추는 빛의 강도는 고려하지 않는다. </p>
<p>다른 종류의 빛으로는 중앙에서 균일한 빛을 발산하는 point light과, 모든 물체에 동일하게 적용하지 않는 spot light이 있다. </p>
<br>

<h2 id="📌-initializing-the-lights-빛-초기화">📌 Initializing the Lights (빛 초기화)</h2>
<p>이 튜토리얼에는 두 개의 광원이 있다. 하나는 정적으로 정육면체의 위, 뒤에 놓이고, 다른 하나는 정육면체 주위를 돈다. </p>
<p>조명은 셰이더에 의해 계산되므로 변수들은 선언된 다음 바인딩되어야 한다. 이 샘플에서는 광원의 방향과 색상 값만 있으면 된다. 
첫 번째 빛은 회색이고 고정되어 있으며, 두 번째 빛은 궤도를 도는 빨간 빛이다. </p>
<pre><code class="language-c">    // Setup our lighting parameters
    XMFLOAT4 vLightDirs[2] =
    {
        XMFLOAT4( -0.577f, 0.577f, -0.577f, 1.0f ),
        XMFLOAT4( 0.0f, 0.0f, -1.0f, 1.0f ),
    };
    XMFLOAT4 vLightColors[2] =
    {
        XMFLOAT4( 0.5f, 0.5f, 0.5f, 1.0f ),
        XMFLOAT4( 0.5f, 0.0f, 0.0f, 1.0f )
    };</code></pre>
<p>빛의 방향이 항상 중앙을 행하도록 회전 행렬을 적용한다. XMVector3Transform 함수는 벡터와 행렬을 곱하는데 사용된다. 이전 튜토리얼에서는 변환 행렬만 월드 행렬에 곱한다음 셰이더로 전달하여 변환을 수행했다. 
그러나 이 경우에는 단순성을 위해 CPU에서 빛을 변환하는 작업을 한다. </p>
<pre><code class="language-c">    // Rotate the second light around the origin
    XMMATRIX mRotate = XMMatrixRotationY( -2.0f * t );
    XMVECTOR vLightDir = XMLoadFloat4( &amp;vLightDirs[1] );
    vLightDir = XMVector3Transform( vLightDir, mRotate );
    XMStoreFloat4( &amp;vLightDirs[1], vLightDir );</code></pre>
<p>빛의 방향과 색상은 모두 행렬처럼 셰이더에 전달된다. 관련 변수가 호출되어 설정되고 매개 변수가 전달된다.</p>
<pre><code class="language-c">    //
    // Update matrix variables and lighting variables
    //
    ConstantBuffer cb1;
    cb1.mWorld = XMMatrixTranspose( g_World );
    cb1.mView = XMMatrixTranspose( g_View );
    cb1.mProjection = XMMatrixTranspose( g_Projection );
    cb1.vLightDir[0] = vLightDirs[0];
    cb1.vLightDir[1] = vLightDirs[1];
    cb1.vLightColor[0] = vLightColors[0];
    cb1.vLightColor[1] = vLightColors[1];
    cb1.vOutputColor = XMFLOAT4(0, 0, 0, 0);
    g_pImmediateContext-&gt;UpdateSubresource( g_pConstantBuffer, 0, NULL, &amp;cb1, 0, 0 );</code></pre>
<br>

<h2 id="📌-rendering-the-lights-in-the-pixel-shader-픽셸-셰이더에-빛-렌더링-하기">📌 Rendering the Lights in the Pixel Shader (픽셸 셰이더에 빛 렌더링 하기)</h2>
<p>빛과 노멀을 내적하면 빛의 효과(색상)를 계산할 수 있다. 
이 값은 범위를 [0. 1]로 변환하는 saturate 함수를 통해 전달된다. 
마지막으로, 두 개를 합쳐 최종 색이 결정된다. </p>
<p>표면의 머터리얼은 계산에 포함되지 않는다고 가정하여 표면의 최종적인 색깔 = 빛의 색깔이다.</p>
<pre><code class="language-c">    //
    // Pixel Shader
    //
    float4 PS( PS_INPUT input) : SV_Target
    {
        float4 finalColor = 0;

        //do NdotL lighting for 2 lights
        for(int i=0; i&lt;2; i++)
        {
            finalColor += saturate( dot( (float3)vLightDir[i],input.Norm) * vLightColor[i] );
        }
        return finalColor;
    }</code></pre>
<p>일단 픽셸 셰이더를 통과하면, 픽셀들은 빛에 의해 변조될 것이고, 큐브 표면에 각 빛이 미치는 영향을 볼 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial5]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial5</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial5</guid>
            <pubDate>Wed, 09 Feb 2022 13:17:40 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729722(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729722(v=vs.85)?redirectedfrom=MSDN</a>
위 내용을 참고하여 정리한 내용입니다.</p>
<h1 id="tutorial5--다른-개체-공전">Tutorial5 : 다른 개체 공전</h1>
<br>

<h2 id="📌-transformation-변환">📌 Transformation (변환)</h2>
<p>정점에서 수행할 수 있는 변환에는 3가지 유형이 있다. 
이동, 회전, 스케일링.
XNA Math 라이브러리에 이동, 회전, 스케일링, 월드-뷰 변환, 뷰-투영 변환과 같은 API가 포함되어 있다. </p>
<br>

<h2 id="📌-translation-이동">📌 Translation (이동)</h2>
<p>🔎 이동에 사용되는 행렬</p>
<pre><code class="language-c">    1  0  0  0
    0  1  0  0
    0  0  1  0
    a  b  c  1</code></pre>
<p>여기서 (a, b, c)는 이동할 방향과 거리를 정의하는 벡터이다. 
예를 들어 x축으로 -5만큼 이동하게 하려면 아래 행렬을 곱하면 된다.</p>
<p>🔎 x축으로 -5만큼 이동 </p>
<pre><code class="language-c">    1  0  0  0
    0  1  0  0
    0  0  1  0
   -5  0  0  1   </code></pre>
<p><img src="https://images.velog.io/images/mi_pine/post/1f0f7691-7047-43bb-923c-d6e75fc29e4d/image.png" alt=""></p>
<br>

<h2 id="📌-rotation-회전">📌 Rotation (회전)</h2>
<p>회전은 원점을 통과하는 X, Y, Z 축을 중심으로 일어난다. 
예를 들어 [1 0]를 반시계로 90도 회전시키면 [0 1]이 된다. </p>
<p>🔎 y축을 기준으로 시계 방향으로 i도 회전시키는 행렬</p>
<pre><code class="language-c">    cosΐ  0  -sinΐ   0
     0    1     0    0
    sinΐ  0   cosΐ  0
     0    0     0    1</code></pre>
<br>

<h2 id="📌-scaling-스케일링">📌 Scaling (스케일링)</h2>
<p>스케일링은 축 방향을 따라 확대하거나 축소하는 것을 말한다. 
모든 방향으로 크기를 조정할수도 있고 하나의 축만 따라 크기를 조정할 수도 있다. </p>
<p>🔎 스케일링 시키는 행렬</p>
<pre><code class="language-c">    p  0  0  0
    0  q  0  0
    0  0  r  0
    0  0  0  1</code></pre>
<br>

<h2 id="📌-multiple-transformations-다중-변환">📌 Multiple Transformations (다중 변환)</h2>
<p>벡터에 다중 변환을 적용하려면 첫 번째 변환 행렬을 곱한 다음 그 행렬에 두 번째 변환 행렬을 곱하면 된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/7044ae7d-5d13-41a4-87f6-ecb12d81eced/image.png" alt=""></p>
<br>

<h2 id="📌-creating-the-orbit-궤도-생성하기">📌 Creating the Orbit (궤도 생성하기)</h2>
<p>이 튜토리얼에서는 제자리에서 회전하는 큐브와, 자신의 축에 대해 회전함과 동시에 다른 큐브를 회전 하는 큐브 2개를 만들 것이다. </p>
<p>XNA Math에 회전, 이동 및 스케일링 행렬을 생성하는 데 도움이 되는 기능이 있다. </p>
<ul>
<li>XMMatrixRotationX, XMMatrixRotationY, XMMatrixRotationZ : x축, y축, z축 중심 회전</li>
<li>XMMatrixTranslation : 매개변수로 받은 점을 이동</li>
<li>XMMatrixScaling : 스케일링. 기본 축을 따라서만 조정되며 임의의 축을 따라 스케일링을 하고 싶으면 스케일링 행렬에 적절한 회전 행렬을 곱하면 된다. </li>
</ul>
<p>첫 번째 정육면체는 제자리에서 회전하고 궤도의 중심 역할을 할 것이다. XMMatrixRotationY 함수를 호출하면 된다. 큐브는 각 프레임에 설정된 양만큼 회전한다.</p>
<pre><code class="language-c">    // 1st Cube: Rotate around the origin
    g_World1 = XMMatrixRotationY( t );</code></pre>
<p>두 번째 정육면체는 첫 번째 정육면체 주위를 공전한다.
먼저 정육면체의 크기가 30%로 축소된 다음, 축(이 경우 z축)을 따라 회전한다.
궤도를 돌게 하기 위해 원점에서 멀리 떨어진 곳으로 이동시킨다음 y축을 따라 회전한다. 
원하는 결과는 4개의 행렬(mScale, mSpin, mTranslate, mOrbit)의 곱으로 얻을 수 있다. </p>
<pre><code class="language-c">    // 2nd Cube:  Rotate around origin
    XMMATRIX mSpin = XMMatrixRotationZ( -t );
    XMMATRIX mOrbit = XMMatrixRotationY( -t * 2.0f );
    XMMATRIX mTranslate = XMMatrixTranslation( -4.0f, 0.0f, 0.0f );
    XMMATRIX mScale = XMMatrixScaling( 0.3f, 0.3f, 0.3f );
    g_World2 = mScale * mSpin * mTranslate * mOrbit;</code></pre>
<p>t : 시간</p>
<p>시간을 이용하여 업데이트를 시키므로 t 값을 증가시켜 주어야 한다. </p>
<pre><code class="language-c">    // Update our time
    t += XM_PI * 0.0125f;</code></pre>
<p>렌더링을 하기 전에 셰이더에 대해 상수 버퍼를 업데이트 해야 한다. 월드 변환 행렬은 큐브마다 고유하므로 모든 큐브에 각각 적용된다. </p>
<pre><code class="language-c">    //
    // Update variables for the first cube
    //
    ConstantBuffer cb1;
    cb1.mWorld = XMMatrixTranspose( g_World1 );
    cb1.mView = XMMatrixTranspose( g_View );
    cb1.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext-&gt;UpdateSubresource( g_pConstantBuffer, 0, NULL, &amp;cb1, 0, 0 );

    //
    // Render the first cube
    //
    g_pImmediateContext-&gt;VSSetShader( g_pVertexShader, NULL, 0 );
    g_pImmediateContext-&gt;VSSetConstantBuffers( 0, 1, &amp;g_pConstantBuffer );
    g_pImmediateContext-&gt;PSSetShader( g_pPixelShader, NULL, 0 );
    g_pImmediateContext-&gt;DrawIndexed( 36, 0, 0 );

    //
    // Update variables for the second cube
    //
    ConstantBuffer cb2;
    cb2.mWorld = XMMatrixTranspose( g_World2 );
    cb2.mView = XMMatrixTranspose( g_View );
    cb2.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext-&gt;UpdateSubresource( g_pConstantBuffer, 0, NULL, &amp;cb2, 0, 0 );

    //
    // Render the second cube
    //
    g_pImmediateContext-&gt;DrawIndexed( 36, 0, 0 );</code></pre>
<br>

<h2 id="📌-the-depth-buffer-깊이-버퍼">📌 The Depth Buffer (깊이 버퍼)</h2>
<p>Depth Buffer가 없다면 궤도를 도는 작은 정육면체가 뒤를 돌 때 다른 정육면체 뒤가 아닌 앞에 그려질 것이다. 따라서 깊이 버퍼를 사용하여 Direct3D 화면에 그려진 모든 픽셀의 깊이를 판단해주어야 한다. 
Direct3D 11의 깊이 버퍼는 화면에 그려지는 모든 픽셀을 해당 화면 공간 픽셀의 깊이 버퍼에 저장된 값과 비교하여 확인한다.
렌더링 되는 픽셀의 z값이 버퍼에 이미 있는 값보다 작거나 같으면 픽셀이 그려지고, Depth Buffer의 값이 새로 그린 픽셀의 깊이로 업데이트 된다. 반면 그리려는 픽셀의 z값이 Depth Buffer에 기존에 있는 값보다 크면 픽셀은 버려지고 깊이 버퍼의 z 값은 변경되지 않는다. </p>
<p>🔎 Depth Buffer의 DepthStencilView를 작성하여 Depth Stencil texture를 사용한다는 것을 알게 한다. </p>
<pre><code class="language-c">    // Create depth stencil texture
    D3D11_TEXTURE2D_DESC descDepth;
    ZeroMemory( &amp;descDepth, sizeof(descDepth) );
    descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D11_USAGE_DEFAULT;
    descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;
    hr = g_pd3dDevice-&gt;CreateTexture2D( &amp;descDepth, NULL, &amp;g_pDepthStencil );
    if( FAILED(hr) )
        return hr;

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &amp;descDSV, sizeof(descDSV) );
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = g_pd3dDevice-&gt;CreateDepthStencilView( g_pDepthStencil, &amp;descDSV, &amp;g_pDepthStencilView );
    if( FAILED(hr) )
        return hr;</code></pre>
<p>새로 만든 Depth Stencil Buffer를 사용하려면 device에 바인딩해야 한다. 이 작업은 Depth Stencil View를 OMSetRenderTargets 함수의 세 번째 매개변수로 전달하여 수행된다. </p>
<pre><code class="language-c">    g_pImmediateContext-&gt;OMSetRenderTargets( 1, &amp;g_pRenderTargetView, g_pDepthStencilView );</code></pre>
<p>렌더 타겟과 마찬가지로 렌더링 전에 Depth Buffer를 초기화해야 한다. </p>
<pre><code class="language-c">    //
    // Clear the depth buffer to 1.0 (max depth)
    //</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial4]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial4</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial4</guid>
            <pubDate>Tue, 08 Feb 2022 08:00:18 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729721(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729721(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h1 id="tutorial4--3d-객체-만들기">Tutorial4 : 3D 객체 만들기</h1>
<br>

<h2 id="📌-3d-spaces-3d-공간">📌 3D Spaces (3D 공간)</h2>
<p>컴퓨터 그래픽스에서는 3D 공간을 주로 데카르트 좌표계(Cartesian coordinate system)에서 가장 흔하다. 
이 좌표계는 왼손 좌표계와 오른손 좌표계로 나뉜다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/74e04e79-87ed-4025-bcdf-cfa88b804f8c/image.png" alt=""></p>
<br>

<h2 id="📌-object-space">📌 Object Space</h2>
<p>object space는 3D 모델을 만들 때 사용하는 공간을 말한다. </p>
<br>

<h2 id="📌-world-space">📌 World Space</h2>
<p>World Space는 씬의 모든 사물이 공유하는 공간이다. 렌더링하려는 객체들의 위치를 정의한다. </p>
<br>

<h2 id="📌-view-space">📌 View Space</h2>
<p>View space(camera space)는 전체 씬이 사용된다는 점에서 World space와 유사하다. 그러나 원점이 viewer나 camera냐 가 다르다. (World Space는 viewer의 좌표가 원점, Camera Space은 camera 좌표가 원점)</p>
<p><img src="https://images.velog.io/images/mi_pine/post/32fe0ca2-a410-409f-80f6-d6da86735eef/image.png" alt=""></p>
<br>

<h2 id="📌-projection-space-투영-공간">📌 Projection Space (투영 공간)</h2>
<p>투영 공간은 뷰 스페이스에서 투영 변환을 적용한 후의 공간을 말한다. 
X, Y는 -1<del>1 이고, Z는 0</del>1 이다. </p>
<br>

<h2 id="📌-screen-space-스크린-공간">📌 Screen Space (스크린 공간)</h2>
<p>스크린 공간은 프레임 버퍼의 위치를 참조하는데 사용된다. 프레임 버퍼는 보통 2D 텍스처이기 때문에 스크린 공간 역시 2D 공간이다. 
왼쪽 상단 모서리가 0,0 이다. </p>
<br>

<h2 id="📌-space-to-space-transformation-공간-변환">📌 Space-to-space Transformation (공간 변환)</h2>
<p>파이프라인에 World, View, Projection transfrom 3가지 변환이 있다. </p>
<br>

<h2 id="📌-world-transformation-월드-변환">📌 World Transformation (월드 변환)</h2>
<p>정점을 객체 공간(Object Space)에서 월드 공간(World Space)로 변환한다. 크기 조절, 회전, 이동 등이 있다. 씬의 물체들은 각각 고유한 월드 변환 행렬을 가진다. </p>
<br>

<h2 id="📌-view-transformation">📌 View Transformation</h2>
<p>정점을 월드 공간에서 뷰 공간으로 변환한다. 뷰 변환 행렬은 viewer나 카메라가 아닌 정점에 적용된다. 즉 카메라의 관점에서 정점을 이동시킨다. 
XNA Math에는 view matrix를 계산하기 위해 XMMatrixLookAtLH()라는 API가 종종 사용된다. 카메라가 어디에 있는지, 어디를 보고 있는지, 업벡터 등을 알려주면 이에 상응하는 뷰 매트릭스를 얻을 수 있다. </p>
<br>

<h2 id="📌-projection-transformation-투영-변환">📌 Projection Transformation (투영 변환)</h2>
<p>투영 변환은 월드, 뷰 공간과 같은 3D 공간에서 투영 공간으로 정점을 변환한다. 투영 공간에서 정점의 X, Y 좌표는 3차원 공간에서 정점의 X/Z, Y/Z 비율에 의해 구해진다. </p>
<p>3D 공간을 정의하는 매개 변수 중 하나를 FOV(Field-Of-View)라고 한다. FOV는 특정 방향을 바라볼 때 특정 위치에서 어떤 물체가 보이는지를 나타낸다. </p>
<p>컴퓨터 그래픽스에서 FOV는 view frustum에 포함되어 있다. view frustum은 3D에서 6개의 평면으로 정의된다. </p>
<p>GPU는 view frustum 밖에 있는 개체는 뺀다. 이 과정을 클리핑(clipping) 이라고 한다. 클리핑 결과 view frustum은 육면체(상자)가 된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/d20954a1-9ded-43dc-92db-1c48f535dbd7/image.png" alt=""></p>
<p>Direct3D 11에서 투영 행렬을 얻는 가장 쉬운 방법은 XMMatrixPerspectiveFovLH() 메소드를 호출하는 것이다. 매개변수는 FOVy, Aspect, Zn, Zf 4개 이다. </p>
<ul>
<li>FOVy : y 방향 시야 범위</li>
<li>Aspect : 가로(view space width) 세로(height) 비율</li>
<li>Zn : near Z 값</li>
<li>Zf : far Z 값</li>
</ul>
<br>

<h2 id="📌-using-transformation">📌 Using Transformation</h2>
<p>앞서 하나의 삼각형을 화면에 렌더링 할때는 정점 버퍼가 바로 투영 공간에 있도록 했기 때문에 변환을 할 필요가 없었다.
이제 정점 버퍼가 객체 공간에 정의되도록 한 후에 정점 셰이더를 수정하여 정점을 객체 공간에서 투영 공간으로 변환해보자. </p>
<br>

<h2 id="📌-modifying-the-vertex-buffer-정점-버퍼-수정하기">📌 Modifying the Vertex Buffer (정점 버퍼 수정하기)</h2>
<p>🔎 큐브 </p>
<pre><code class="language-c">
SimpleVertex vertices[] =
    {
        { XMFLOAT3( -1.0f,  1.0f, -1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
        { XMFLOAT3(  1.0f,  1.0f, -1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
        { XMFLOAT3(  1.0f,  1.0f,  1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f,  1.0f,  1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
        { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },
        { XMFLOAT3(  1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },
        { XMFLOAT3(  1.0f, -1.0f,  1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f, -1.0f,  1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },
    };
</code></pre>
<p>이 8개의 점을 통해 정육면체를 형성하는 삼각형을 지정해야 한다. </p>
<p>많은 삼각형들은 같은 꼭짓점을 공유할 것이므로 8개의 점만 지정한 다음 Driect3D가 삼각형에 대해 어떤 점을 선택해야 하는지 알려주는 방법을 상요할 것이다. 이는 인덱스 버퍼(index buffer)를 통해 수행된다. 인덱스 버퍼는 버퍼의 정점 인덱스를 참조하여 각 삼각형에서 사용할 점을 지정하는 리스트를 포함한다.</p>
<p>인덱스 버퍼는 정점 버퍼와 매우 유사하다. </p>
<p>🔎 index buffer </p>
<pre><code class="language-c">    // Create index buffer
    WORD indices[] =
    {
        3,1,0,
        2,1,3,

        0,5,4,
        1,5,0,

        3,4,7,
        0,4,3,

        1,6,5,
        2,6,1,

        2,7,6,
        3,7,2,

        6,4,5,
        7,4,6,
    };

    D3D11_BUFFER_DESC bd;
    ZeroMemory( &amp;bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( WORD ) * 36;        // 36 vertices needed for 12 triangles in a triangle list
    bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    InitData.pSysMem = indices;
    if( FAILED( g_pd3dDevice-&gt;CreateBuffer( &amp;bd, &amp;InitData, &amp;g_pIndexBuffer ) ) )
        return FALSE;</code></pre>
<p>array를 WORD를 이용하여 선언해주었으므로 sizeof(WORD)를 이용하여 bd의 ByteWidth를 지정해주었다. </p>
<p>Direct3D가 삼각형을 생성할 때 이 인덱스 버퍼를 참조하도록 설정해야 한다.</p>
<pre><code class="language-c">
 // Set index buffer
    g_pImmediateContext-&gt;IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );
</code></pre>
<br>

<h2 id="📌-modifying-the-vertex-shader-정점-셰이더-수정하기">📌 Modifying the Vertex Shader (정점 셰이더 수정하기)</h2>
<p>정점 셰이더에서 객체에서 월드 공간으로, 월드에서 뷰 공간으로, 뷰 공간에서 투영공간으로 변환을 진행해야 한다.</p>
<p>가장 먼저 할 일은 3개의 상수 버퍼(constant buffer) 변수를 정의하는 것이다. 상수 버퍼는 어플리케이션이 셰이더에 전달해야 하는 데이터를 저장하는데 사용된다. </p>
<p>우리가 사용할 세가지 변수는 월드, 뷰, 투영 변환 행렬이다. </p>
<p>이 행렬들을 이용하여 정점 셰이더에서 위치를 변환한다.  </p>
<p>🔎 변수 선언 및 새로운 정점 셰이더 </p>
<pre><code class="language-c">    cbuffer ConstantBuffer : register( b0 )
    {
        matrix World;
        matrix View;
        matrix Projection;
    }

    //
    // Vertex Shader
    //
    VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR )
    {
        VS_OUTPUT output = (VS_OUTPUT)0;
        output.Pos = mul( Pos, World );
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Color = Color;
        return output;
    }</code></pre>
<br>

<h2 id="📌-setting-up-the-matrices-행렬-설정하기">📌 Setting up the Matrices (행렬 설정하기)</h2>
<p>렌더링 할 때 사용할 변환을 저장한 세 개의 행렬도 정의해야 한다. 렌더링하기 전에 이러한 행렬의 값을 Constant buffer에 복사한다. 그러고 난 후 Draw()를 호출하여 렌더링을 시작하면, 정점 셰이더는 Constant Buffer에 저장된 행렬을 읽는다.</p>
<pre><code class="language-c">    ID3D11Buffer* g_pConstantBuffer = NULL;
    XMMATRIX g_World;
    XMMATRIX g_View;
    XMMATRIX g_Projection;</code></pre>
<p>ID3D11Buffer 객체를 만들기 위해서는 ID3D11Device::CreateBuffer()를 사용하고 D3D11_BIND_CONSTANT_BUFFER를 명시해야 한다. </p>
<pre><code class="language-c">    D3D11_BUFFER_DESC bd;
    ZeroMemory( &amp;bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(ConstantBuffer);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    if( FAILED(g_pd3dDevice-&gt;CreateBuffer( &amp;bd, NULL, &amp;g_pConstantBuffer ) ) )
        return hr;</code></pre>
<p>다음으로 할 일은 변환을 수행하기 위해 사용할 세 가지 행렬을 만드는 것이다. 우리는 삼각형이 XY 평면에 평행하게 원점에 위치하기를 원한다. 객체 공간의 정점버퍼에서 이런식으로 이미 저장되어 있으므로 월드 변환은 기본 행렬이 된다. 
카메라가 [0 1 -5]에 위치하며 [0 1 0]을 바라보게 하고자,  XMMatrixLookAtLH()를 호출하여 계산할 수 있다. +Y 방향이 위쪽으로 유지되어야 한다.
마지막으로 투영 행렬을 얻기 위해 XMMatrixPerspectiveFovLH()를 호출한다. 
이 세가지 행렬은 전역 변수 g_World, g_View, g_Projection에 저장된다. </p>
<br>

<h2 id="📌-updating-constant-buffers-상수-버퍼-업데이트-하기">📌 Updating Constant Buffers (상수 버퍼 업데이트 하기)</h2>
<p>행렬을 상수 버퍼에 기록해야 렌더링할 때 GPU가 읽을 수 있다. ID3D11DeviceContext::UpdateSubresource() API를 사용하여 셰이더의 상수 버퍼와 동일한 순서로 저장된 행렬에 포인터를 전달 할 수 있다. 이를 위해 셰이더의 상수 버퍼와 동일한 레이아웃을 가진 구조체를 만들 것이다. </p>
<pre><code class="language-c">    //
    // Update variables
    //
    ConstantBuffer cb;
    cb.mWorld = XMMatrixTranspose( g_World );
    cb.mView = XMMatrixTranspose( g_View );
    cb.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext-&gt;UpdateSubresource( g_pConstantBuffer, 0, NULL, &amp;cb, 0, 0 );</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial3]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial3</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial3</guid>
            <pubDate>Sun, 06 Feb 2022 11:57:52 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729720(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729720(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h1 id="tutorial3-">Tutorial3 :</h1>
<h2 id="1-그래픽-파이프라인을-통해-삼각형을-그리는-과정-알아보기">1) 그래픽 파이프라인을 통해 삼각형을 그리는 과정 알아보기</h2>
<h2 id="2-셰이더">2) 셰이더</h2>
<br>

<h2 id="📌-shaders-셰이더">📌 Shaders (셰이더)</h2>
<p>Direct3D 11은 정점 셰이더, 지오메트리 셰이더, 픽셀 셰이더를 지원한다. 
정점 셰이더는 정점을 입력으로 받아 정점 버퍼를 통해 GPU로 전달되는 모든 정점에 대해 한 번씩 실행된다. 
지오메트리 셰이더는 점, 선, 삼각형 등을 입력으로 받아 각각 한 번씩 실행된다. 
픽셀 셰이더는 렌더링하고자 하는 픽셀마다 한 번씩 실행된다. </p>
<p>지오메트리 셰이더는 선택사항이다. </p>
<p>실행 순서 : 정점 셰이더 -&gt; (지오메트리 셰이더) -&gt; 레스터라이저 -&gt; 픽셀 셰이더 </p>
<br>

<h2 id="📌-vertex-shaders-정점-셰이더">📌 Vertex Shaders (정점 셰이더)</h2>
<p>정점 셰이더의 가장 중요한 역할은 변환(transformation)이다. 
예를 들어 삼각형이 2D 텍스처 버퍼에 그려질 때 GPU는 꼭짓점이 그려질 버퍼 상의 점의 2D 좌표를 알아야 한다. 
우선 이 튜토리얼에서는 변환을 진행하지 않는다.</p>
<p>Direct3D 11 튜토리얼에서는 셰이더를 HLSL(High-Level Shading Language)로 작성하였다. </p>
<p>정점 셰이더는 다음과 같다. </p>
<br>

<p>🔎 정점 셰이더</p>
<pre><code class="language-c">    float4 VS( float4 Pos : POSITION ) : SV_POSITION
    {
        return Pos;
    }</code></pre>
<br>

<h2 id="📌-pixel-shaders-픽셀-셰이더">📌 Pixel Shaders (픽셀 셰이더)</h2>
<p>모니터는 픽셀들의 2차원 격자이고, 각각의 픽셀들은 독립적인 색깔을 가지고 있다. 따라서 우리가 보는 삼각형은 실제로는 아래 그림에서 오른쪽과 같은 형태이다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/8fd29a55-9b84-4c87-bbe1-696d39fe1db0/image.png" alt=""></p>
<p>세 개의 정점으로 정의된 삼각형을 픽셀 모음으로 변환하는 과정을 레스터라이즈라고 한다. GPU는 먼저 렌더링되는 삼각형에 포함되는 픽셀을 결정한다. 그런 다음 각 픽셀에 대해 활성 픽셀 셰이더를 호출한다.
픽셸 셰이더의 주된 목적은 정점 셰이더나 지오메트리부터 입력을 얻어서 각 픽셀의 색깔을 계산하는 것이다.
이 색상을 계산한 다음 다시 파이프라인으로 보낸다. </p>
<br>

<p>🔎 픽셀 셰이더 </p>
<pre><code class="language-c">    float4 PS( float4 Pos : SV_POSITION ) : SV_Target
    {
        return float4( 1.0f, 1.0f, 0.0f, 1.0f );    // Yellow, with Alpha = 1
    }</code></pre>
<p>SV_Target : 렌더 타겟 형식으로의 출력을 의미</p>
<br>

<h2 id="📌-creating-the-shaders-셰이더-만들기">📌 Creating the Shaders (셰이더 만들기)</h2>
<p>D3DX11CompileFromFile()를 호출하여 만들 수 있다. </p>
<p>🔎 어플리케이션 코드에서 셰이더 만들기</p>
<pre><code class="language-c">    // Create the vertex shader
    if( FAILED( D3DX11CompileFromFile( &quot;Tutorial03.fx&quot;, NULL, NULL, &quot;VS&quot;, &quot;vs_4_0&quot;, D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &amp;pVSBlob, &amp;pErrorBlob, NULL ) ) )
        return FALSE;

    // Create the pixel shader
    if( FAILED( D3DX11CompileFromFile( &quot;Tutorial03.fx&quot;, NULL, NULL, &quot;PS&quot;, &quot;ps_4_0&quot;, D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &amp;pPSBlob, &amp;pErrorBlob, NULL ) ) )
        return FALSE;</code></pre>
<br>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial2]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial2</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial2</guid>
            <pubDate>Sun, 06 Feb 2022 07:05:08 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729719(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729719(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h1 id="tutorial2--삼각형-그리기">Tutorial2 : 삼각형 그리기</h1>
<br>

<h2 id="📌-elements-of-a-triangle-삼각형의-요소">📌 Elements of a Triangle (삼각형의 요소)</h2>
<p>GPU가 삼각형을 렌더링하기 위해서는 삼각형의 꼭짓점 위치 3개가 주어져야 한다. </p>
<p>정점 버퍼에 꼭짓점 3개를 저장하면 된다. </p>
<br>

<h2 id="📌-input-layout">📌 Input Layout</h2>
<p>정점은 위치, 노멀, 색상, 텍스처 좌표(텍스처 매핑에 이용) 등을 가지고 있다. 정점 레이아웃은 이러한 속성들이 메모리에 어떻게 위치하는지를 나타낸다. (각 속성들이 어떤 데이터 타입을 이용하는지, 각 속성의 사이즈는 얼마인지, 저장된 순서 등)</p>
<p>각 속성은 다른 타입을 가지고 있기 때문에 정점은 일반적으로 구조체로 표현한다. 정점의 사이즈는 구조체의 사이즈로 쉽게 얻을 수 있다.</p>
<pre><code class="language-c">struct SimpleVertex
{
    XMFLOAT3 Pos; // Position
};</code></pre>
<p>이걸 GPU에 공급하기 위해서는 input layout을 알고 있어야 한다. (GPU는 이게 정점에 대한 정보라는걸 모른다) </p>
<p>Direct3D 11에서 input layout은 GPU가 이해할 수 있는 형태로 정점 구조체를 설명하는 것이다. 각 정점 속성들은 D3D11_INPUT_ELEMENT_DESC 구조체를 이용하여 설명될 수 있다. </p>
<p>D3D11_INPUT_ELEMENT_DESC에 대해 좀 더 자세히 알아보자.</p>
<br> 

<p>🔎 ** D3D11_INPUT_ELEMENT_DESC **</p>
<ul>
<li>SemanticName</li>
</ul>
<p>: Semantic name은 요소의 성격이나 목적을 설명하는 문자열로 GPU에게 이게 뭔지 (정점인지, 노멀인지 등)을 알려주는 것이다. 여기선 POSITION이다. </p>
<ul>
<li>SemanticIndex</li>
</ul>
<p>: 정점은 동일한 성질의 여러 속성을 가질 수 있다. 예를 들어 색상이 2개 일수도 있고 텍스처 좌표가 2개 일 수도 있다. 이 때 같은 COLOR 이라는 Semantic name을 쓰면서 Semantic index를 0과 1로 설정해준다. </p>
<ul>
<li>Format </li>
</ul>
<p>: 요소에 사용할 데이터 타입을 정의한다. 예를 들어 DXGI_FORMAT_R32G32B32_FLOAT은 3개의 32비트 float을 가지고 있어 총 12byte가 된다. </p>
<ul>
<li>InputSlot</li>
</ul>
<p>: Direct3D 11에서 GPU에 최대 16개의 정점 버퍼가 동시에 공급될 수 있다. 각 정점 버퍼는 0부터 15까지의 숫자를 가진다. InputSlot이 이 숫자를 이용해서 뭘 가져올건지 알려주는 역할을 한다. </p>
<ul>
<li>AlignedByteOffset </li>
</ul>
<p>: AlignedByteOffeset은 GPU에 메모리 상에서 정점 버퍼가 시작되는 위치를 알려준다. </p>
<ul>
<li>InputSlotClass</li>
</ul>
<p>: 여기엔 일반적으로 D3D11_INPUT_PER_VERTEX_DATA를 쓴다.</p>
<ul>
<li>InstanceDataStepRate </li>
</ul>
<p>: 인스턴스화에 사용된다. 튜토리얼에서는 인스턴스화를 사용하지 않으므로 0으로 설정한다. </p>
<p>이제 D3D11_INPUT_ELEMENT_DESC 배열을 채워 놓으면 다음과 같다. </p>
<pre><code class="language-c">// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { &quot;POSITION&quot;, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
};
UINT numElements = ARRAYSIZE(layout);</code></pre>
<br>

<h2 id="📌-vertex-layout">📌 Vertex Layout</h2>
<pre><code class="language-c">// Create the input layout
if( FAILED( g_pd3dDevice-&gt;CreateInputLayout( layout, numElements, pVSBlob-&gt;GetBufferPointer(), 
        pVSBlob-&gt;GetBufferSize(), &amp;g_pVertexLayout ) ) )
    return FALSE;
// Set the input layout
g_pImmediateContext-&gt;IASetInputLayout( g_pVertexLayout );</code></pre>
<br>

<h2 id="📌-creating-vertex-buffer">📌 Creating Vertex Buffer</h2>
<p>초기화할동안 해야할 일 중 하나는 정점 데이터를 저장한 정점 버퍼(vertex buffer)를 만드는 것이다. 
Direct3D 11에서 정점 버퍼를 만들려면  D3D11_BUFFER_DESC, D3D11_SUBRESOURCE_DATA 두가지 구조체를 만들고 ID3D11Device::CreateBuffer()를 호출해야 한다. </p>
<p>정점 버퍼에 복사될 데이터는 아래 코드에서 SimpleVertex에 저장된 3개의 정점들이다. 
정점 버퍼가 생성된 후 ID3D11DeviceContext::IASetVertexBuffers()를 호출한다.</p>
<br>

<p>🔎 vertex buffer 만들기 </p>
<pre><code class="language-c">// Create vertex buffer
SimpleVertex vertices[] =
{
    XMFLOAT3( 0.0f, 0.5f, 0.5f ),
    XMFLOAT3( 0.5f, -0.5f, 0.5f ),
    XMFLOAT3( -0.5f, -0.5f, 0.5f ),
};
D3D11_BUFFER_DESC bd;
ZeroMemory( &amp;bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA InitData; 
ZeroMemory( &amp;InitData, sizeof(InitData) );
InitData.pSysMem = vertices;
if( FAILED( g_pd3dDevice-&gt;CreateBuffer( &amp;bd, &amp;InitData, &amp;g_pVertexBuffer ) ) )
    return FALSE;

// Set vertex buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pImmediateContext-&gt;IASetVertexBuffers( 0, 1, &amp;g_pVertexBuffer, &amp;stride, &amp;offset );</code></pre>
<br>

<h2 id="📌-primitive-topology">📌 Primitive Topology</h2>
<p>Primitive Topology 는 GPU가 삼각형을 렌더링하는데 필요한 3개의 정점을 얻는 방법을 말한다. </p>
<p>만약 삼각형을 2개 그리고 싶다면 GPU에 6개의 정점을 보내면 되고 이 때 topology를 삼각형 리스트(triangle list)라고 부른다. 삼각형 리스트는 이해가 쉽지만, 비효율적이다. 여러개의 삼각형이 정점을 공유하는 경우 한 정점이 여러번 저장된다. </p>
<blockquote>
<p>A B C C B D</p>
</blockquote>
<p><img src="https://images.velog.io/images/mi_pine/post/21284cc9-55ed-455d-847b-8532b08fd09a/image.png" alt=""></p>
<p>위 그림처럼 삼각형이 두개의 꼭짓점을 공유한다면 먼저 그려진 삼각형에서 두 개의 정점을 사용하고 정점 버퍼에서 한개만 가져온다면 훨씬 효율적일 것이다. 이렇게 할 때 topology는 triangle strip이다. 
triangle strip을 이용하면 두번째 삼각형을 그릴 때 이전 삼각형에서 마지막 두 정점을 가져오고, 정점 버퍼에서 그 다음 정점을 하나 가져온다. </p>
<blockquote>
<p>A B C D</p>
</blockquote>
<p><img src="https://images.velog.io/images/mi_pine/post/4ced088c-f648-4b33-ad3d-ee8706785ab9/image.png" alt=""></p>
<p>위 도형의 경우 아래와 같이 전달된다. </p>
<blockquote>
<p>A B C D E</p>
</blockquote>
<p>두번째, 네번째, 여섯번째 삼각형 등은 반시계방향으로 정점이 주어진다.
(기본적으론 시계)</p>
<br>

<p>🔎 삼각형 리스트를 이용하여 topology 설정</p>
<pre><code class="language-c">// Set primitive topology
g_pImmediateContext-&gt;IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );</code></pre>
<br>

<h2 id="📌-rendering-the-triangle">📌 Rendering the Triangle</h2>
<p>이제 삼각형을 렌더링해보자. 렌더링을 위해 정점 셰이더와 픽셀 셰이더 두개를 만들었다. 정점 셰이더는 삼각형의 정점들을 올바른 위치로 변환(space transform)하는 역할을 한다. 픽셀 셰이더는 삼각형의 각 픽셀의 색상을 계산한다. (이건 다음 튜토리얼에서)</p>
<p>이러한 셰이더들을 사용하려면 각각 ID3D11DeviceContext::VSSetShader(), ID3D11DeviceContext::PSSetShader()을 호출하면 된다. </p>
<p>마지막으로 ID3D11DeviceContext::Draw()를 호출하여 현재 정점 버퍼, 정점 레이아웃, primitive topology 등을 사용하여 GPU가 렌더링하도록 하면 된다. </p>
<p>Draw()의 첫 번째 매개변수는 GPU로 보낼 정점 수이고, 두 번째 매개변수는 보낼 첫번째 정점의 인덱스이다. 우리는 하나의 삼각형을 렌더링하고 정점 버퍼의 처음에서부터 하기 때문에 각각 3, 0 에 해당한다. </p>
<pre><code class="language-c">// Render a triangle 
g_pImmediateContext-&gt;VSSetShader( g_pVertexShader, NULL, 0 );
g_pImmediateContext-&gt;PSSetShader( g_pPixelShader, NULL, 0 );
g_pImmediateContext-&gt;Draw( 3, 0 );</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Direct3D Tutorial1]]></title>
            <link>https://velog.io/@mi_pine/Direct3D-Tutorial1</link>
            <guid>https://velog.io/@mi_pine/Direct3D-Tutorial1</guid>
            <pubDate>Sat, 05 Feb 2022 15:41:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/previous-versions//ff729718(v=vs.85)?redirectedfrom=MSDN">https://docs.microsoft.com/en-us/previous-versions//ff729718(v=vs.85)?redirectedfrom=MSDN</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<h2 id="📌-setting-up-the-direct3d-11-device-direct3d-11-장치-설정">📌 Setting Up The Direct3D 11 Device (Direct3D 11 장치 설정)</h2>
<br>

<p>3D 장면을 렌더링할 때 가장 먼저 해야 할 일은 device, immediate context, swap chain 세 가지 객체를 만드는 것이다.</p>
<p>(immediate context는 direct3d 11에 새로 등장한 객체임)</p>
<p>1) <strong>디바이스(device)</strong> 는 리소스를 만든다.</p>
<p>2) <strong>immediate context</strong>는 버퍼에 렌더링을 수행한다. </p>
<p>3) <strong>스왑 체인(swap chain)</strong> 은 장치가 렌더링되는 버퍼를 가져와서 실제 모니터 화면에 콘텐츠를 표시하는 역할을 한다. 
스왑 체인에는 두 개 이상의 버퍼가 있는데, 보통 백 버퍼와 프론트 버퍼가 있다. 프론트 버퍼가 현재 사용자에게 제공되고 있는 버퍼이고, 백 버퍼는 앞으로 그려질 타겟이다. 따라서 다 그려지고 나면 백 버퍼가 프런트 버퍼가 된다. </p>
<p>스왑 체인을 만들기 위해 생성하려는 스왑 체인을 설명하는 DXGI_SWAPCHAIN_DESC 구조체를 체워야 한다. </p>
<br>

<p>🔎 ** DXGI_SWAPCHAIN_DESC **</p>
<p>** BackBufferUsage ** : 백 버퍼가 어떻게 쓸건지.</p>
<p>백 버퍼로 렌더링을 해주고자 하므로 BackBufferUsage로 DXGI_USAGE_RENDER_TARGET_OUTPUT 으로 설정해주었다. </p>
<p>** OutputWindow ** : 어떤 창에 이미지를 표시할 건지 </p>
<p>이렇게 설정이 다 채워지고 나면 아래 함수를 호출할 수 있다. </p>
<p>** D3D11CreateDeviceAndSwapChain ** : 장치와 스왑 체인을 생성하는 함수</p>
<br>
<br>

<p>🔎 ** 디바이스와 스왑 체인을 만드는 코드 **</p>
<pre><code class="language-c">DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&amp;sd, sizeof(sd));
sd.BufferCount = 1; 
sd.BufferDesc.Width = 640;
sd.BufferDesc.Height = 480;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60; // 60프레임
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // swap chain 백 버퍼 용도 - 렌더링
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1; // 
sd.SampleDesc.Quality = 0; // 다중 샘플링을 이용하지 않으므로
sd.Windowed = TRUE;

if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, featureLevels, numFeatureLevels,
    D3D11_SDK_VERSION, &amp;sd, &amp;g_pSwapChain, &amp;g_pd3dDevice, NULL, &amp;g_pImmediateContext))) 
{
    return FALSE;
}</code></pre>
<p>다음으로 할 일은 스왑 체인의 백 버퍼를 render target으로 바인딩해야 하기위해 render target view를 작성하는 것이다. </p>
<p>render target view는
<a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=agebreak&amp;logNo=60114584311">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=agebreak&amp;logNo=60114584311</a>
에 쉽게 설명이 되어있다. </p>
<p>먼저 백 버퍼의 객체를 얻기 위해 GetBuffer()을 호출한다. 
이번에는 기본 render target view만 있으면 되므로 CreateRenderTargetView의 두번째 매개변수는 NULL에 해당한다. </p>
<p>render target view를 만들었으면 immediate context에서 OMSetRenderTargets()를 호출하여 파이프라인에 바인딩할 수 있다. 
최종적으로 파이프라인 렌더링의 결과물이 백 버퍼에 기록된다. </p>
<br>


<p>🔎 ** render target view를 만들고 설정 **</p>
<pre><code class="language-c">// Create a render target view
ID3D11Texture2D* pBackBuffer;
if (FAILED(g_pSwapChain-&gt;GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&amp;pBackBuffer)))
    return FALSE;
hr = g_pd3dDevice-&gt;CreateRenderTargetView(pBackBuffer, NULL, &amp;g_pRenderTargetView);
pBackBuffer-&gt;Release();
if (FAILED(hr))
    return FALSE;
g_pImmediateContext-&gt;OMSetRenderTargets(1, &amp;g_pRenderTargetView, NULL);</code></pre>
<p>마지막으로 해야하는 것은 뷰포트를 초기화하는 것이다. 
뷰포트는 clip space coordinate이다. (X와 Y는 -1<del>1, Z는 0</del>1)
왼쪽 상단 점을 (0, 0)이다. 너비 및 높이는 렌더 대상의 크기와 동일하게 설정한다.</p>
<br>

<p>🔎 viewport 초기화</p>
<pre><code class="language-c">  D3D11_VIEWPORT vp;
  vp.Width = (FLOAT)width;
  vp.Height = (FLOAT)height;
  vp.MinDepth = 0.0f;
  vp.MaxDepth = 1.0f;
  vp.TopLeftX = 0;
  vp.TopLeftY = 0;
  g_pImmediateContext-&gt;RSSetViewports( 1, &amp;vp );</code></pre>
<br>
<br>

<h2 id="📌-modifying-the-message-loop-메세지-루프-수정하기">📌 Modifying the Message Loop (메세지 루프 수정하기)</h2>
<br>

<p>GetMessage()는 대기열에 메세지가 없으면 GetMessage()가 차단되어 메세지를 사용할 수 있을 때까지 반환되지 않는다.
메세지가 안들어오더라도 그동안 다른 걸 수행해야 하므로GetMessage()대신 PeekMessage()를 사용한다. PeekMessage()는 대기열에 메세지가 없어도 PeekMessage()가 즉시 반환되어 렌더링을 할 수 있다.  </p>
<br>

<p>🔎 GetMessage() 대신 PeekMessage() 사용하기</p>
<pre><code class="language-c">    MSG msg = {0};
    while( WM_QUIT != msg.message )
    {
        if( PeekMessage( &amp;msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &amp;msg );
            DispatchMessage( &amp;msg );
        }
        else
        {
            Render();  // Do some rendering
        }
    }</code></pre>
<br>
<br>

<h2 id="📌-the-rendering-code-렌더링-코드">📌 The Rendering code (렌더링 코드)</h2>
<br>

<p>렌더링은 Render() 함수에서 완료된다. 
예시에서는 단일 색으로 화면을 칠했다.
이를 위해 ClearRanderTargetView() 함수를 이용한다. 화면을 채우고 싶은 색상을 설정한다음 ClearRenderTargetView()에 전달한다. 백 버퍼를 채우면 스왑 체인의 Present() 메소드를 호출하여 렌더링을 완료한다. </p>
<p>** Present() ** : 스왑 체인의 백 버퍼를 사용자가 볼 수 있도록 화면에 표시하는 역할 </p>
<br>

<p>🔎 렌더링 </p>
<pre><code class="language-c">
    void Render()
    {
        //
        // Clear the backbuffer
        //
        float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA
        g_pd3dDevice-&gt;ClearRenderTargetView( g_pRenderTargetView, ClearColor );

        g_pSwapChain-&gt;Present( 0, 0 );
    }
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Windows - 창 (2)]]></title>
            <link>https://velog.io/@mi_pine/Windows-%EC%B0%BD-2</link>
            <guid>https://velog.io/@mi_pine/Windows-%EC%B0%BD-2</guid>
            <pubDate>Thu, 03 Feb 2022 07:44:31 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/ko-kr/windows/win32/learnwin32/your-first-windows-program">https://docs.microsoft.com/ko-kr/windows/win32/learnwin32/your-first-windows-program</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<br>

<hr>
<br> 

<p>모듈1. 첫 번째 Windows 프로그램 </p>
<p>4) 창 그리기
5) 창 닫기
6) 어플리케이션 상태 관리 </p>
<br>

<hr>
<br>

<h1 id="📍-creating-a-window">📍 Creating a Window</h1>
<p>창을 처음 표시할 때나, 창의 일부를 다시 그려야 할 때 운영체제는 창에 WM_PAINT 메세지를 보낸다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/94e980d5-1d44-44fd-b4b7-bbb2a4aa2d9a/image.png" alt=""></p>
<p>우리는 여기서 Client Area만 그리면 된다. 그리고 업데이트 영역이 남아있는 동안에는 다른 WM_PAINT 메세지를 보낼 필요가 없다는 것을 운영체제에게 알린다. 이 업데이트 영역은 클라이언트 영역 그리기를 완료한 후 지우면 된다.  </p>
<p>다른 창으로 창의 일부를 가렸다가 다시 보이게 된 상황에서도 해당 부분이 업데이트 영역에 추가되고 창에 다른 WM_PAINT 메세지가 표시된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/65cf4ddf-c254-48ac-80ab-cabb581a4755/image.png" alt=""></p>
<p>창을 늘이는 경우에도 마찬가지이다. </p>
<p>🔎 전체 클라이언트 영역 단색으로 채우기 </p>
<pre><code class="language-cpp">switch (uMsg)
{
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &amp;ps);

        // All painting occurs here, between BeginPaint and EndPaint.

        FillRect(hdc, &amp;ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));

        EndPaint(hwnd, &amp;ps);
    }
    return 0;
}</code></pre>
<p>1) BeginPaint 함수를 호출하여 그리기 작업 시작
: PAINTSTRUCT 구조체를 요청에 대한 정보로 채우는 함수</p>
<p>2) 현재 업데이트 영역은 PAINTSTRUCT의 rcPaint 멤버에 저장
(업데이트 영역은 클라이언트 영역 기준)</p>
<p>3) FillRect로 시스템 정의 창 배경색을 이용하여 업데이트 영역을 단일 색으로 채우기</p>
<p>4) 그리기 작업이 완료되면 EndPaint 호출 </p>
<p>🔎 FillRect</p>
<pre><code class="language-cpp">FillRect(hdc, &amp;ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));</code></pre>
<p>두번째 매개변수 : 채울 사각형의 좌표 
여기서는 PAINTSTRUCT의 rcPaint 멤버이며 rcPaint에는 전체 클라이언트 영역이 포함된다. </p>
<br>

<hr>
<br>

<h1 id="📍-closing-the-window">📍 Closing the Window</h1>
<p>사용자가 창을 닫으면 창에서 WM_CLOSE 메세지가 수신된다. WM_CLOSE 메세지는 창을 닫기 전에 사용자에게 메세지를 표시하며 창을 닫으려면 DestroyWindow 함수를 호출, 그렇지 않으면 운영 체제에서 메세지를 무시하고 창을 삭제하지 않는다(WM_CLOSE 메세지가 0을 반환)</p>
<p>🔎 프로그램이 WM_CLOSE를 처리하는 방법</p>
<pre><code class="language-cpp">case WM_CLOSE:
    if (MessageBox(hwnd, L&quot;Really quit?&quot;, L&quot;My application&quot;, MB_OKCANCEL) == IDOK)
    {
        DestroyWindow(hwnd);
    }
    // Else: User canceled. Do nothing.
    return 0;</code></pre>
<p>이 경우 사용자가 확인(OK)을 클릭하면 프로그램에서 DestroyWindow를 호출하고, 그렇지 않고 취소(Cancle)을 클릭하면 호출을 건너뛰고 창이 열린 상태로 유지된다. </p>
<p>사용자에게 메세지를 표시하지 않고 창을 닫으려면 DefWindowProc을 이용할 수 있다. WM_CLOSE의 경우 DefWindowProc은 DestroyWindow를 자동으로 호출한다. </p>
<p>창이 제거될 때 WM_DESTROY 메세지를 받는다. 이 메세지는 소멸하기 전에 전송되며, 일반적으로 PostQuitMessage를 호출하여 WM_DESTOY에 응답한다. </p>
<pre><code class="language-cpp">case WM_DESTROY:
    PostQuitMessage(0);
    return 0; </code></pre>
<p>🔎 WM_CLOSE 및 WM_DESTROY 메세지를 처리하는 일반적인 방법</p>
<p><img src="https://images.velog.io/images/mi_pine/post/12665dba-6fba-4763-9105-8682dded0f05/image.png" alt=""></p>
<br>

<hr>
<br>

<h1 id="📍-managing-application-state">📍 Managing Application State</h1>
<p>윈도우 프로시저는 상태가 저장되지 않기 때문에 한 함수 호출에서 다음 함수 호출로 어플리케이션의 상태를 추적하는 방법이 필요하다.</p>
<p>🔎 전역 변수에 모두 배치</p>
<p>소규모 프로그램에서 good. 
대규모에서는 좋지않다. (윈도우 프로시저가 많다, 전역 변수 확산)</p>
<p>🔎 ** CreateWindowEx 함수 **</p>
<p>모든 데이터 구조를 창에 전달한는 방법을 제공한다. 이 함수가 호출되면 윈도우 프로시저에 다음 두 개의 메세지를 보낸다. </p>
<ul>
<li>WM_NCCREATE</li>
<li>WM_CREATE</li>
</ul>
<p>이 두 메세지는 창이 표시되기 전에 전송된다. </p>
<p>CreateWindowEx의 마지막 매개 변수는 void* 형식의 포인터이다. </p>
<p>이제 이 매개변수를 사용하여 어플리케이션 데이터를 창에 전달하는 방법을 알아보자. </p>
<p>1) 상태 정보를 보유하는 클래스 또는 구조체를 정의한다. </p>
<pre><code class="language-cpp">struct StateInfo { };</code></pre>
<p>2) CreateWindowEx를 호출하는 경우 최종 <em>* void</em> 매개 변수에서 이 구조체에 대한 포인터를 전달 **</p>
<pre><code class="language-cpp">StateInfo *pState = new (std::nothrow) StateInfo;

if (pState == NULL)
{
    return 0;
}

// Initialize the structure members (not shown).

HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L&quot;Learn to Program Windows&quot;,    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style

    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

    NULL,       // Parent window    
    NULL,       // Menu
    hInstance,  // Instance handle
    pState      // Additional application data
    );</code></pre>
<p>WM_NCCREATE 및 WM_CREATE 각 메세지의 lParam 매개 변수는 CREATESTRUCT 구조체에 대한 포인터이다. 그리고 이 CREATETRUCT 구조체에는 CreateWindowEx에 전달한 포인터가 포함되어 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/c31d967a-3ff1-4d12-9c50-260c7fc01d31/image.png" alt=""></p>
<p>🔎 데이터 구조에 대한 포인터 추출 </p>
<p>1) lParam 매개 변수를 캐스팅하여 CREATESTRUCT 구조체 얻기</p>
<pre><code class="language-cpp">CREATESTRUCT *pCreate = reinterpret_cast&lt;CREATESTRUCT*&gt;(lParam);</code></pre>
<p>2) lpCreateParams를 캐스팅하여 사용자 고유의 데이터 구조에 대한 포인터 얻기</p>
<pre><code class="language-cpp">pState = reinterpret_cast&lt;StateInfo*&gt;(pCreate-&gt;lpCreateParams);</code></pre>
<p>3) SetWindowLongPtr 함수를 호출하고 포인터를 데이터 구조에 전달</p>
<pre><code class="language-cpp">SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);</code></pre>
<p>: 이렇게 하면 항상 GetWindowLongPtr을 호출하여 창에서 포인터를 다시 얻을 수 있다. </p>
<pre><code class="language-cpp">LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast&lt;StateInfo*&gt;(ptr);</code></pre>
<p>이 방법은 클래스의 창을 2개 이상 만드는 경우에 유용하다. GetWindwoLongPtr의 호출을 작은 도우미 함수로 래핑하는 것이 편리하다. </p>
<pre><code class="language-cpp">inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast&lt;StateInfo*&gt;(ptr);
    return pState;
}</code></pre>
<p>🔎 최종적으로 작성한 윈도우 프로시저 </p>
<pre><code class="language-cpp">LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;
    if (uMsg == WM_CREATE)
    {
        CREATESTRUCT *pCreate = reinterpret_cast&lt;CREATESTRUCT*&gt;(lParam);
        pState = reinterpret_cast&lt;StateInfo*&gt;(pCreate-&gt;lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
    }
    else
    {
        pState = GetAppState(hwnd);
    }

    switch (uMsg)
    {


    // Remainder of the window procedure not shown ...

    }
    return TRUE;
}</code></pre>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[📘17장 Advanced Uses of Pointers | Study]]></title>
            <link>https://velog.io/@mi_pine/17%EC%9E%A5-Advanced-Uses-of-Pointers-Study</link>
            <guid>https://velog.io/@mi_pine/17%EC%9E%A5-Advanced-Uses-of-Pointers-Study</guid>
            <pubDate>Thu, 03 Feb 2022 07:34:33 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING으로 C언어를 공부하면서 정리한 내용입니다.</p>
<hr>
<br>
17.1 Dynamic Storage Allocation <br>
17.2 Dynamic Allocated Strings <br>
17.3 Dynamic Allocated Arrays <br>
17.4 Deallocating Storage <br>
17.5 Linked Lists <br>
17.6 Pointers to Pointers <br>
17.7 Pointers to Functions <br>
<br>

<hr>
<br>

<h1 id="📍-dynamic-storage-allocation-동적-메모리-할당">📍 Dynamic Storage Allocation (동적 메모리 할당)</h1>
<p>고정된 사이즈 자료구조를 이용할 경우 프로그램을 수정하고, 다시 컴파일하지 않고는 size를 변경할수가 없다. </p>
<p>따라서 C는 동적 메모리 할당(Dynamic Storage Allocation) 기능을 제공한다. 이는 실행 시간동안 사용할 메모리 공간을 할당받는 것을 말한다. </p>
<br> 
<br>

<h2 id="📌-memory-allocation-functions-메모리-할당-함수들">📌 Memory Allocation Functions (메모리 할당 함수들)</h2>
<p>🔎 메모리 할당 함수들</p>
<ul>
<li>malloc : 메모리를 할당받고, 초기화는 하지 않는다. </li>
<li>calloc : 메모리를 할당받고, 0으로 초기화한다.</li>
<li>realloc : malloc이나 calloc으로 할당받는 메모리의 크기를 변경할 때 사용한다. </li>
</ul>
<p>보통 malloc이 가장 많이 쓰인다. </p>
<p>어떤 타입이 할당될지 모르기 때문에 함수는 void* 형식을 반환한다. void*은 어떤 타입으로도 변신이 가능하다. </p>
<br> 
<br>

<h2 id="📌-null-pointers-널-포인터">📌 Null Pointers (널 포인터)</h2>
<p>동적 할당 함수를 호출할 때, 그만큼 충분한 공간이 남아있지 않을 가능성이 있다. 그럼 함수는 null pointer를 반환한다. 
null pointer는 아무것도 가리키지 않는 포인터이다. 따라서 함수의 반환값을 포인터 변수에 저장한 후에 반드시 null pointer인지 검사를 해주어야 한다. </p>
<p>null pointer는 매크로에 의해 NULL로 선언되어있다. 보통 #include &lt;stdlib.h&gt;를 이용한다. 
따라서 malloc의 반환 값을 아래와 같이 테스트할 수 있다. </p>
<pre><code class="language-cpp">p = malloc(10000);
if (p == NULL) { 
    /* allocation failed */
}</code></pre>
<p>p == NULL이라고 쓰는 대신
if (!p) ... 도 가능하다. </p>
<br>

<hr>
<br>

<h1 id="📍-dynamic-allocated-strings-br">📍 Dynamic Allocated Strings <br></h1>
<p>(동적 할당된 문자열)</p>
<br> 
<br>

<h2 id="📌-using-malloc-to-allocate-memoery-for-a-string-malloc으로-문자열-함수에-메모리-할당하기">📌 Using malloc to Allocate Memoery for a String (malloc으로 문자열 함수에 메모리 할당하기)</h2>
<p>🔎 malloc 함수</p>
<pre><code class="language-cpp">void *malloc(size_t size); </code></pre>
<p>malloc은 size만큼의 메모리를 할당하고 포인터를 반환한다. </p>
<p>char의 size는 1이기 때문에 malloc을 이용하여 문자열 함수에 메모리를 할당하는 것은 쉽다. </p>
<p>🔎 문자열 함수에 n개의 문자를 저장할 공간 할당하기</p>
<pre><code class="language-cpp">p = malloc(n+1);</code></pre>
<p>그럼 할당을 할 때 p는 char* 이 된다.</p>
<br>
<br>

<h2 id="📌-using-dynamic-storage-allocation-in-string-functions-문자열-함수에서-동적-메모리-할당하기">📌 Using Dynamic Storage Allocation in String Functions (문자열 함수에서 동적 메모리 할당하기)</h2>
<p>두 문자열이 주어졌을 때, 둘다 바꾸지 않고 문자열을 합쳐보자. 
그러려면 먼저 두 문자열의 길이를 측정한 다음 malloc을 이용하면 된다. 
그 다음 첫번째 문자열을 새 공간에 복사한다음 strcat을 호출하여 두번째 문자열을 연결한다. </p>
<p>🔎 malloc을 이용하여 문자열 합치기</p>
<pre><code class="language-cpp">char* concat(const char* s1, const char* s2)
{
    char* result;

    result = malloc(strlen(s1) + strlen(s2) + 1);
    if (result == NULL) {
        printf(&quot;Error : malloc failed in concat\n&quot;);
        exit(EXIT_FAILURE);
    }
    strcpy(result, s1);
    strcat(result, s2);
    return result; 
}</code></pre>
<br>
<br>

<h2 id="📌-arrays-of-dynamically-allocated-strings-동적-할당-문자열의-배열">📌 Arrays of Dynamically Allocated Strings (동적 할당 문자열의 배열)</h2>
<p>앞서 문자열을 2차원 배열에 저장하면 공간이 낭비된다는 문제로 인해, 문자열을 포인터로 가리키는 배열을 이용하였다. 이는 동적 할당된 문자열에도 이용될 수 있다. </p>
<br>
<br>

<h2 id="📌-program-printing-a-one-month-reminder-list-revisited-한-달-리마인더-목록-프린트하기">📌 [Program] Printing a One-Month Reminder List (Revisited) (한 달 리마인더 목록 프린트하기)</h2>
<pre><code class="language-c">#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;stdio.h&gt;

#define MAX_REMIND (50)
#define MSG_LEN (60)

int read_line(char str[], int n);

int main()
{
    char* reminders[MAX_REMIND];
    char day_str[3], msg_str[MSG_LEN + 1];
    int day, i, j, num_remind = 0;

    for (;;) {
        if (num_remind == MAX_REMIND) {
            printf(&quot;-- No space left --\n&quot;);
            break; 
        }

        printf(&quot;Enter day and reminder : &quot;);
        scanf(&quot;%2d&quot;, &amp;day);
        if (day == 0)
            break;
        sprintf(day_str, &quot;%2d&quot;, day);
        read_line(msg_str, MSG_LEN);

        for (i = 0; i &lt; num_remind; i++)
            if (strcmp(day_str, reminders[i] &lt; 0))
                break;
        for (j = num_remind; j &gt; i; j--)
            reminders[j] = reminders[j - 1];

        reminders[i] = malloc(2 + strlen(msg_str) + 1);
        if (reminders[i] == NULL) {
            printf(&quot;-- No space left --\n&quot;);
            break; 
        }

        strcpy(reminders[i], day_str);
        strcat(reminders[i], msg_str);

        num_remind++;
    }

    printf(&quot;\nDay Reminder\n&quot;);
    for (i = 0; i &lt; num_remind; i++)
        printf(&quot; %s\n&quot;, reminders[i]);

    return 0; 
}

int read_line(char str[], int n)
{
    int ch, i = 0;

    while ((ch = getchar()) != &#39;\n&#39;)
        if (i &lt; n)
            str[i++] = ch;
    str[i] = &#39;\0&#39;;
    return i; 
}</code></pre>
<br>

<hr>
<br>

<h1 id="📍-dynamic-allocated-arrays-동적-할당된-배열">📍 Dynamic Allocated Arrays (동적 할당된 배열)</h1>
<p>C에서는 프로그램이 실행 중에 배열에 공간을 할당할 수 있다. </p>
<br>
<br>

<h2 id="📌-using-malloc-to-allocate-storage-for-an-array-malloc으로-배열-메모리-할당하기">📌 Using malloc to Allocate Storage for an Array (malloc으로 배열 메모리 할당하기)</h2>
<p>문자열에서와 가장 큰 차이점은 요소의 크기가 반드시 1byte가 아니라는 점이다. 따라서 sizeof 연산자를 이용하여 크기를 계산해주어야 한다. </p>
<p>n개의 정수를 담는 array를 만들어보자. </p>
<p>먼저 포인터 변수를 선언해준다.</p>
<pre><code class="language-c">int *a;</code></pre>
<p>n이 이미 선언되어 있을 때, 아래와 같이 malloc을 이용하여 공간을 할당할 수 있다. </p>
<pre><code class="language-c">a = malloc(n * sizeof(int));</code></pre>
<p>이렇게 하고 나면 a를 배열의 이름으로 사용할 수 있다. </p>
<br>
<br>

<h2 id="📌-the-calloc-function-calloc-함수">📌 The calloc Function (calloc 함수)</h2>
<p>calloc은 &lt;stdlib.h&gt;에 선언되어 있다.</p>
<p>🔎 calloc 함수</p>
<pre><code class="language-c">void *calloc(size_t nmemb, size_t size);</code></pre>
<p>calloc은 size 크기의 변수를 nmemb개 만큼 저장할 수 있도록 한다. </p>
<p>메모리를 할당한 다음 calloc은 0으로 초기화한다. </p>
<p>🔎 calloc 함수 사용 예시 </p>
<pre><code class="language-c">a = calloc(n, sizeof(int)); 

struct point { int x, y; } *p;
p = calloc(1, sizeof(struct point)); </code></pre>
<br>
<br>

<h2 id="📌-the-realloc-function-realloc-함수">📌 The realloc Function (realloc 함수)</h2>
<p>realloc은 할당된 메모리(ex.array)의 크기를 조절한다. </p>
<p>🔎 realloc 함수</p>
<pre><code class="language-c">void *realloc(void *ptr, size_t size);</code></pre>
<p>ptr은 반드시 malloc, calloc, realloc에 의해 얻어진 메모리 블록을 가리켜야한다. size는 블록의 새로운 사이즈를 말한다. 보통은 재할당된 메모리를 ptr이 다시 가리킨다. </p>
<p>메모리 블록의 크기를 줄이라는 요청을 받았을 때 realloc은 블록에 저장된 데이터를 이동시키지 않고 축소해야 한다. 확장할 때도 마찬가지이다. 만약 블록 뒤에 오는 바이트가 사용되고 있어 확장할 수 없을 때, realloc은 새 블록을 다른 곳에 할당한 다음 이전 블록의 내용을 새 블록에 복사한다. </p>
<br>

<hr>
<br>

<h1 id="📍-deallocating-storage-메모리-해제">📍 Deallocating Storage (메모리 해제)</h1>
<p>malloc과 다른 메모리 할당 함수는 힙(heap)이라고 하는 저장소 풀(storage pool)에서 메모리 블록을 가져온다. 이러한 함수를 너무 자주 호출하거나 큰 메모리 블록을 요청하면 힙이 소진되어 함수가 널 포인터를 반환할 수 있습니다.
심지어 할당된 메모리를 추적하지 못해 공간을 낭비할 수 있다. </p>
<pre><code class="language-c">p = malloc(...);
q = malloc(...);
p = q; </code></pre>
<p>위 예제의 경우 아래처럼 되므로 첫번째 block은 영원히 쓰이지 않는다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/8597bcc9-fdb6-4f00-9071-e09f2da2d8d8/image.png" alt=""></p>
<p>이렇게 더 이상 쓰이지 않는 메모리를 garbage라고 부른다. 이런 garbage가 발생하는 프로그램에서는 메모리 누수(memory leak)이 발생한다. 몇몇 언어에서는 garbage collector을 제공하여 자동으로 처리해주나 C는 그렇지 않다. 대신 free 함수를 이용하여 불필요한 메모리를 해제해 주어야 한다. </p>
<br>
<br>

<h2 id="📌-the-free-function-free-함수">📌 The free function (free 함수)</h2>
<p>free 함수는 &lt;stdlib.h&gt;에 들어 있다.</p>
<p>🔎 free 함수</p>
<pre><code class="language-c">void free(void *ptr);</code></pre>
<p>🔎 free 함수 사용 예시</p>
<pre><code class="language-c">p = malloc(...);
q = malloc(...);
free(p);
p = q;</code></pre>
<p>이렇게 하여 p가 가리키는 메모리 블락을 해제할 수 있다. 이 때 free의 인수는 메모리 할당 함수에 의해 반환된 포인터이어야 한다. </p>
<br>
<br>

<h2 id="📌-the-dangling-pinter-problem-매달린-포인터-문제">📌 The &quot;Dangling Pinter&quot; Problem (&quot;매달린 포인터&quot; 문제)</h2>
<p>free(p)는 p가 가리키는 것은 해제하지만 p 자체를 바꾸지는 않는다. 
따라서 아래와 같이 사용하게 되면 혼돈을 야기한다. </p>
<pre><code class="language-c">char *p = malloc(4);
...
free(p);
...
strcpy(p, &quot;abc&quot;); /*** WRONG ***/</code></pre>
<p>여러 포인터들이 같은 메모리 블록을 가리킬 수 있기 때문에 매달린 포인터들은 찾기 어려울 수 있다. 블록이 해제되면 모든 포인터가 매달린 상태로 유지된다. </p>
<br>

<hr>
<br>

<h1 id="📍-linked-lists-연결-리스트">📍 Linked Lists (연결 리스트)</h1>
<p>연결 리스트(linked list)는 각 노드가 데이터와 포인터를 가지고 한 줄로 연결되어 있는 것을 말한다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/c4d74cec-8159-4575-80f8-415d1237075f/image.png" alt=""></p>
<p>마지막 노드는 null pointer를 가지고 있다. </p>
<p>연결 리스트는 배열의 대안이 될 수 있다. 연결 리스트를 이용하면 쉽게 요소를 추가하고 삭제할 수 있다. 그러나 &quot;random access&quot;는 불가능하다. 어레이의 경우 모든 요소에 접근하는데 동일한 시간이 소요되지만 연결 리스트의 경우 가까울수록 빠르다. </p>
<br>
<br>

<h2 id="📌-declaring-a-node-type-노드형-선언하기">📌 Declaring a Node Type (노드형 선언하기)</h2>
<p>노드가 정수와 다음 노드를 가리키는 포인터만을 가지고 있다고 가정하자. </p>
<pre><code class="language-c">struct node {
    int value; /* data stored in the node */
    struct node* next; /* pointer to the next node */
};</code></pre>
<p>list가 어디에서부터 시작하는지를 추적하기 위해 첫번째 노드를 가리키는 변수가 필요하다. 이를 first라고 하였다. </p>
<pre><code class="language-c">struct node *first = NULL:</code></pre>
<p>처음엔 비어 있으므로 first에 NULL값을 부여하였다. </p>
<br>
<br>

<h2 id="📌-creating-a-node-노드-생성하기">📌 Creating a Node (노드 생성하기)</h2>
<p>노드를 아래 3단계로 생성한다. </p>
<p>1) 노드를 위한 메모리를 할당한다.
2) 노드에 데이터를 저장한다.
3) 리스트에 노드를 넣는다. </p>
<p>노드를 생성할 때 노드를 목록에 삽입할 때까지 노드를 일시적으로 가리킬 수 있는 변수가 필요하다. 이를 new_node라고 하자. </p>
<pre><code class="language-c">struct node *new_node;</code></pre>
<p>malloc을 사용하여 새 노드에 메모리를 할당하고 반환 값을 new_node에 저장한다. </p>
<pre><code class="language-c">new_node = malloc(sizeof(struct node)); </code></pre>
<p><img src="https://images.velog.io/images/mi_pine/post/1e30623d-1be9-4f31-b9fb-4a7edc7d52c8/image.png" alt=""></p>
<p>다음으로, 데이터를 새 노드의 value에 저장한다. </p>
<pre><code class="language-c">(*new_node).value = 10;</code></pre>
<p><img src="https://images.velog.io/images/mi_pine/post/79f43c45-9d50-433a-9cfe-85addc7e420a/image.png" alt=""></p>
<br>
<br>

<h2 id="📌-the---operator---연산자">📌 The -&gt; Operator (-&gt; 연산자)</h2>
<p>-&gt; 연산자 : 포인터를 사용하여 구조체의 멤버에 접근 </p>
<p>🔎 -&gt; 연산자 사용 예시</p>
<pre><code class="language-c">new_node-&gt;value = 10; </code></pre>
<p>-&gt; 연산자는 *과 .의 결합이라고 보면 된다. new_node가 가리키는 값을 찾은다음 그 구조체의 멤버를 선택한다. </p>
<p>-&gt; 연산자는 lvalue를 생성한다. </p>
<p>🔎 -&gt; 연산자 사용 예시 (2) - scnaf에서</p>
<pre><code class="language-c">scanf(&quot;%d&quot;, &amp;new_node-&gt;value); </code></pre>
<br>
<br>

<h2 id="📌-inserting-a-node-at-the-beginning-of-a-linked-list-연결-리스트-시작점에-노드-추가하기">📌 Inserting a Node at the Beginning of a Linked List (연결 리스트 시작점에 노드 추가하기)</h2>
<p>new_node : node가 삽입되는 지점을 가리킨다.
first : 연결리스트의 첫번째 노드를 가리킨다. </p>
<p>이 두가지를 이용하여 연결 리스트의 첫번째에 노드를 추가할 수 있다. </p>
<p>먼저 new_node의 next 멤버가 첫번째 노드에 저장된 것을 가리키게 한다.</p>
<pre><code class="language-c">new_node-&gt;next = first;</code></pre>
<p>두번째로, first가 new_node를 가리키게 한다. </p>
<pre><code class="language-c">first = new_node </code></pre>
<p>🔎 비어있는 연결 리스트에 10 추가 후 20 추가</p>
<p><img src="https://images.velog.io/images/mi_pine/post/96339bce-7f04-437f-9ff8-b1acff6b61a4/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/mi_pine/post/61c1f426-3101-4d20-86d0-f1612fd7d751/image.png" alt=""></p>
<p>이러한 과정을 add_to_list라는 이름의 함수로 만들어보자. 파라미터는 list(추가 전 리스트의 첫번째 노드를 가리키는 포인터), n(첫번째 노드에 저장할 정수) 두가지 이다. </p>
<pre><code class="language-c">struct node* add_to_list(struct node* list, int n)
{
    struct node* new_node;

    new_node = malloc(sizeof(struct node));
    if (new_node == NULL) {
        printf(&quot;Error : malloc failed in add_to_list\n&quot;);
        exit(EXIT_FAILURE);
    }
    new_node-&gt;value = n;
    new_node-&gt;next = list;
    return new_node; 
}</code></pre>
<p>first가 가리키는 지점은 변하지 않았으므로 add_to_list를 다시 호출하기 전에 반환값을 first에 저장해주어야 한다. </p>
<pre><code class="language-c">first = add_to_list(first, 10);
first = add_to_list(first, 20);</code></pre>
<p>🔎 add_to_list 사용 예시 - 사용자가 입력한 정수 넣기</p>
<p>struct node* read_numbers(void)
{
    struct node* first = NULL;
    int n;</p>
<pre><code>printf(&quot;Enter a series of integers (0 to terminate) : &quot;);
for (;;) {
    scanf(&quot;%d&quot;, &amp;n);
    if (n == 0)
        return first;
    first = add_to_list(first, n);
}</code></pre><p>}</p>
<br>
<br>

<h2 id="📌-searching-a-linked-list-연결-리스트에서-탐색하기">📌 Searching a Linked List (연결 리스트에서 탐색하기)</h2>
<p>🔎 포인터 p를 이용하여 노드 추적 </p>
<pre><code class="language-c">for (p = first; p != NULL; p = p-&gt;next) {
    ...
}</code></pre>
<p>🔎 search_list (1) </p>
<pre><code class="language-c">struct node* search_list(struct node* list, int n)
{
    struct node* p;

    for (p = list; p != NULL; p = p-&gt;next)
        if (p-&gt;value == n)
            return p;
    return NULL
}</code></pre>
<p>🔎 search_list (2)</p>
<p>두번째 방법은 p를 이용하지 않고 list 자체를 이용하는 것이다.</p>
<pre><code class="language-c">struct node* search_list(struct node* list, int n)
{
    for (; list != NULL; list = list-&gt;next)
        if (list-&gt;value == n)
            return list;
    return NULL;
}</code></pre>
<p>list는 복사본이기 때문에 바꿔도 상관없다. </p>
<p>🔎 search_list (3)</p>
<p>세번째 방법은 while 문을 이용하는 것이다. </p>
<pre><code class="language-c">struct node* search_list(struct node* list, int n)
{
    while (list != NULL &amp;&amp; list-&gt;value != n)
        list = list-&gt;next;
    return list;
}</code></pre>
<br>
<br>

<h2 id="📌-deleting-a-node-from-a-linked-list-연결-리스트에서-노드-삭제하기">📌 Deleting a Node from a Linked List (연결 리스트에서 노드 삭제하기)</h2>
<p>연결리스트의 큰 장점은 노드를 삭제하기가 쉽다는 것이다. 노드를 삭제하는 것은 다음 3단계로 이루어진다. </p>
<p>1) 지울 노드가 있는 곳으로 간다. 
2) 삭제된 노드를 &quot;통과&quot;하도록 이전 노드를 변경한다.
3) free를 호출하여 삭제된 노드가 사용하던 공간을 지운다.  </p>
<p>1번 단계에서 지울 노드가 있는 곳으로 가면 2번 단계에서 이전 단계에 대한 일을 수행할 수가 없다. 
이 문제를 &quot;trailing pointer&quot; 기술을 이용하여 해결할 수 있다. 
1단계에서 목록을 검색할 때 현재 노드를 가리키는 포인터(cur) 뿐만 아니라 이전 노드를 가리키는 포인터(prev)도 있게 한다. </p>
<pre><code class="language-c">for (cur = list, prev = NULL; cur != NULL &amp;&amp; cur-&gt;value != n; prev = cur, cur = cur-&gt;next); </code></pre>
<p>30 -&gt; 40 -&gt; 20 -&gt; 10 이 연결되어 있는 연결리스트에서 20을 찾는다고 할 때 위 구문을 이용하여 1단계를 수행하면 아래와 같이 된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/4bb3dfde-0aca-4b70-97d9-860ddfb5b4cb/image.png" alt=""></p>
<p>여기서 2단계를 수행하면 다음과 같다. </p>
<p>🔎 삭제할 노드의 이전 노드와 다음 노드를 연결해주기</p>
<pre><code class="language-c">prev-&gt;next = cur-&gt; next;</code></pre>
<p><img src="https://images.velog.io/images/mi_pine/post/758669b8-7b60-46c6-bc09-a7fffe5819cc/image.png" alt=""></p>
<p>🔎 20 삭제</p>
<pre><code class="language-c">free(cur);</code></pre>
<p>이 세가지 과정을 통해 원하는 정수를 삭제하도록 하는 함수를 만들어보면 다음과 같다. </p>
<p>🔎 delete_from_list</p>
<pre><code class="language-c">struct node* delete_from_list(struct node* list, int n)
{
    struct node* cur, * prev;

    for (cur = list, prev = NULL; cur != NULL &amp;&amp; cur-&gt;value != n; prev = cur, cur = cur-&gt;next);

    if (cur == NULL)
        return list; /* n was not found */
    if (prev == NULL)
        list = list-&gt;next; /* n is in the first node */
    else
        prev-&gt;next = cur-&gt;next; /* n is in some other node */
    free(cur);
    return list; 
}</code></pre>
<br>
<br>

<h2 id="📌-ordered-lists-정렬된-리스트">📌 Ordered Lists (정렬된 리스트)</h2>
<p>정렬된 리스트 : 노드들이 순서대로 정렬되어 있는 경우
이 경우 노드를 삽입하는 것은 더 어렵지만 검색은 더 빠르다. </p>
<br>
<br>

<h2 id="📌-program-maintaining-a-prats-database-revisited-부품-데이터베이스-유지관리-재방문">📌 [Program] Maintaining a Prats Database (Revisited) (부품 데이터베이스 유지관리 (재방문))</h2>
<p>🔎 inventory2.c</p>
<pre><code class="language-c">/* Maintains a parts database (linked list version) */

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &quot;readline.h&quot;

#define NAME_LEN (25)

struct part {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
    struct part* next;
};

struct part* inventory = NULL; /* points to first part */

struct part* find_part(int number);
void insert(void);
void search(void);
void update(void);
void print(void);

int main(void)
{
    char code;

    for (;;) {
        printf(&quot;Enter operation code : &quot;);
        scanf(&quot; %c&quot;, &amp;code);
        while (getchar() != &#39;\n&#39;);
        switch (code) {
        case &#39;i&#39;: insert();
            break;
        case &#39;s&#39;: search();
            break;
        case &#39;u&#39;: update();
            break;
        case &#39;p&#39;: print();
            break;
        case &#39;q&#39;: return 0;
        default: printf(&quot;Illegal code\n&quot;);
        }
        printf(&quot;\n&quot;);
    }
}

struct part* find_part(int number)
{
    struct part* p;

    for (p = inventory; p != NULL &amp;&amp; number &gt; p-&gt;number; p = p-&gt;next);
    if (p != NULL &amp;&amp; number == p-&gt;number)
        return p;
    return NULL;
}

void insert(void)
{
    struct part* cur, * prev, * new_node;

    new_node = malloc(sizeof(struct part));
    if (new_node == NULL) {
        printf(&quot;Database is full; can&#39;t add more parts.\n&quot;);
        return;
    }

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;new_node-&gt;number);

    for (cur = inventory, prev = NULL; cur != NULL &amp;&amp; new_node-&gt;number &gt; cur-&gt;number; prev = cur, cur = cur-&gt;next);
    if (cur != NULL &amp;&amp; new_node-&gt;number == cur-&gt;number) {
        printf(&quot;Part already exists.\n&quot;);
        free(new_node);
        return; 
    }

    printf(&quot;Enter part name : &quot;);
    read_line(new_node-&gt;name, NAME_LEN);
    printf(&quot;Enter quantity on hand : &quot;);
    scanf(&quot;%d&quot;, &amp;new_node-&gt;on_hand);

    new_node-&gt;next = cur;
    if (prev == NULL)
        inventory = new_node;
    else
        prev-&gt;next = new_node; 
}

void search(void)
{
    int number;
    struct part* p;

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;number);
    p = find_part(number);
    if (p != NULL) {
        printf(&quot;Part name : %s\n&quot;, p-&gt;name);
        printf(&quot;Quantity on hand : %d\n&quot;, p-&gt;on_hand);
    } 
    else {
        printf(&quot;Part not found.\n&quot;);
    }
}

void update(void)
{
    int number, change;
    struct part* p;

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;number);
    p = find_part(number);
    if (p != NULL) {
        printf(&quot;Enter change in quantity on hand : &quot;);
        scanf(&quot;%d&quot;, &amp;change);
        p-&gt;on_hand += change;
    }
    else
        printf(&quot;Part not found.\n&quot;);
}

void print(void)
{
    struct part* p;
    printf(&quot;Part Number Part Name Quantity on Hand&quot;);
    for (p = inventory; p != NULL; p = p-&gt;next)
        printf(&quot;%7d        %-25s11d\n&quot;, p-&gt;number, p-&gt;name, p-&gt;on_hand);
}</code></pre>
<br>

<hr>
<br>

<h1 id="📍-pointers-to-pointers-포인터의-포인터">📍 Pointers to Pointers (포인터의 포인터)</h1>
<p>포인터를 매개변수로 받는 함수가 다른 곳을 가리키게 함으로써 변수를 수정하고자 할 때 포인터의 포인터를 이용한다. </p>
<p>앞서 작성한 add_to_list에서는 new_node를 반환한다. 여기서 new_node를 반환하는 대신에 list가 new_node를 가리키도록 바꾼다고 해보자. </p>
<pre><code class="language-c">list = new_node </code></pre>
<p>포인터는 pass by value기 때문에 위 구문은 잘 작동하지 않는다. </p>
<p>올바른 코드는 다음과 같다.</p>
<p>🔎 포인터의 포인터를 이용한 add_to_list</p>
<pre><code class="language-c">void add_to_list(struct node** list, int n)
{
    struct node* new_node;

    new_node = malloc(sizeof(struct node));
    if (new_node == NULL) {
        printf(&quot;Error : malloc failed in add_to_list\n&quot;);
        exit(EXIT_FAILURE);
    }
    new_node-&gt;value = n;
    new_node-&gt;next = *list;
    *list = new_node; 
}</code></pre>
<p>이렇게 하면 new_node의 next가 원래 list가 가리키던 곳(첫번째였던 노드)를 가리키게 되고, list가 가리키는 곳은 new_node로 변한다. </p>
<br>

<hr>
<br>

<h1 id="📍-pointers-to-functions-함수-포인터">📍 Pointers to Functions (함수 포인터)</h1>
<p>함수들도 메모리 위치를 차지하기 때문에 C에서는 포인터가 함수를 가리키는 것도 가능하다.</p>
<br>
<br>

<h2 id="📌-function-pointers-as-arguments-입력변수로서의-함수-포인터">📌 Function Pointers as Arguments (입력변수로서의 함수 포인터)</h2>
<p>점 a부터 b까지 적분하는 함수를 만든다고 할 때 f를 함수에 대한 포인터로 선언할 것이다. </p>
<pre><code class="language-c">double integrate(double (*f) (double), double a, double b);</code></pre>
<p>f앞에 포인터를 붙여서 f가 포인터를 반환하는 함수가 아니라 함수에 대한 포인터임을 나타낸다. * 없이 그냥 함수처럼 보이게 쓰는 것도 가능하다. </p>
<pre><code class="language-c">double integrate(double f(double), double a, double b);</code></pre>
<p>integrate를 호출할 때 첫 번째 인수로 함수 이름을 제공한다. 예를 들어 아래 함수는 sin 함수를 0부터 ㅠ/2까지 적분한다. </p>
<pre><code class="language-c">result = integrate(sin, 0.0, PI/2);</code></pre>
<p>컴파일러는 함수 호출을 위한 코드를 생성하는 대신 함수에 대한 포인터를 생성한다. 따라서 위 코드에서 sin을 호출하는 것이 아니라 integrate에 sin의 포인터를 전달하는 것이 된다. </p>
<p>이 개념은 a가 배열의 이름인 경우 a[i]는 배열의 한 요소를 나타내고, a 자체는 배열에 대한 포인터 역할을 하는 것과 유사하다. </p>
<p>마찬가지로 f가 함수라면 f(x)는 함수의 호출로 생각하지만, f 그자체는 함수에 대한 포인터로 취급한다. </p>
<p>integrate에서 아래와 같이 f가 가리키는 함수를 호출한다. </p>
<pre><code class="language-c">y = (*f)(x);</code></pre>
<p>*f가 f가 가리키는 함수를 나타내기 때문에, integrate(sin, 0.0, PI/2) 가 실행되는 동안 *f의 호출은 사실상 sin의 홀출과 같다. </p>
<br>
<br>

<h2 id="📌-the-qsort-function-qsort-함수">📌 The qsort Function (qsort 함수)</h2>
<p>C 라이브러리의 유용한 함수들의 대부분이 함수 포인터를 인수로 받는다. 그 중 하나가 &lt;stdlib.h&gt;의 qsort 함수이다. </p>
<p>qsort는 우리가 선택한 기준에 따라 모든 배열을 정렬할 수 있는 기능이다. </p>
<p>정렬되는 배열의 요소들은 어떤 타입이든 될 수 있기 때문에, 구조체나 공유체 타입의 qsort는 두 배열 요소 중 어떤 것이 &quot;더 작은&quot;지 결정하는 방법을 가르쳐야 한다. 비교 함수(comparison function)을 작성하여 qsort에게 정보를 제공할 것이다. </p>
<pre><code class="language-c">void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));</code></pre>
<p>base : 어레이의 첫 번째 원소를 가리킨다. 
nmemb : 정렬되어야 할 원소의 갯수
size : 각 원소의 사이즈 (바이트 단위)
compar : 비교할 함수를 가리키는 포인터</p>
<p>🔎 qsort 사용 예시</p>
<pre><code class="language-c">qsort(inventory, num_parts, sizeof(structpart), compare_parts);</code></pre>
<p>🔎 compare_parts 함수 작성하기</p>
<p>qsort에서는 매개변수로 void* 유형을 받지만, void* 포인터를 통해 part 구조체의 멤버에 접근할 수가 없다. 이 멤버들은 struct part* 유형의 포인터가 필요하다. </p>
<p>이러한 문제 해결을 위해 compare_parts의 매개변수 p, q를 struct part*의 변수에 할당하여 원하는 유형으로 변환하였다.</p>
<pre><code class="language-c">int compare_parts(const void* p, const void* q)
{
    const struct part* p1 = p;
    const struct part* q1 = q;

    if (p1-&gt;number &lt; q1-&gt;number)
        return -1;
    else if (p1-&gt;number == q1-&gt;number)
        return 0;
    else
        return 1; 
}</code></pre>
<p>p와 q는 const 포인터기 때문에 const로 선언된 변수에만 할당될 수 있다. </p>
<br>
<br>

<h2 id="📌-other-uses-of-function-pointers-함수-포인터의-여러-응용">📌 Other Uses of Function Pointers (함수 포인터의 여러 응용)</h2>
<p>함수 포인터를 변수에 저장하거나 배열의 요소 또는 구조체나 공용체의 멤버로서 사용할 수 있다. 함수 포인터를 반환하는 함수를 작성할 수도 있다. </p>
<p>🔎 함수에 대한 포인터를 저장할 수 있는 변수</p>
<pre><code class="language-c">void (*pf)(int);</code></pre>
<p>pf는 int형의 파라미터를 받고, 반환 값이 void 형인 모든 함수를 가리킬 수 있다. </p>
<p>pf가 가리키는 함수를 호출하는 방법은 다음과 같다.</p>
<pre><code class="language-c">(*pf)(i);</code></pre>
<p>또는</p>
<pre><code class="language-c">pf(i);</code></pre>
<p>🔎 요소가 함수 포인터인 배열</p>
<pre><code class="language-c">void (*file_cmd[])(void) = { new_cmd, open_cmd, close_cmd, close_all_cmd };</code></pre>
<p>배열 첨자를 이용하여 함수를 호출할 수 있다.</p>
<pre><code class="language-c">(*file_cmd[n]))(); /* or file_cmd[n](); */</code></pre>
<br>
<br>

<h2 id="📌-program-tabulating-the-trigonometric-functions-삼각함수-표로-보여주기">📌 [Program] Tabulating the Trigonometric Functions (삼각함수 표로 보여주기)</h2>
<p>🔎 cos, sin 및 tan 함수의 값을 보여주는 표를 출력</p>
<pre><code class="language-c">/* Tabulates values of trigonometric functions */

#include &lt;math.h&gt;
#include &lt;stdio.h&gt;

void tabulate(double (*f)(double), double first, double last, double incr);

int main(void)
{
    double final, increment, initial;

    printf(&quot;Enter initial value : &quot;);
    scanf(&quot;%lf&quot;, &amp;initial);

    printf(&quot;Enter final value : &quot;);
    scanf(&quot;%lf&quot;, &amp;final);

    printf(&quot;Enter increment : &quot;);
    scanf(&quot;%lf&quot;, &amp;increment);

    printf(&quot;\n      x           cos(x)&quot;
              &quot;\n   -------    -------\n&quot;);
    tabulate(cos, initial, final, increment);

    printf(&quot;\n      x           sin(x)&quot;
        &quot;\n   -------    -------\n&quot;);
    tabulate(sin, initial, final, increment);

    printf(&quot;\n      x           tan(x)&quot;
        &quot;\n   -------    -------\n&quot;);
    tabulate(tan, initial, final, increment);

    return 0; 
}

void tabulate(double (*f)(double), double first, double last, double incr)
{
    double x;
    int i, num_intervals;

    num_intervals = ceil((last - first) / incr);
    for (i = 0; i &lt;= num_intervals; i++) {
        x = first + i * incr;
        printf(&quot;%10.5f %10.5f\n&quot;, x, (*f)(x));
    }
}</code></pre>
<br>
<br>

<h2 id="📌-c99-restricted-pointers-제한-포인터">📌 [C99] Restricted Pointers (제한 포인터)</h2>
<p>🔎 restrict </p>
<pre><code class="language-c">int* restrict p;</code></pre>
<p>위처럼 restrict로 선언된 포인터를 제한 포인터(restricted pointer)라고 부른다. </p>
<p>이렇게 선언하면 p가 가리키는 개체가 담긴 메모리에는 다른 포인터가 접근할 수 없다. </p>
<p>객체에 접근하는 두 가지 이상의 방법을 aliasing이라고 한다.</p>
<pre><code class="language-c">int* restrict p;
int* restrict q;

p = malloc(sizeof(int));

q = p;
*q = 0; /* casus undefined behavior */</code></pre>
<p>p가 제한 포인터이기 때문에, *q = 0 이 정의되지 않는다.  </p>
<p>제한된 포인터 p가 외부 저장소 클래스 없이 로컬 변수로 선언되면, restrict는 p로 선언된 블록이 실행될 때만 p에 적용된다. </p>
<p>restrict 사용법을 더 알아보기 위해 &lt;string.h&gt;에 있는 memcpy와 memmove를 알아보자. </p>
<p>🔎 memcpy</p>
<pre><code class="language-c">void *memcpy(void* restrict s1, const void* restrict s2, size_t n); </code></pre>
<p>memcpy는 한 개체에서 다른 개체로 바이트를 복사한다. </p>
<ul>
<li>s2 : 복사할 데이터</li>
<li>s1 : 복사본의 대상</li>
<li>n : 복사할 바이트 수
s1, s2에 restrict를 사용하는 것은 복사할 것과 복사본의 대상이 겹치지 않아야 한다는 것을 나타낸다. </li>
</ul>
<p>반대로 memmove에서는 restrict를 사용하지 않는다. </p>
<p>🔎 memmove</p>
<pre><code class="language-c">void *memmove(void *s1, const void *s2, size_t n);</code></pre>
<p>차이점은 소스와 타깃이 겹치더라도 memmove는 작동이 된다는 것이다. </p>
<p>예를 들어, memmove를 이용하여 어레이의 원소들을 한칸씩 옮길 수 있다. </p>
<pre><code class="language-c">memmove(&amp;a[0], &amp;a[1], 99 * sizeof(int));</code></pre>
<p>restrict는 컴파일러에 정보를 제공하여 보다 효율적인 코드를 생성함으로써 최적화(optimization)을 한다. </p>
<p>그러나 모든 컴파일러가 최적화를 시도하는 것은 아니기 때문에 대부분의 프로그래머가 제한을 사용하지 않는다. </p>
<br>
<br>

<h2 id="📌-c99-flexible-array-members-유동적-배열-멤버">📌 [C99] Flexible Array Members (유동적 배열 멤버)</h2>
<p>문자열 저장을 좀 더 유동적이게 할 수 있는 방법을 알아보자</p>
<pre><code class="language-c">struct vstring {
    int len;
    char chars[N];
};</code></pre>
<p>이런 고정 메모리 배열은 문자열의 길이를 제한하고 메모리를 낭비한다. </p>
<p>C 프로그래머들은 전통적으로 문자 길이를 1로 선언하고 각 문자열을 동적으로 할당함으로써 이 문제를 해결해왔다.</p>
<pre><code class="language-c">struct vstring {
    int len;
    char chars[1];
};
...
struct vstring* str = malloc(sizeof(struct vstring) + n - 1);
str-&gt;len = n;</code></pre>
<p>이런 바법은 &quot;struct hack&quot;이란 이름으로 알려져 있다. </p>
<p>struct hack의 유용성 때문에 C99에서는 유동적 배열 멤버(flexible array member) 기능이 추가되었다. </p>
<p>🔎 flexible array member</p>
<pre><code class="language-c">struct vstring {
    int len;
    char chars[]; /* flexible array member - C99 only */
};
...
struct vstring* str = malloc(sizeof(struct vstring) + n);
str-&gt;len = n;</code></pre>
<p>유동적 배열 멤버를 담고 있는 구조체는 완전하지 않은 유형(incomplete type)이다. 이러한 타입은 다른 구조체의 멤버가 되거나 어레이의 원소가 될 수 없다. 하지만 어레이가 이 구조체를 가리키는 포인터를 포함할 수는 있다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Windows - 창 (1)]]></title>
            <link>https://velog.io/@mi_pine/Windows-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%B0%BD</link>
            <guid>https://velog.io/@mi_pine/Windows-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%B0%BD</guid>
            <pubDate>Tue, 01 Feb 2022 13:02:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/ko-kr/windows/win32/learnwin32/your-first-windows-program">https://docs.microsoft.com/ko-kr/windows/win32/learnwin32/your-first-windows-program</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<br>

<hr>
<br> 

<p>모듈1. 첫 번째 Windows 프로그램 </p>
<p>1) 창 만들기
2) 창 메세지
3) 창 프로시저 작성</p>
<br>

<hr>
<br> 
아래 코드들로 창을 만들기 위해 Visual Studio를 이용하였다. Visual Studio에서 Windows 데스크톱 어플리케이션으로 프로젝트를 생성하면 된다. 

<p><img src="https://images.velog.io/images/mi_pine/post/abc66603-1c5d-4f90-a5b4-e4a4aec1471a/image.png" alt=""></p>
<h1 id="📍-creating-a-window">📍 Creating a Window</h1>
<h2 id="📌-window-classes">📌 Window Classes</h2>
<p>창 클래스(Window Class)는 여러 창에서 공통적으로 사용할 수 있는 동작의 집합을 말한다. 예를 들어 버튼 그룹에서 각 버튼은 버튼을 클릭할 때 유사한 동작을 포함한다. 물론 버튼이 완전히 동일하지는 않고, 각 창에 대해 고유한 데이터를 인스턴스 데이터(instance data)라고 한다. </p>
<p>창 클래스는 C++의 &quot;class&quot;가 아님을 이해하는 것이 중요하다. 
창 클래스는 운영 체제에서 내부적으로 사용하는 데이터 구조로 런타임에 시스템에 등록된다. 새 창 클래스로 등록 하려면 먼저 구조체를 입력하면된다. </p>
<pre><code class="language-cpp">const wchar_t CLASS_NAME[] = L&quot;Sample Window Class&quot;;

WNDCLASS wc = { };

wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;</code></pre>
<p>🔎 구조체 멤버 설정</p>
<ul>
<li><p>lpfnWndProc : 윈도우 프로시저(Window procedure)라는 응용 프로그램 정의 함수에 대한 포인터이다. 창의 동작 대부분을 정의한다. </p>
</li>
<li><p>Hinstance : 응용 프로그램 인스턴스에 대한 핸들로, Wwinmain의 hinstance 매개 변수에서 이 값을 가져온다.</p>
</li>
<li><p>lpszClassName : 창 클래스를 식별하는 문자열이다. </p>
</li>
</ul>
<p>그런 다음 구조체의 주소를 RegisterClass 함수에 전달한다. 이 함수는 windows 클래스를 운영 체제에 등록한다. </p>
<pre><code class="language-cpp">RegisterClass(&amp;wc);</code></pre>
<br>

<h2 id="📌-creating-the-window">📌 Creating the Window</h2>
<p>창의 새 인스턴스를 만들려면 CreateWindowEx 함수를 호출하면 된다. </p>
<pre><code class="language-cpp">HWND hwnd = CreateWindowEx(0, // Optional window styles
    CLASS_NAME,  // Window class 
    L&quot;Learn to Program Windows&quot;, // Window text
    WS_OVERLAPPEDWINDOW, // Window style

    // Size and Position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

    NULL, // Parent window
    NULL, // Menu
    hInstance, // Instance handle
    NULL // Additional application data
    );

if (hwnd = NULL)
{
    return 0; 
}</code></pre>
<ul>
<li>마지막 매개변수는 void* 형식의 임의의 데이터에 대한 포인터이다. 이 값을 사용하여 데이터 구조를 윈도우 프로시저에게 전달할 수 있다. </li>
</ul>
<p>CreateWindowEx는 새 창에 대한 핸들을 반환하거나, 실패할 경우 0을 반환한다. 창을 표시하려면 창 핸들을 ShowWindow 함수에 전달하면 된다. </p>
<pre><code class="language-cpp">ShowWindow(hwnd, nCmdShow);</code></pre>
<p>hwnd는 CreateWindowEx에서 반환되는 창 핸들이며 nCmdShow는 창을 최소화하거나 최대화 하는데 사용할 수 있다. 운영체제에서는 Wwinmain 함수를 통해 이 값을 프로그램에 전달한다. </p>
<p>🔎 창 만들기(전체 코드)</p>
<pre><code class="language-cpp">// Register the window class.
const wchar_t CLASS_NAME[] = L&quot;Sample Window Class&quot;;

WNDCLASS wc = { };

wc.lpfnWndPRoc = windowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;

RegisterClass(&amp;wc);

// Create the window 

HWND hwnd = CreateWindowEx(0, // Optional window styles
    CLASS_NAME,  // Window class 
    L&quot;Learn to Program Windows&quot;, // Window text
    WS_OVERLAPPEDWINDOW, // Window style

    // Size and Position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

    NULL, // Parent window
    NULL, // Menu
    hInstance, // Instance handle
    NULL // Additional application data
    );

if (hwnd = NULL)
{
    return 0; 
}</code></pre>
<p>위 코드를 실행하면 아래와 같이
Learn to Program Windows 라는 이름의 창이 만들어진다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/e6912892-b2c3-46aa-a52b-c7745a52486d/image.png" alt=""></p>
<br>

<hr>
<br>

<h1 id="📍-window-messages-get-started-with-win32-and-c">📍 Window Messages (Get Started with Win32 and C++)</h1>
<p>GUI 어플리케이션은 사용자 및 운영체제의 이벤트에 응답해야 한다. </p>
<ul>
<li>사용자의 이벤트 : 마우스 클릭, 키 스트로크, 터치 스크린 제스처 등</li>
<li>운영 체제 이벤트 : 프로그램 동작에 영향을 줄 수 있는 것들. ex. 사용자가 새 하드웨어 디바이스를 연결하는 등</li>
</ul>
<p>이러한 이벤트들은 미리 예측할 수 없기에 Windows 메세지 전달 모델(message-passing model)을 이용한다. 운영 체제는 메세지를 전달하여 어플리케이션 창과 통신한다. </p>
<p>예를 들어 사용자가 마우스 왼쪽 버튼을 누르면 창에 다음과 같은 메세지가 간다. </p>
<pre><code class="language-cpp">#define WM_LBUTTONDOWN    0x0201</code></pre>
<p>창에 메세지를 전달하기 위해 운영체제는 해당 창에 등록된 윈도우 프로시저를 호출한다. </p>
<br> 

<h2 id="📌-the-message-loop">📌 The Message Loop</h2>
<p>어플리케이션에는 수많은 메세지가 오고가기 때문에 메세지를 검색하고 올바른 창으로 전달하는 루프가 필요하다. </p>
<p>운영체제는 창 만드는 각 스레드에 대해 창 메세지에 대한 큐를 만드는데, 이 큐는 해당 스레드에서 만든 모든 창에 대한 메세지를 포함한다. </p>
<p>큐를 직접 조작할 수는 없으나 GetMessage 함수를 호출하여 메세지를 끌어올 수 있다. </p>
<pre><code class="language-cpp">MSG msg;
GetMessage(&amp;msg, NULL, 0, 0);</code></pre>
<p>이 함수는 큐의 헤드에서 첫번째 메세지를 제거한다. 큐가 비어 있으면 다른 메세지가 큐에 대기될 때까지 함수가 차단된다. GetMessage가 다른 메세지를 기다리는 동안 계속 실행되는 추가 스레드를 만들 수 있다. </p>
<p>GetMessage의 첫번째 매개변수는 MSG 구조체의 주소로 함수가 성공하면 메세지에 대한 정보로 MSG 구조체를 지운다. </p>
<p>MSG를 직접 검사하지는 않고, 다른 두 함수에 전달한다. </p>
<pre><code class="language-cpp">TranslateMessage(&amp;msg);
DispatchMessage(&amp;msg);</code></pre>
<ul>
<li><p>TranlateMessgae : 키보드 입력을 문자로 변환, DispatchMessage전에 호출해야 한다. </p>
</li>
<li><p>DispatchMessage : 메세지의 대상인 윈도우 프로시저를 호출하도록 운영체제에게 지시한다. 즉 운영체제는 창 테이블에서 창 핸들을 찾고 창과 연결된 함수 포인터를 찾아 함수를 호출한다. </p>
</li>
</ul>
<p>예를 들어 사용자가 마우스 왼쪽 버튼을 누를 경우 아래의 순서로 작동한다. </p>
<p>1) 운영체제는 WM_LBUTTONDOWN 메세지를 메세지 큐에 넣는다. 
2) 프로그램에서 GetMessage 함수를 호출한다.
3) GetMessage는 큐에서 WM_LBUTTONDOWN 메세지를 끌어와 MSG 구조를 채운다. 
4) 프로그램에서 TranslateMessage 및 DispatchMessage 함수를 호출한다. 
5) DispatchMessage 내에서 운영체제는 윈도우 프로시저를 호출한다. 
6) 윈도우 프로시저는 메세지에 응답하거나 무시할 수 있다.  </p>
<p>프로그램이 실행되는 동안 메세지는 계속 큐에 도착하므로 지속적으로 메세지를 받아서 Dispatch 하는 루프가 있어야 한다. 
따라서 다음과 같은 루프가 필요하다.</p>
<p>🔎 지속적으로 메세지를 받아서 Dispatch 하는 루프</p>
<pre><code class="language-cpp">// Correct.

MSG msg = { };
while (GetMessage(&amp;msg, NULL, 0, 0) &gt; 0)
{
    TranslateMessage(&amp;msg);
    DispatchMessage(&amp;msg);
}</code></pre>
<p>어플리케이션을 종료하고 메세지 루프를 중단하려면 PostQuitMessage 함수를 호출한다. </p>
<p>🔎 PostQuitMessage 함수</p>
<pre><code class="language-cpp">    PostQuitMessage(0);</code></pre>
<p>여기서 주의깊게 볼 점은 윈도우 프리시저가 WM_QUIT 메세지를 받지 않아 이 메세지에 대한 case 문이 필요 없다는 것이다. </p>
<br> 

<h2 id="📌-posted-messages-versus-sent-messages">📌 Posted Messages versus Sent Messages</h2>
<ul>
<li>메세지 게시(Post) : 메세지가 메세지 큐로 이동하고 루프를 통해 Dispatch</li>
<li>메세지 전송(Send) : 큐를 건너뛰고 운영 체제에서 윈도우 프로시저를 직접 호출 </li>
</ul>
<br>

<hr>
<br> 

<h1 id="📍-writing-the-window-procedure">📍 Writing the Window Procedure</h1>
<p>🔎 윈도우 프로시저</p>
<pre><code class="language-cpp">LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);</code></pre>
<ul>
<li><p>hwnd : 창에 대한 핸들</p>
</li>
<li><p>Umsg : 메세지 코드 (ex. WM_SIZE : 창의 크기가 조절되었음)</p>
</li>
<li><p>wParam &amp; lParam : 추가 데이터</p>
</li>
<li><p>LRESULT : 프로그램에서 Windows 에게 반환하는 정수 값. 이 값의 의미는 메세지 코드에 따라 달라진다. </p>
</li>
<li><p>CALLBACK : 함수에 대한 호출 규칙 </p>
</li>
</ul>
<p>🔎 일반적인 코드</p>
<pre><code class="language-cpp">switch (uMsg)
{
    case WM_SIZE: // Handle window resizing

    // etc
}</code></pre>
<p>일반적인 윈도우 프로시저는 수십 개의 메세지를 처리하므로 시간이 길어질 수 있어, 코드를 모듈식으로 만들고자 개별 함수에서 각 메세지를 처리하도록 한다. 
이 때 윈도우 프로시저에서 wParam, lParam을 캐스팅하고 함수에 전달한다. </p>
<p>🔎 WM_SIZE 메세지</p>
<pre><code class="language-cpp">LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE:
    {
        int width = LOWORD(lParam); // Macro to get the low-order word
        int height = HIWORD(lParam);  // Macro to get the high-order word

        // Respond to the message :
        OnSize(hwnd, (UINT)wParam, width, height);
    }
    break;
    }
}

void OnSize(HWND hwnd, UINT flag, int width, int height)
{
    // Handle resizing 
}</code></pre>
<br> 

<h2 id="📍-default-message-handling">📍 Default Message Handling</h2>
<p>윈도우 프로시저에서 특정 메세지를 처리하지 않는 경우에는 메세지 매개 변수를 DefWindowProc 함수에 직접 전달한다. </p>
<p>🔎 매개 변수를 DefWindowProc 함수에 전달 </p>
<pre><code class="language-cpp">return DefWindowProc(hwnd, uMsg, wParam, lParam);</code></pre>
<br> 

<h2 id="📍-avoiding-bottlenecks-in-your-window-procedure">📍 Avoiding Bottlenecks in Your Window Procedure</h2>
<p>윈도우 프로시저는 실행되는 동안 동일한 스레드(thread)에 생성된 windows에 대한 다른 메세지를 차단한다. 이런 점으로 문제가 발생했을 때는 Windows에 기본 제공되는 멀티태스킹 기능 중 하나를 사용하여 작업을 다른 스레드로 이동해야 한다. </p>
<ul>
<li>새 스레드를 만든다.</li>
<li>스레드 풀(thread pool)을 사용한다. </li>
<li>비동기 I/O 호출을 사용한다.</li>
<li>비동기 프로시저 호출을 사용한다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Windows 프로그래밍 ]]></title>
            <link>https://velog.io/@mi_pine/Windows-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@mi_pine/Windows-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Mon, 31 Jan 2022 12:29:31 GMT</pubDate>
            <description><![CDATA[<p><a href="https://docs.microsoft.com/en-us/windows/win32/learnwin32/learn-to-program-for-windows">https://docs.microsoft.com/en-us/windows/win32/learnwin32/learn-to-program-for-windows</a></p>
<p>위 내용을 참고하여 정리한 내용입니다. </p>
<br>

<hr>
<h1 id="📍-windows-coding-conventions">📍 Windows Coding Conventions</h1>
<h2 id="📌-typedefs">📌 Typedefs</h2>
<h3 id="integer-types">Integer types</h3>
<table>
<thead>
<tr>
<th align="center"><strong>데이터 형식</strong></th>
<th align="center"><strong>크기</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">BYTE</td>
<td align="center">8비트</td>
</tr>
<tr>
<td align="center">DWORD</td>
<td align="center">32비트</td>
</tr>
<tr>
<td align="center">INT32</td>
<td align="center">32비트</td>
</tr>
<tr>
<td align="center">INT64</td>
<td align="center">64비트</td>
</tr>
<tr>
<td align="center">LONG</td>
<td align="center">32비트</td>
</tr>
<tr>
<td align="center">LONGLONG</td>
<td align="center">64비트</td>
</tr>
<tr>
<td align="center">UINT32</td>
<td align="center">32비트</td>
</tr>
<tr>
<td align="center">UINT64</td>
<td align="center">64비트</td>
</tr>
<tr>
<td align="center">ULONG</td>
<td align="center">32비트</td>
</tr>
<tr>
<td align="center">ULONGLONG</td>
<td align="center">64비트</td>
</tr>
<tr>
<td align="center">WORD</td>
<td align="center">16비트</td>
</tr>
</tbody></table>
<br>

<h3 id="boolean-type">Boolean Type</h3>
<p>WinDef.h에는 BOOL과 함께 사용되는 두가지 값이 정의되어 있다. </p>
<pre><code class="language-cpp">#define FALSE 0
#define TRUE 1</code></pre>
<p>그러나 BOOL 형식을 반환하는 대부분의 함수는 참일 경우 0은 아니지만 1도 아닌 값이 반환될 수 있다. </p>
<p>따라서 아래처럼 작성하면 안된다.</p>
<pre><code class="language-cpp">if (result == TRUE) // wrong !!
{
  ...
}</code></pre>
<p>아래는 맞는 표현들이다. </p>
<pre><code class="language-cpp">if (SomeFunctionThatReturnsBoolean())
{
  ...
}

// or

if (SomeFunctionThatReturnsBoolean() != FALSE)
{
  ...
}</code></pre>
<br> 

<h3 id="pointer-types">Pointer Types</h3>
<p>Windows에서 포인터는 일반적으로 이름 앞에 P나 LP를 붙여서 표현한다. 예를 들어 LPRECT는 RECT에 대한 포인터이다. </p>
<p>RECT가 사각형을 설명하는 구조체라고 할 때 RECT의 포인터는 다음과 같다. </p>
<pre><code class="language-cpp">RECT* rect; // Pointer to a RECT structure
LPRECT rect; // The same 
PRECT rect; // Also the same</code></pre>
<p>P는 포인터이고, LP는 long 포인터를 의미한다. LP는 16비트 코드를 32비트 Windows에 쓸 때 유용하게 사용되었으나 현재는 P와 LP는 구분되지 않으며 동일하게 쓰인다. 포인터를 나타낼 때 보통은 P를 쓴다.</p>
<br> 

<h3 id="pointer-precision-types">Pointer Precision Types</h3>
<ul>
<li>DWORD_PTR</li>
<li>INT_PTR</li>
<li>LONG_PTR</li>
<li>ULONG_PTR</li>
<li>UINT_PTR</li>
</ul>
<p>위 포인터 정밀도 타입들은 32비트, 64비트 등의 환경에 맞게 정의되어 있다. 즉 32비트에서는 32비트 포인터, 64비트에서는 64비트 포인터가 된다.</p>
<p>따라서 이러한 형식은 정수를 포인터로 캐스팅하는 상황에서 사용된다. 포인터 산술이나, 루프로 전체 바이트 범위를 반복할 때 등에서도 사용된다. 또 32비트에서 64비트로 확장하는 경우도 가능하다. 이 경우에는 포인터의 크기는 여전히 4byte이다. </p>
<br>

<hr>
<br>


<h1 id="📍-working-with-strings">📍 Working with Strings</h1>
<p>Windows는 기본적으로 UTF-16 인코딩을 사용하여 유니코드 문자를 나타내어 각 문자는 16비트로 인코딩된다. 
UTF-8 문자를 8비트 ANSI 문자와 구별하기 위해 wide 문자라고 부른다. </p>
<ul>
<li>ASCII : 1960년대 미국에서 정의한 표준화한 부호체계</li>
<li>ANSI : 8비트로 확장한 ASCII 코드</li>
<li>UNICODE : ANSI로는 한국, 중국, 일본과 같은 문자 표현에 한계가 있어 용량을 크게 확장한 2바이트 기반의 코드 </li>
<li>UTF-8 : 유니코드를 8비트 숫자의 집합으로 나타낸 것</li>
</ul>
<p>Visual C++ 컴파일러는 wide 문자에 대해 wchar_t를 지원하며 WinNT(헤더파일)에는 아래 typedef가 정의되어 있다. </p>
<pre><code class="language-cpp">typedef wchar_t WCHAR;</code></pre>
<p>wide 문자, 문자열을 작성하려면 앞에 L을 붙이면 된다. </p>
<pre><code class="language-cpp">wchar_t a = L&#39;a&#39;;
wchar_t *str = L&quot;hello&quot;;</code></pre>
<p>문자열 관련 typedef는 다음과 같다 </p>
<table>
<thead>
<tr>
<th align="center"><strong>Typedef</strong></th>
<th align="center"><strong>Definition</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">CHAR</td>
<td align="center">char</td>
</tr>
<tr>
<td align="center">PSTR or LPSTR</td>
<td align="center">char*</td>
</tr>
<tr>
<td align="center">PCSTR or LPCSTR</td>
<td align="center">const char*</td>
</tr>
<tr>
<td align="center">PWSTR or LPWSTR</td>
<td align="center">wchar_t*</td>
</tr>
<tr>
<td align="center">PCWSTR or LPCWSTR</td>
<td align="center">const wchar_t*</td>
</tr>
</tbody></table>
<br> 

<h2 id="📌-unicode-and-ansi-functions">📌 Unicode and ANSI Functions</h2>
<p>Windows에 대한 유니코드 지원을 도입하는 경우 두 개의 병렬 API를 이용한다. (ANSI 문자열과 유니코드 문자열 용) </p>
<p>🔎 창 제목 표시줄의 텍스트를 설정하는 두 가지 함수</p>
<ul>
<li>Setwindowtexta : ANSI 문자열 사용</li>
<li>Setwindowtextw : 유니코드 문자열 사용</li>
</ul>
<p>내부적으로 ANSI 버전은 문자열을 유니코드로 전환한다. </p>
<p>또한 Windows 헤더는 아래와 같은 매크로를 지원한다. </p>
<pre><code class="language-cpp">#ifdef UNICODE
#define SetWindowText SetWindowTextW
#else 
#define SetWindowText SetWindowTextA
#endif</code></pre>
<p>ANSI 문자열은 호환되지 않는 언어가 많기 때문에 유니코드 버전을 쓰는 것이 좋다. Windows의 최신 API에는 ANSI 버전이 없고 유니코드 버전만 있다. </p>
<br> 

<h2 id="📌-tchars">📌 TCHARs</h2>
<p>응용 프로그램이 여러 윈도우 버전을 모두 지원해야 하는 경우 대상 플랫폼에 따라 ANSI 또는 유니코드 문자열에 대해 동일한 코드를 컴파일 하는 것이 유용하다. 따라서 Windows SDK는 플랫폼에 따라 문자열을 유니코드나 ANSI로 매핑하는 매크로를 재공한다. </p>
<table>
<thead>
<tr>
<th align="center"><strong>Macro</strong></th>
<th align="center"><strong>유니코드(Unicode)</strong></th>
<th align="center"><strong>ANSI</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">TCHAR</td>
<td align="center">wchar_t</td>
<td align="center">char</td>
</tr>
<tr>
<td align="center">TEXT(&quot;x&quot;)</td>
<td align="center">L&quot;x&quot;</td>
<td align="center">&quot;x&quot;</td>
</tr>
</tbody></table>
<p>예를 들어 아래 코드는</p>
<pre><code class="language-cpp">SetWindowText(TEXT(&quot;My Application&quot;));</code></pre>
<p>둘 중 하나가 된다. </p>
<pre><code class="language-cpp">SetWindowTextW(L&quot;My Application&quot;); // 유니코드
SetWindowTextA(&quot;My Application&quot;); // ANSI</code></pre>
<p>몇몇 헤더는 전처리기 기호 UNICODE를 이용하고 다른 것들은 _UNICODE 를 이용하기 때문에 둘다 define 해주어야 한다. </p>
<p>Visual C++ 에서는 default로 설정된다. </p>
<br>

<hr>
<br>


<h1 id="📍-what-is-a-window">📍 What Is a Window?</h1>
<p><img src="https://images.velog.io/images/mi_pine/post/44942cf0-bcb9-45e9-8f19-3aadb2d23184/image.png" alt=""></p>
<p>창을 생각하면 보통 위와 같은 창을 떠올린다. 이러한 창을 application window 또는 main window 라고 한다. 일반적으로 제목 표시줄, 최소화, 최대화 버튼 및 기타 표준 UI 요소가 있는 프레임이 있다.</p>
<p>프레임은 운영 체제가 창의 해당 영역 부분을 관리하기 때문에 비 클라이언트 영역 (non-client area)라고 부른다. </p>
<p>프레임 내의 영역은 클라이언트 영역(client area)이다.</p>
<p>다른 유형의 창은 다음과 같다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/d1e88f19-834c-4f70-9f92-15b4b5eb1d5f/image.png" alt=""></p>
<p>이러한 버튼과 같은 UI control도 창이다. control과 어플리케이션 창은 서로 통신할 수 있다. </p>
<p>창을 다음과 같이 생각하는 것이 좋다</p>
<ul>
<li>화면의 특정 부분을 차지한다.</li>
<li>특정 시점에 표시될 수도, 표시되지 않을 수도 있다.</li>
<li>자신을 그리는 방법을 알고 있다.</li>
<li>사용자 또는 운영 체제의 이벤트에 응답한다. </li>
</ul>
<br>


<h2 id="📌-parent-windows-and-owner-windows">📌 Parent Windows and Owner Windows</h2>
<p>control 창은 어플리케이션 창의 자식이다. 
또 modal 대화 상자가 표시될 때, 어플리케이션 창은 소유자(owner)창이고 대화 상자는 소유된(owned)창이다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/a8ba4d9f-f940-4635-9748-bbcf71e9d979/image.png" alt=""></p>
<br>


<h2 id="📌-window-handles">📌 Window Handles</h2>
<p>window는 개체이며 코드와 데이터가 모두 있지만 C++ 클래스는 아니다. 대신 프로그램은 핸들(handle) 이라는 값을 사용하여 창을 참조한다. 핸들은 기본적으로 개체를 식별하는데 사용되는 숫자일 뿐이다. 
Windows가 만든 모든 창이 담긴 큰 테이블이 있다고 간주하고, 이 테이블을 사용하여 핸들로 창을 조회한다.  창 핸들의 데이터 형식은 일반적으로 HWND이다. (aitch-wind로 발음) 창 핸들은 창을 만드는 함수 CreateWindow 및 CreateWindowEx에서 반환된다. </p>
<p>창에서 작업을 수행하려면 일반적으로 HWND 값을 매개 변수로 받는 일부 함수를 호출한다. 예를 들어 화면의 창 위치를 변경하려면 MoveWindow 함수를 호출한다. </p>
<pre><code class="language-cpp">BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);</code></pre>
<p>여기서 첫번째 매개변수 HWND hWnd가 이동하려는 창에 대한 핸들이다. </p>
<p>핸들은 포인터가 아니다. </p>
<br>


<h2 id="📌-screen-and-window-coordinates">📌 Screen and Window Coordinates</h2>
<p>좌표는 디바이스에 독립적인 픽셀(device-independent pixels)로 측정된다. </p>
<p>작업에 따라 화면을 기준으로, 창(프레임 포함)을 기준으로 또는 창의 클라이언트 영역을 기준으로 좌표를 측정할 수 있다.</p>
<p>각 경우에 원점(0,0)은 항상 영역의 왼쪽 위 모퉁이이다.</p>
<p><img src="https://images.velog.io/images/mi_pine/post/6288d5ac-e4b3-4c7b-b26d-c55ac8e3b558/image.png" alt=""></p>
<br>

<hr>
<br>


<h1 id="📍-winmain-the-application-entry-point">📍 WinMain: The Application Entry Point</h1>
<p>모든 Windows 프로그램에는 WinMain 또는 wWinMain 이라는 진입점 함수가 포함되어 있다. </p>
<pre><code class="language-cpp">int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);</code></pre>
<ul>
<li>hinstance : &quot;인스턴스에 대한 핸들&quot; 또는 &quot;모듈에 대한 핸들&quot;이라고 한다. 운영 체제는 이 값을 사용하여 메모리에 로드될 때 실행 파일(exe)를 식별한다. 인스턴스 핸들은 특정 Windows 함수(ex.아이콘 또는 비트맵 로드)에 필요하다. </li>
<li>hPrevInstance : 16비트 Windows에서 사용되었으나 지금은 의미가 없다. </li>
<li>pCmdLine : 명령줄 인수를 유니코드 문자열로 포함</li>
<li>nCmdShow : 기본 어플리케이션 창이 최소화, 최대화 또는 정상적으로 표시되는지 여부를 표시하는 플래그 </li>
</ul>
<p>함수는 int 값을 반환하며 이 반환 값이 운영체제에서 사용되지는 않지만 반환 값을 사용하여 작성하는 다른 프로그램에 전달할 수 있다. </p>
<ul>
<li>WINAPI : 호출 규칙. 함수가 호출자로부터 매개 변수를 받는 방법을 정의. 예를 들어 스택에 매개 변수가 표시되는 순서를 정의한다. </li>
</ul>
<p>WinMain 함수는 명령줄 인수가 ANSI 문자열로 전달된다는 점을 제외하고 wWinMain과 동일하다. </p>
<p>🔎 빈 WinMain 함수</p>
<pre><code class="language-cpp">INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR lpCmdLine, INT nCmdShow)
{
    return 0;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📗16장 Structures, Unions, and Enumerations | Exercises and Programming Projects]]></title>
            <link>https://velog.io/@mi_pine/16%EC%9E%A5-Structures-Unions-and-Enumerations-Exercises-and-Programming-Projects</link>
            <guid>https://velog.io/@mi_pine/16%EC%9E%A5-Structures-Unions-and-Enumerations-Exercises-and-Programming-Projects</guid>
            <pubDate>Sun, 30 Jan 2022 14:09:24 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING 의 Exercises와 Programming Projects를 푼 내용입니다. </p>
<h3 id="📍-exercises">📍 Exercises</h3>
<br>

<ol>
<li>가능하다. 구두점(.)을 이용하여 사용할 수 있다. </li>
</ol>
<br>

<ol start="2">
<li><p>(a)</p>
<pre><code class="language-c">struct {
 double real;
 double imaginary;
} c1, c2, c3; </code></pre>
<p>(b)</p>
<pre><code class="language-c">struct {
 double real;
 double imaginary;
} c1{ 0.0, 1.0 }, c2{ 1.0, 0.0 }, c3;</code></pre>
<p>(c) </p>
<pre><code class="language-c">c1 = c2</code></pre>
<p>(d)</p>
<pre><code class="language-c">c3.real = c1.real + c2.real;
c3.imaginary = c3.imaginary + c2.imaginary;</code></pre>
</li>
<li><p>(a) </p>
<pre><code class="language-c">struct complex {
 double real;
 double imaginary;
};</code></pre>
</li>
</ol>
<p>(b) </p>
<pre><code class="language-c">struct complex c1, c2, c3;</code></pre>
<p>(c) </p>
<pre><code class="language-c">struct complex make_complex(double real, double imaginary) {
    return (struct complex) { real, imaginary };
}</code></pre>
<p>(d) </p>
<pre><code class="language-c">struct complex add_complex(struct complex a, struct complex b) {
    return (struct complex) { a.real + b.real, a.imaginary + b.imaginary };
}</code></pre>
<ol start="4">
<li><pre><code class="language-c">typedef struct {
 double real;
 double imaginary;
} complex;
</code></pre>
</li>
</ol>
<p>complex c1, c2, c3;</p>
<p>complex make_complex(double real, double imaginary) {
    return (complex) { real, imaginary };
}</p>
<p>complex add_complex(complex a, complex b) {
    return (complex) { a.real + b.real, a.imaginary + b.imaginary };
}</p>
<pre><code>
5. 
(a)
~~~ c
struct date {
    int month;
    int day;
    int year;
};

int day_of_year(struct date d)
{
    int day = 0;
    int days_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    if (((d.year % 4 == 0 &amp;&amp; d.year % 100 != 0) || d.year % 400 == 0))
        day++;

    for (int i = 0; i &lt; d.month; i++)
        day += days_month[i];

    return day + d.day; 
}</code></pre><p>(b) </p>
<pre><code class="language-c">int compare_dates(struct date d1, struct date d2) {
    int day_of_d1 = day_of_year(d1);
    int day_of_d2 = day_of_year(d2);

    if (day_of_d1 &gt; day_of_d2)
        return -1;
    else if (day_of_d1 &lt; day_of_d2)
        return 1;
    else
        return 0;
}</code></pre>
<p>6.</p>
<pre><code class="language-c">struct time split_time(long total_seconds)
{
    struct time t; 

    t.hours = total_seconds / 3600;
    total_seconds %= 3600;

    t.minutes = total_seconds / 60;
    total_seconds %= 60;

    t.seconds = total_seconds;

    return t;
}</code></pre>
<p>7.
(a) (약분하기)</p>
<pre><code class="language-c">struct fraction reduce_fraction(struct fraction f) {

    int n = f.numerator;
    int d = f.denominator;

    int temp;
    while (n % d != 0) {
        temp = d;
        d = n % d;
        n = temp;
    }

    f.numerator /= d;
    f.denominator /= d;

    return f;
}</code></pre>
<p>(b) </p>
<pre><code class="language-c">struct fraction add_fraction(struct fraction f1, struct fraction f2) {
    struct fraction f; 

    f.denominator = f1.denominator * f2.denominator;
    f.numerator = f1.numerator * f2.denominator + f1.denominator * f2.numerator;

    f = reduce_fraction(f);

    return f;
}</code></pre>
<p>(c) </p>
<pre><code class="language-c">struct fraction sub_fraction(struct fraction f1, struct fraction f2) {
    struct fraction f;

    f.denominator = f1.denominator * f2.denominator;
    f.numerator = f1.numerator * f2.denominator - f1.denominator * f2.numerator;

    f = reduce_fraction(f);

    return f;
}</code></pre>
<p>(d) </p>
<pre><code class="language-c">struct fraction multi_fraction(struct fraction f1, struct fraction f2) {
    struct fraction f;

    f.denominator = f1.denominator * f2.denominator;
    f.numerator = f1.numerator * f2.numerator;

    f = reduce_fraction(f);

    return f;
}</code></pre>
<p>(e) </p>
<pre><code class="language-c">struct fraction multi_fraction(struct fraction f1, struct fraction f2) {
    struct fraction f;

    f.denominator = f1.denominator * (1/f2.denominator);
    f.numerator = f1.numerator * (1/f2.numerator);

    f = reduce_fraction(f);

    return f;
}</code></pre>
<br> 

<p>8.
(a)</p>
<pre><code class="language-c">struct color MAGENTA =  {255, 0, 255};</code></pre>
<p>(b) </p>
<pre><code class="language-c">struct color MAGENTA =  {.red = 255, .blue = 255};</code></pre>
<ol start="9">
<li><p>(a) </p>
<pre><code class="language-c">struct color make_color(int red, int green, int blue) {
 if (red &lt; 0)
     red = 0;
 else if (red &gt; 255)
     red = 255;
 if (blue &lt; 0)
     blue = 0;
 else if (blue &gt; 255)
     blue = 255;
 if (green &lt; 0)
     green = 0;
 else if (green &gt; 255)
     green = 255;

 return (struct color) { red, green, blue };
}</code></pre>
</li>
</ol>
<p>(b) </p>
<pre><code class="language-c">int getRed(struct color c) {
    return c.red;
}</code></pre>
<p>(c) </p>
<pre><code class="language-c">bool equal_color(struct color color1, struct color color2) {
    if (color1.red == color2.red &amp;&amp; color1.green == color2.green &amp;&amp; color1.blue == color2.blue)
        return true; 
}</code></pre>
<p>(d) </p>
<pre><code class="language-c">struct color brighter(struct color c) {
    if (c.red + c.green + c.blue == 0)
        return (struct color) { 3, 3, 3 };

    if (c.red &gt; 0 &amp;&amp; c.red &lt; 3)
        c.red = 3;
    if (c.green &gt; 0 &amp;&amp; c.green &lt; 3)
        c.green = 3;
    if (c.blue &gt; 0 &amp;&amp; c.blue &lt; 3)
        c.blue = 3;

    c.red /= 0.7;
    c.green /= 0.7;
    c.blue /= 0.7;

    if (c.red &gt; 255)
        c.red = 255;
    if (c.green &gt; 255)
        c.green = 255;
    if (c.blue &gt; 255)
        c.blue = 255;

    return c;
}</code></pre>
<p>(e) </p>
<pre><code class="language-c">struct color darker(struct color c) {
    c.red *= 0.7;
    c.green *= 0.7;
    c.blue *= 0.7;

    return c;
}</code></pre>
<ol start="10">
<li>(a) <pre><code class="language-c">int area = (r.lower_right.x - r.upper_left.x) * (r.lower_right.y - r.upper_left.y);</code></pre>
</li>
</ol>
<p>(b) </p>
<pre><code class="language-c">struct point rectangle_center(struct rectangle r) {
    struct point p;

    p.x = (r.lower_right.x - r.upper_left.x) / 2;
    p.y = (r.lower_right.y - r.upper_left.y) / 2;

    return p;
}</code></pre>
<p>(c) </p>
<pre><code class="language-c">struct rectangle move_rect(struct rectangle r, int x, int y) {
    r.upper_left.x += x;
    r.upper_left.y += y;
    r.lower_right.x += x;
    r.lower_right.y += y;

    return r;
}</code></pre>
<p>(d) </p>
<pre><code class="language-c">bool is_within_rect(struct rectangle r, struct point p) {
    if (p.x &gt; r.upper_left.x &amp;&amp; p.x &lt; r.lower_right.x &amp;&amp; p.y &gt; r.upper_left.y &amp;&amp; p.y &lt; r.lower_right.y)
        return true;
    else
        return false;
}</code></pre>
<ol start="11">
<li>double 8 byte + union 8 byte (for double) + char 4 byte 
총 20byte가 할당된다. </li>
</ol>
<br> 

<ol start="12">
<li><p>struct가 4+8+4로 16byte를 할당받으므로 union에는 16byte가 할당된다. </p>
</li>
<li><p>(a), (b), (d) 가 옳은 표현이다. </p>
</li>
</ol>
<p>(c) s.u.rectangle.height = 25;
(e) s.u.circle.radius = 5;
(f) s.u.circle.radius = 5;</p>
<br> 

<p>14.
(a)</p>
<pre><code class="language-c">double shape_area(struct shape s) {
    if (s.shape_kind == RECTANGLE)
        return s.u.rectangle.height * s.u.rectangle.width;
    else
        return 3.1415 * s.u.circle.radius * s.u.circle.radius;
}</code></pre>
<p>(b) </p>
<pre><code class="language-c">struct shape shape_move(struct shape s, int x, int y) {
    s.center.x += x;
    s.center.y += y;

    return s;
}</code></pre>
<p>(c)</p>
<pre><code class="language-c">struct shape shape_scale(struct shape s, double c) {
    if (s.shape_kind == RECTANGLE) {
        s.u.rectangle.height *= c;
        s.u.rectangle.width *= c;
    } 
    else
        s.u.circle.radius *= c;
    return s;
}</code></pre>
<br> 

<p>15.
(a) </p>
<pre><code class="language-c">enum days = { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };</code></pre>
<p>(b)</p>
<pre><code class="language-c">typedef enum { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } days;</code></pre>
<br> 

<p>16.
(a), (c), (e) 가 옳은 표현이다. </p>
<br> 

<p>17.
(b), (c)는 범위 밖의 정수를 가리키게 될 위험이 있다. </p>
<br>

<ol start="18">
<li>(a) <pre><code class="language-c">typedef enum {EMPTY, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING} Piece;
typedef enum {BLACK, WHITE} Color;</code></pre>
</li>
</ol>
<p>(b) </p>
<pre><code class="language-c">typedef struct { Piece; Color; } Square;</code></pre>
<p>(c) 
Square board[8][8];</p>
<br> 

<ol start="19">
<li><pre><code class="language-c">struct pinball_machine {
char[40] name;
int year;
enum { EM, SS } type;
int players;
};</code></pre>
</li>
</ol>
<br> 

<ol start="20">
<li><pre><code class="language-c">switch (direction) {
case NORTH: y--;
break;
case SOUTH: y++;
break;
case EAST:  x++;
break;
case WEST:  x--;
break;
default:    
break;
}</code></pre>
</li>
</ol>
<br> 

<ol start="21">
<li>(a) 0, 1, 2, 3
(b) 11, 12, 13
(c) 14, 15, 16, 24, 25
(d) 45, 46, 47, 37, 38, 39</li>
</ol>
<br> 

<ol start="22">
<li>(a) <pre><code class="language-c">enum chess_pieces {KING = 200, QUEEN = 9, ROOK = 5, BISHOP = 3, KNIGHT = 3, PAWN = 1};</code></pre>
</li>
</ol>
<p>(b) </p>
<pre><code class="language-c">const int piece_value[] = {
    [KING] = 200, 
    [QUEEN] = 9,
    [ROOK] = 5, 
    [BISHOP] = 3,
    [KNIGHT] = 3,
    [PAWN] = 1
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘16장 Structures, Unions, and Enumerations | Study]]></title>
            <link>https://velog.io/@mi_pine/16%EC%9E%A5-Structures-Unions-and-Enumerations-Study</link>
            <guid>https://velog.io/@mi_pine/16%EC%9E%A5-Structures-Unions-and-Enumerations-Study</guid>
            <pubDate>Sun, 30 Jan 2022 07:27:48 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING으로 C언어를 공부하면서 정리한 내용입니다.
번역본 <a href="https://wikidocs.net/book/2494">https://wikidocs.net/book/2494</a> 을 참고하였습니다.</p>
<hr>
<br>
16.1 Structure Variables<br>
16.2 Structure Types<br>
16.3 Nested Arrays and Structures<br>
16.4 Unions<br>
16.5 Enumerations<br> 
<br>

<hr>
<br>

<h1 id="📍-structures-unions-and-enumerations">📍 Structures, Unions, and Enumerations</h1>
<p>구조체 : 서로 다른 형을 가질 수도 있는 값(구성원)의 집합
공용체 : 구조체와 유사하나 각 구성원이 같은 저장 공간을 공유, 그렇기에 공용체는 한 번에 동시에 모든 구성원을 저장할 수 없고, 한 구성원만을 저장
열거형 : 프로그래머가 명명한 값으로 된 정수형 </p>
<br>

<h2 id="📍-structure-variables">📍 Structure Variables</h2>
<p>구조체의 원소(member)는 같은 형일 필요가 없다. 
구조체의 구성원들에겐 이름이 존재하여 특정 구성원을 선택하려면 위치가 아닌 이름을 특정해주어야 한다. </p>
<br>

<h3 id="📍-declaring-structure-variables">📍 Declaring Structure Variables</h3>
<p>🔎 부품 번호, 부품 이름, 부품 개수 저장하기</p>
<pre><code class="language-c">struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part1, part2;</code></pre>
<p>구조체의 구성원(member) : number, name, on_hand
형 : struct
변수 : part1, part2</p>
<p>NAME_LEN의 값이 25라고 하면, 변수 part1은 다음과 같이 메모리에 저장된다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/455aef93-2dbb-4d56-98b0-42f5299cf73a/image.png" alt=""></p>
<p>각 구조체는 새로운 스코프를 갖는다. C 용어로 말하면 각 구조체는 구성원들에 대해 서로 다른 이름 공간 (namespace)를 갖는다고 한다. </p>
<p>예를 들어 아래와 같이 선언할 수 있다. </p>
<pre><code class="language-c">struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part1, part2;

struct {
    char name[NAME_LEN + 1];
    int number;
    char sex;
} employee1, employee2;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-initializing-structure-variables">📍 Initializing Structure Variables</h3>
<p>🔎 구조체 선언 시에 초기화하기</p>
<pre><code class="language-c">struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part1 = { 528, &quot;Disk drive&quot;, 10 }, 
part2 = { 914, &quot;Printer cable&quot;, 5 };</code></pre>
<p>초기화할 구조체의 구성원 수보다 적은 수의 초기자가 주어질 경우 나머지 초깃값은 0이 된다. 특히 문자형 배열의 나머지 바이트들은 0이 되어 비어있는 문자열이 된다. </p>
<br>

<hr>
<br>

<h3 id="📍-c99-designated-initializers">📍 [C99] Designated Initializers</h3>
<p>🔎 지정 초기자</p>
<pre><code class="language-c">{.number = 528, .name = &quot;Disk drive&quot;, .on_hand = 10}</code></pre>
<p>지정자(designator) : 구두점(.) + 구성원 이름 </p>
<p>🔎 지정 초기자의 장점</p>
<p>1) 구조체의 구성원과 초기자 값의 대응을 볼 수 있어 코드가 읽기 쉽다.</p>
<p>2) 초기자의 값들이 구조체의 구성원과 같은 순서일 필요가 없다. 
즉, 순서가 중요하지 않다. </p>
<p>지정 초기자에 있는 모든 값이 반드시 지정자를 적어주어야 하는 것은 아니다. </p>
<pre><code class="language-c">{.number = 528, &quot;Disk drive&quot;, .on_hand = 10}</code></pre>
<br>

<hr>
<br>

<h3 id="📍-operations-on-structures">📍 Operations on Structures</h3>
<p>구조체 내의 구성원에 접근하려면, (구조체의 이름).(구성원의 이름)으로 접근할 수 있다. </p>
<p>🔎 part1의 구성원 값들 출력</p>
<pre><code class="language-c">printf(&quot;Part number: %d\n&quot;, part1.number);
printf(&quot;Part name: %s\n&quot;, part1.name);
printf(&quot;Quantity on hand: %d\n&quot;, part1.on_hand);</code></pre>
<p>구조체의 구성원은 lvalue이다. </p>
<p>구두점(.)은 연산자로, ++, -- 등과 같은 연산 순서를 가진다. </p>
<p>또한 구조체는 = 연산자를 이용하여 복사가 가능하다.</p>
<pre><code class="language-c">part2 = part1;</code></pre>
<p>위 구문은 part1.number을 part2.number2에 복사, part1.name은 part2.name에 복사한다. </p>
<p>배열은 = 연산자로 복사가 불가능하지만 구조체 안에 존재하는 배열은 해당 구조체가 복사될 때 같이 복사된다. </p>
<p>🔎 구조체 내 배열 복사 </p>
<pre><code class="language-c">struct { int a[10]; } a1, a2;

a1 = a2; </code></pre>
<p>= 연산자는 호환 가능한 구조체 간에만 사용할 수 있다. 동시에 선언된 두 구조체나, 같은 구조체 태그를 갖고 선언된 구조체, 같은 형명을 갖는 구조체들이 서로 호환될 수 있다. </p>
<p>할당 이외에는 추가적인 연산이 제공되지 않는다. </p>
<br>

<hr>
<br>

<h2 id="📍-structure-types">📍 Structure Types</h2>
<p>변수를 한 번만 선언하는 경우는 괜찮지만, 다른 곳에서 또 선언해야 하는 경우에는 복잡해진다. </p>
<p>🔎 여러 번 선언 </p>
<pre><code class="language-c">struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part1;

struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part2;</code></pre>
<p>이렇게 했을 때의 문제점은 part1과 part2가 호환이 되지 않는다는 점이다. part1은 part2에 할당될 수 없고, part2는 part1에 할당될 수 없다. 또한 part1, part2의 형 이름이 무엇인지 알 수 없어 함수 호출에서 인수로 사용할 수 없다. </p>
<p>이런 어려움들을 피하기 위해 구조체의 이름을 정의한다. </p>
<p>🔎 structure의 이름 짓기</p>
<p>1) 구조체 태그(structure tag)를 선언하기 
2) typedef를 사용하여 형 이름 정의하기 </p>
<br>

<h3 id="📍-declaring-a-structure-tag">📍 Declaring a Structure Tag</h3>
<p>구조체 태그(structure tag) : structure의 종류를 식별하는데 사용되는 이름 </p>
<p>🔎 구조체 태그(part) 선언하기</p>
<pre><code class="language-c">struct part {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
};</code></pre>
<p>이제 part를 변수 선언에 이용할 수 있다. </p>
<pre><code class="language-c">struct part part1, part2;</code></pre>
<p>이 때 struct를 빼먹으면 안 된다. part는 형 이름은 아니다. </p>
<p>structure tag를 선언함과 동시에 변수 선언도 가능하다. </p>
<pre><code class="language-c">struct part {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} part1, part2;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-defining-a-structure-type">📍 Defining a Structure Type</h3>
<p>typedef를 이용하여 실제 형 이름(type name)을 정의할 수 있다. </p>
<p>🔎 typedef를 이용하여 형 이름을 Part로 선언하기</p>
<pre><code class="language-c">typedef struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} Part;</code></pre>
<p>Part는 형 이름이므로 structure tag에서와는 다르게 Part만을 이용하여 변수를 선언해줄 수 있다.</p>
<pre><code class="language-c">Part part1, part2;</code></pre>
<p>🔎 Structure tag vs Typedef</p>
<p>구조체가 linked list로 사용될 때는 Structure tag가 필수적이다. 따라서 이 책의 대부분의 예에서는 typedef 대신 structure tag를 사용하였다.</p>
<br>

<hr>
<br>

<h3 id="📍-structures-as-arguments-and-return-values">📍 Structures as Arguments and Return Values</h3>
<p>함수는 인자(argument)와 반환 값(return value)로 구조체를 가질 수 있다. </p>
<p>🔎 구조체가 인자</p>
<pre><code class="language-c">void print_part(struct part p)
{
    printf(&quot;Part number : %d\n&quot;, p.number);
    printf(&quot;Part name : %s\n&quot;, p.name);
    printf(&quot;Quantity on hand : %d\n&quot;, p.on_hand);
}


print_part(part1); </code></pre>
<p>🔎 구조체가 반환값</p>
<pre><code class="language-c">struct part build_part(int number, const char* name, int on_hand)
{
    struct part p;

    p.number = number;
    strcpy(p.name, name);
    p.on_hand = on_hand;
    return p;
}</code></pre>
<p>이 때 구조체를 함수에 전달하는 것과, 구조체를 반환할 때 모두 구조체 내 모든 구성원의 복사가 일어난다. </p>
<p>따라서 구조체 자체를 전달하기보다 포인터를 전달하는 것이 권장될 때가 있다. 반환 값에서도 포인터를 반환할 수 있다. </p>
<p>또한 열린 파일의 상태에서 작업을 수행하는 경우에도 포인터를 사용하는 것이 효과적이다. 예를 들어 &lt;stdio.h&gt;는 FILE이라는 형식의 type 이름을 가지고 있는데, 각 FILE 구조는 열린 파일의 상태에 대한 정보를 저장하므로 프로그램에서 고유해야 한다. 따라서 이 경우도 포인터를 이용한다. </p>
<p>경우에 따라 함수 내부의 구조체를 다른 구조체로 초기화시킬 수 있다. </p>
<p>🔎 part2를 part1와 동일한 값으로 초기화</p>
<pre><code class="language-c">void f(struct part part1)
{
    struct part part2 = part1;
    ...
}</code></pre>
<br>

<hr>
<br>

<h3 id="📍-c99-compound-literals-복합리터럴">📍 [C99] Compound Literals (복합리터럴)</h3>
<p>구조체에서 복합 리터럴은 먼저 변수를 저장하지 않고 &quot;즉시&quot; 구조를 만드는데 사용될 수 있다. 그 결과물(구조체)는 파라미터로 전달될 수 있고, 함수에 의해 반환될 수 있고, 변수에 할당될 수 있다.</p>
<p>일반적으로 복합리터럴은 괄호 안의 형식 이름과, 중괄호로 둘러싸인 값의 집합으로 구성된다. </p>
<p>🔎 복합리터럴을 이용하여 함수에 전달 </p>
<pre><code class="language-c">print_part((struct part) {528, &quot;Disk drive&quot;, 10});</code></pre>
<p>복합리터럴은 part라는 구조체를 만든다. </p>
<p>🔎 복합리터럴을 이용하여 변수에 할당</p>
<pre><code class="language-c">part1 = (struct part) {528, &quot;Disk drive&quot;, 10};</code></pre>
<p>복합리터럴은 지정자(designators)를 포함할 수 있다. </p>
<p>🔎 지정자가 포함된 복합 리터럴</p>
<pre><code class="language-c">print_part((struct part) {
        .on_hand = 10,
        .name = &quot;Disk drive&quot;,
        .number = 528
});</code></pre>
<br>

<hr>
<br>

<h2 id="📍-nested-arrays-and-structures">📍 Nested Arrays and Structures</h2>
<p>구조체는 어레이의 구성요소로 들어갈 수 있고 어레이는 구조체의 구성요소가 될 수 있다. </p>
<br>

<hr>
<br>

<h3 id="📍-nested-structures">📍 Nested Structures</h3>
<p>아래와 같은 구조체가 있을 때,</p>
<pre><code class="language-c">struct person_name {
    char first[FIRST_NAME_LEN + 1];
    char middle_initial;
    char last[LAST_NAME_LEN + 1];
};</code></pre>
<p>구조체 안에 구조체를 넣을 수 있다.</p>
<pre><code class="language-c">struct student {
    struct person_name name;
    int id, age;
    char sex;
} student1, student2;</code></pre>
<p>이 때 student1의 name 중에서도 first에 접근하고자 한다면 아래와 같이 구두점(.)을 이용하면 된다</p>
<pre><code class="language-c">strcpy(student1.name.first, &quot;Fred&quot;); </code></pre>
<p>🔎 name을 구조체로 만든 것의 장점</p>
<p>name을 데이터의 단위로써 다룰 수 있다. name을 출력하는 함수를 만들고자 할 때, 인수로 하나만 전달하면 된다. </p>
<p>🔎 name을 출력하는 함수</p>
<pre><code class="language-c">display_name(student1.name);</code></pre>
<p>🔎 person_name 구조체에서 name을 student 구조체의 member로 복사하기</p>
<pre><code class="language-c">struct person_name new_name;
...
student1.name = new_name;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-arrays-of-structures">📍 Arrays of Structures</h3>
<p>어레이와 구조체의 결합에서 자주 쓰이는 것 중 하나는 구조체가 어레이를 이루는 것이다. </p>
<p>아래 예시 구조체 part의 어레이는 part 100개의 정보를 저장할 수 있다</p>
<pre><code class="language-c">struct part inventory[100];</code></pre>
<p>이 어레이의 요소에 접근하려면 배열첨자[ ]를 이용하면 된다.</p>
<pre><code class="language-c">print_part(inventory[i]);</code></pre>
<p>구조체 part의 구성원(member)에 접근하려면 배열첨자와 구두점을 이용하여 멤버를 선택하면 된다. </p>
<p>🔎 inventory[i]의 구성원 number에 883 할당하기</p>
<pre><code class="language-c">inventory[i].number = 883;</code></pre>
<p>name의 문자 하나에 접근하려면 마찬가지로 배열 첨자를 이용하면 된다. </p>
<p>🔎 inventory[i]에 저장된 name을 빈 문자열로 만들기</p>
<pre><code class="language-c">inventory[i].name[0] = &#39;\0&#39;;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-initializing-an-array-of-structures">📍 Initializing an Array of Structures</h3>
<p>구조체 어레이를 초기화 하는 것은 다차원 어레이를 초기화하는 것과 거의 동일하다. 
구조체 어레이를 초기화하는 이유 중 하나는 이를 실행 중 값이 변하지 않는 데이터베이스 취급하기 위해서 이다. </p>
<p>🔎 구조체를 이용하여 국가번호 저장하기</p>
<pre><code class="language-c">struct dialing_code {
    char* country;
    int code;
};</code></pre>
<p>🔎 위 구조체를 담고 있는 어레이 초기화 하기</p>
<pre><code class="language-c">const struct dialing_code country_codes[] = {
    {&quot;Argentina&quot;,        54}, {&quot;Bangladesh&quot;,        880},
    {&quot;Brazil&quot;,        55}, {&quot;Burma (Myanmar)&quot;,     95}
    ...
};</code></pre>
<pre><code class="language-c">struct part inventory[100] =
{ [0] .number = 528, [0].on_hand = 10, [0].name[0] = &#39;\0&#39; };</code></pre>
<br>

<hr>
<br>

<h3 id="📍-program-maintaining-a-parts-database">📍 [PROGRAM] Maintaining a Parts Database</h3>
<p>창고에 남아있는 부품들을 표시하는 프로그램을 작성해보자 
들어갈 정보는 부품 번호, 이름, 수량이다. </p>
<ul>
<li><p>새로운 부품 번호, 이름, 남은 수량을 추가할 수 있도록 한다. 만약 이미 해당 부품이 있거나 가득찬 경우 에러 메세지를 출력한다. </p>
</li>
<li><p>부품 번호가 주어지면 이름, 남은 수량을 출력할 수 있도록 한다. 만약 해당 부품이 데이터베이스에 없다면 에러 메세지를 출력한다. </p>
</li>
<li><p>부품 번호가 주어지면 남은 수량을 변경할 수 있도록 한다. 만약 해당 부품이 데이터베이스에 없다면 에러 메세지를 출력한다. </p>
</li>
<li><p>데이터베이스에 담긴 정보 전체를 출력할 수 있도록 한다. 입력한 순서대로 출력되도록 한다. </p>
</li>
<li><p>프로그램 실행 종료</p>
</li>
</ul>
<p>이 행동들을 하기 위해 i(insert), s(search), u(update), p(print), q(quit)을 이용한다. </p>
<p>🔎 inventory.c</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &quot;readline.h&quot;

#define NAME_LEN 25
#define MAX_PARTS 100

struct part {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
} inventory[MAX_PARTS];

int num_parts = 0;

int find_part(int number);
void insert(void);
void search(void);
void update(void);
void print(void);

int main(void)
{
    char code;
    for (;;) {
        printf(&quot;Enter operation code : &quot;);
        scanf(&quot; %c&quot;, &amp;code);
        while (getchar() != &#39;\n&#39;);
        switch (code) {
        case &#39;i&#39;: insert();
            break;
        case &#39;s&#39;: search();
            break;
        case &#39;u&#39;: update();
            break;
        case &#39;p&#39;: print();
            break;
        case &#39;q&#39;: return 0;
        default: printf(&quot;Illegal code\n&quot;);
        }
        printf(&quot;\n&quot;);
    }
}

int find_part(int number)
{
    int i;

    for (i = 0; i &lt; num_parts; i++)
        if (inventory[i].number == number)
            return i;
    return -1;
}

void insert(void)
{
    int part_number;

    if (num_parts == MAX_PARTS) {
        printf(&quot;Database is full; can&#39;t add more parts.\n&quot;);
        return;
    }

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;part_number);

    if (find_part(part_number) &gt;= 0) {
        printf(&quot;Part already exists.\n&quot;);
        return;
    }

    inventory[num_parts].number = part_number;
    printf(&quot;Enter part name : &quot;);
    read_line(inventory[num_parts].name, NAME_LEN);
    printf(&quot;Enter quantity on hand : &quot;);
    scanf(&quot;%d&quot;, &amp;inventory[num_parts].on_hand);
    num_parts++;
}

void search(void)
{
    int i, number;

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;number);
    i = find_part(number);
    if (i &gt;= 0) {
        printf(&quot;Part name : %s\n&quot;, inventory[i].name);
        printf(&quot;Quantity on hand : %d\n&quot;, inventory[i].on_hand);
    }
    else
        printf(&quot;Part not found.\n&quot;);
}

void update(void)
{
    int i, number, change;

    printf(&quot;Enter part number : &quot;);
    scanf(&quot;%d&quot;, &amp;number);
    i = find_part(number);
    if (i &gt;= 0) {
        printf(&quot;Enter change in quantity on hand : &quot;);
        scanf(&quot;%d&quot;, &amp;change);
        inventory[i].on_hand += change;
    }
    else
        printf(&quot;Part not found.\n&quot;);
}

void print(void)
{
    int i;

    printf(&quot;Part Number        Part Name            &quot;
        &quot;Quantity on Hand\n&quot;);
    for (i = 0; i &lt; num_parts; i++)
        printf(&quot;%7d            %-25s%11d\n&quot;, inventory[i].number,
            inventory[i].name, inventory[i].on_hand);
}</code></pre>
<br>

<p>🔎 readline.h</p>
<pre><code class="language-c">#ifndef READLINE_H
#define READLINE_H

int read_line(char str[], int n);

#endif</code></pre>
<br>


<p>🔎 readline.c</p>
<pre><code class="language-c">
#include &lt;ctype.h&gt;
#include &lt;stdio.h&gt;
#include &quot;readline.h&quot;

int read_line(char str[], int n)
{
    int ch, i = 0;

    while (isspace(ch = getchar()));
    while (ch != &#39;\n&#39; &amp;&amp; ch != EOF) {
        if (i &lt; n)
            str[i++] = ch;
        ch = getchar();
    }
    str[i] = &#39;\0&#39;;
    return i;
}
</code></pre>
<br>

<hr>
<br>

<h3 id="📍-unions">📍 Unions</h3>
<p>공용체(union)는 구조체 처럼 하나 이상의 서로 다른 유형의 구성원(member)을 가질 수 있다. 그러나 컴파일러는 가장 큰 구성원을 위한 공간만을 할당하고, 서로 이 공간을 공유한다.</p>
<p>🔎 공용체 선언</p>
<pre><code class="language-c">union {
    int i;
    double d;
} u;</code></pre>
<p>🔎 공용체 vs 구조체 </p>
<pre><code class="language-c">struct {
    int i;
    double d;
} u;</code></pre>
<p>가 있다고 가정할 때 둘의 차이는 메모리의 주소에 있다. </p>
<p><img src="https://images.velog.io/images/mi_pine/post/513c2725-f1df-4cea-a435-dee9f8581e23/image.png" alt=""></p>
<p>🔎 공용체 선언 - u의 구성원 i에 82 할당하기 </p>
<pre><code class="language-c">u.i = 82;</code></pre>
<p>공용체의 경우 한 멤버를 변경하면 다른 멤버에 저장되어 있던 값들이 변화한다. 
따라서 이 경우 u.d의 값을 변화시켜주면 u.i에 저장된 값은 사라진다. </p>
<p>따라서 공용체 u는 i, d 둘다가 아닌 i나 d 중 하나를 저장시키는 공간이다. </p>
<p>공용체의 특성은 구조체와 거의 동일하다. tag와 type을 동일한 방법으로 선언할 수 있으며 = 연산자를 이용하여 할당할 수 있다. </p>
<p>다만 공용체에서는 첫번째 구성원만 초기화가 가능하다. </p>
<pre><code class="language-c">union {
    int i;
    double d;
} u = { 0 };</code></pre>
<p>C99에서는 어떤 구성원을 초기화할지 지정할 수 있다. 구성원 하나만 초기화가 가능하나, 꼭 첫번째일 필요는 없다.</p>
<pre><code class="language-c">union {
    int i;
    double d;
} u = { .d = 10.0 };</code></pre>
<br>

<hr>
<br>

<h3 id="📍-using-unions-to-save-space">📍 Using Unions to Save Space</h3>
<p>아래의 선물 목록을 저장하고자 할 때, </p>
<p>Books : Title, author, number of pages
Mugs : Design
Shirts : Design, colors avialable, sizes available</p>
<p>구조체를 만들면 다음과 같다. </p>
<pre><code class="language-c">struct catalog_item {
    int stock_number;
    double price;
    int item_type;
    char title[TITLE_LEN + 1];
    char author[AUTHOR_LEN + 1];
    int num_pages;
    char design[DESIGN_LEN + 1];
    int colors;
    int sizes;
};</code></pre>
<p>BOOK, MUG, SHIRT 모두 item_type을 가지고 있지만, colors, size 등은 shirts만 가지고 있는 것이기 때문에 공간 낭비가 일어나게 된다. 즉 책의 경우 design, colors, sizes를 저장할 필요가 없다. </p>
<p>이 경우 구조체에 공용체를 넣으면 공간낭비를 줄일 수 있다. 공용체의 구성원은 각각의 특수한 목록을 담은 구조체이어야 한다. 즉 겹치지 않는 것들은 구조체 내 공용체에 담으면 된다. </p>
<p>🔎 공용체를 이용한 catalog_item </p>
<pre><code class="language-c">struct catalog_item {
    int stock_number;
    double price;
    int item_type;
    union {
        struct {
            char title[TITLE_LEN + 1];
            char author[AUTHOR_LEN + 1];
            int num_pages
        } book;
        struct {
            char design[DESIGN_LEN + 1];
        } mug;
        struct {
            char design[DESIGN_LEN + 1];
            int colors;
            int sizes;
        } shirt;
    } item;
};</code></pre>
<p>만약 c가 책을 가리키는 catalog_item 구조체라면, 다음과 같이 책의 title을 출력할 수 있다. </p>
<pre><code class="language-c">printf(&quot;%s&quot;, c.item.book.title);</code></pre>
<p>만약 mug의 design에 &quot;Cats&quot;를 할당한다면, 공용체 내 다른 구조체의 design에도 똑같이 &quot;Cats&quot;가 할당된다. </p>
<pre><code class="language-c">strcpy(c.item.mug.design, &quot;Cats&quot;);

printf(&quot;%s&quot;, c.item.shirt.design); /* prints &quot;Cats&quot; */</code></pre>
<br>

<hr>
<br>

<h3 id="📍-using-unions-to-build-mixed-data-structures">📍 Using Unions to Build Mixed Data Structures</h3>
<p>union을 활용하여 다양한 유형의 데이터가 혼합된 데이터 구조를 생성할 수 있다. </p>
<p>만약 int와 double이 혼합된 어레이를 만든다고 가정하자. 어레이는 한 가지의 유형만 가질 수 있으므로 불가능한 것처럼 보이지만 공용체를 사용하면 가능하다. </p>
<p>먼저 서로 다른 종류의 데이터를 담는 공용체를 만든다.</p>
<pre><code class="language-c">typedef union {
    int i;
    double d;
} Number;</code></pre>
<p>그 후, 요소가 Number인 어레이를 제작하면 된다. </p>
<pre><code class="language-c">Number number_array[1000];</code></pre>
<p>그럼 number_array에는 int 와 double 모두를 저장할 수 있다.</p>
<pre><code class="language-c">number_array[0].i = 5;
number_array[1].d = 8.395;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-adding-a-tag-field-to-a-union">📍 Adding a &quot;Tag Field&quot; to a Union</h3>
<p>Union에는 치명적인 단점이 있다. 최근에 어떤 구성원이 변경되었는지 알 수 없어 의미없는 값이 담겨있어도 알 수가 없다. </p>
<p>우리는 조합에 현재 저장되어 있는 것을 상기시키는 것을 목적으로 태그 필드(tag field) 또는 차별자(discriminant)를 구조체 안에 공용체를 포함시킬 수 있다.</p>
<p>앞서 catalog_item 구조체에서는 item_type이 이러한 역할을 수행한다. </p>
<p>🔎 tag field를 이용하여 구조체 선언 </p>
<pre><code class="language-c">#define INT_KIND 0
#define DOUBLE_KIND 1

typedef struct {
    int kind; /* tag field */
    union {
        int i;
        double d;
    } u;
} Number; </code></pre>
<p>kind는 INT_KIND거나 DOUBLE_KIND이다. </p>
<p>u의 구성원에 값을 할당할 때 마다 수정사항을 상기키시기 위해 kind도 함께 변경시키면 된다.</p>
<p>🔎 n이 Number의 변수인 경우 u의 i 멤버를 할당하기</p>
<pre><code class="language-c">n.kind = INT_KIND;
n.u.i = 82;</code></pre>
<p>이렇게 하면 Number 변수에 지정된 숫자를 검색해야 할 때, kind는 어떤 공용체가 마지막으로 값을 할당받았는지 알려준다. </p>
<p>void print_number(Number n)
{
    if (n.kind == INT_KIND)
        printf(&quot;%d&quot;, n.u.i);
    else
        printf(&quot;%g&quot;, n.u.d);
}</p>
<br>

<hr>
<br>

<h2 id="📍-enumerations">📍 Enumerations</h2>
<p>많은 프로그램에서, 단지 작은 의미 있는 값들의 집합만을 가진 변수들이 필요할 것이다.
예를 들어 카드 게임에서 카드명을 저장하는 변수는 &quot;클로버&quot; &quot;다이아몬드&quot; &quot;하트&quot; &quot;스페이드&quot; 4가지의 값이 필요하다. </p>
<p>이러한 변수를 처리하는 확실한 방법은 가능한 변수의 값과 정수를 매치시키는 것이다.</p>
<pre><code class="language-c">int s; /* s will store a suit */
...
s = 2; /* 2 represents &quot;hearts&quot; */</code></pre>
<p>이러한 경우 총 4가지의 변수를 가지고 있다는 점이나, 2가 의미하는 바 등이 직관적이지 않다. </p>
<p>매크로를 이용하여 suit의 유형과 이름을 정의할 수 있다. </p>
<pre><code class="language-c">#define SUIT int 
#define CLUBS 0
#define DIAMONDS 0
#define HEARTS 0
#define SPADES 0</code></pre>
<p>이 경우 좀 더 읽기 쉽도록 만들어진다</p>
<pre><code class="language-c">SUIT s;
...
s = HEARTS;</code></pre>
<p>그러나 완벽한 해결책은 아니다. 프로그램을 읽는 사람에게는 매크로가 동일한 &quot;유형&quot;의 값을 나타낸다는 표시가 없다. 또한 정의한 이름은 전처리에 의해 제거되므로 디버깅 중에 사용할 수 없다.</p>
<p>C는 가능한 값의 수가 적은 변수를 위해 특별히 설계된 특수한 유형을 제공한다. 열거형(enumerated type)은 프로그래머에 의해 값이 나열되는 유형이며, 프로그래머는 각 값에 대한 이름(enumeration constant)을 생성해야 한다.</p>
<p>🔎 변수 s1과 s2에 할당할 수 있는 값을 열거</p>
<pre><code class="language-c">enum {CLUBS, DIAMONDS, HEARTS, SPADES} s1, s2; </code></pre>
<p>열거형이 함수 내부에서 선언되면 해당 상수는 함수 외부에서 사용할 수 없다. </p>
<br>

<h3 id="📍-enumeration-tags-and-type-names">📍 Enumeration Tags and Type Names</h3>
<p>구조체, 공용체와 함께 열거형의 이름을 지을 두가지 방법이 있다. tag를 선언하거나 typedef를 사용하여 실제 형 이름을 만드는 것이다. </p>
<p>열거형 태그는 구조체, 공용체 태그와 유사하다. </p>
<p>🔎 suit 태그 정의</p>
<pre><code class="language-c">enum suit {CLUBS, DIAMONDS, HEARTS, SPADES};</code></pre>
<p>🔎 suit의 변수 정의 </p>
<pre><code class="language-c">enum suit s1, s2;</code></pre>
<p>대안으로 Suit를 형 이름으로 만들기 위해 typedef를 이용할 수 있다</p>
<pre><code class="language-c">typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit; 
Suit s1, s2;</code></pre>
<br>

<hr>
<br>

<h3 id="📍-enumerations-as-integers">📍 Enumerations as Integers</h3>
<p>C는 열거형 변수들을 정수처럼 다룬다. 컴파일러는 각각의 열겨형들에 0, 1, 2.. 를 할당한다. 앞서 suit 열거형을 예시로 들면, CLUBS, DIAMONDS, HEARTS, SPADES는 각각 0, 1, 2, 3에 대응된다. </p>
<p>다른 상수를 넣고 싶다면 원하는 값을 할당해줄 수도 있다. </p>
<pre><code class="language-c">enum suit {CLUBS = 1, DIAMONDS = 2, HEARTS = 3, SPADES = 4};</code></pre>
<p>같은 상수를 갖는 것도 가능하다.
열거형은 결국 정수이므로 다른 정수와 섞는 것도 가능하다. </p>
<pre><code class="language-c">int i;
enum {CLUBS, DIAMONDS, HEARTS, SPADES} s;

i = DIAMONDS;
s = 0;
s++;
i = s + 2; </code></pre>
<p>컴파일러는 s를 정수처럼 다룬다. </p>
<br>

<hr>
<br>

<h3 id="📍-using-enumerations-to-declare-tag-fields">📍 Using Enumerations to Declare &quot;Tag Fields&quot;</h3>
<p>앞서 16.4 절에서 다루었던, 최근에 수정된 값들을 파악하는데 열거형은 효과적이다. 
예를 들어 kind 변수를 int 대신 열거형으로 만들면 다음과 같다. </p>
<p>🔎 열거형을 이용해 kind 만들기</p>
<pre><code class="language-c">typedef struct {
    enum {INT_KIND, DOUBLE_KIND} kind;
    union {
        int i;
        double d;
    } u;
} Number;</code></pre>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[📗15장 Writing Large Programs | Exercises and  Programming Projects]]></title>
            <link>https://velog.io/@mi_pine/15%EC%9E%A5-Writing-Large-Programs-Exercises-and-Programming-Projects</link>
            <guid>https://velog.io/@mi_pine/15%EC%9E%A5-Writing-Large-Programs-Exercises-and-Programming-Projects</guid>
            <pubDate>Thu, 27 Jan 2022 16:10:08 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING 의 Exercises와 Programming Projects를 푼 내용입니다. </p>
<h3 id="📍-exercises">📍 Exercises</h3>
<br>

<ol>
<li><p>(a) 
1) 프로그램의 구조가 명확해진다.
2) 각 소스 파일들을 따로 컴파일 시킬 수 있어 프로그램의 규모가 크거나 자주 수정되는 경우 시간이 절약된다.
3) main함수와 다른 함수들을 분리함으로써 다른 프로그램에서 함수 재사용이 가능하다. </p>
<p>(b)
1) 오류가 발생할 가능성이 있다. (형정의를 서로 다르게 하는 등)</p>
</li>
</ol>
<br>

<ol start="2">
<li>(b) 같은 함수가 여러번 컴파일 된다. </li>
</ol>
<br>

<ol start="3">
<li>문제가 발생한다. 우리가 직접 만든 파일의 이름이 해당 경로에 있는 헤더의 이름과 일치하지 않아 찾을 수가 없다.</li>
</ol>
<br>

<ol start="4">
<li><pre><code class="language-c">Output if DEBUG is defined:
Value of i : 1
Value of j : 2
Value of k : 3
Value of i + j : 3
Value of 2 * i + j - k : 1</code></pre>
(b) <pre><code class="language-c">Output if DEBUG is not defined:</code></pre>
(c)</li>
</ol>
<p><strong>i. #define DEBUG를 한 경우</strong>
testdebug.c에서 DEBUG를 정의해준 경우 debug.h에서 PRINT_DEBUG(n)는  printf(&quot;Value of &quot; #n &quot; : %d\n&quot;, n)로 대체되어 값을 출력해준다. 
testdebug.c에서는 #ifdef 문을 만족하여 Output if DEBUG is defined: 가 출력한다.
그 후 PRINT_DEBUG로 가는데, 여기서 헤더파일에 의해 값들을 출력해준다. </p>
<p>*<em>ii. #define DEBUG를 하지 않은 경우 *</em>
testdebug.c에서 DEBUG를 정의해주지 않은 경우 debug.h 에서 PRINT_DEBUG(n)가 정의되지 않는다. 
testdebug.c에서는 #else 문으로 가게 되어 Output if DEBUG is not defined: 가 출력된다. 
그 후 PRINT_DEBUG로 가는데, 여기서 아무 행동도 하지 않으므로 아무것도 출력하지 않고 끝난다. </p>
<br>
5.

<pre><code class="language-c">demo: main.o f1.o f2.o
    gcc -o demo main.o f1.o f2.o

main.o: main.c f1.h
    gcc -c main.c

f1.o: f1.h f2.h
    gcc -c f1.c

f2.o: f1.h f2.h
    gcc -c f2.c</code></pre>
<br>

<ol start="6">
<li>(a) main.c, f1.c, f2.c
(b) f1.c
(c) main.c, f1.c, f2.c
(d) f1.c, f2.c </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘15장 Writing Large Programs | Study]]></title>
            <link>https://velog.io/@mi_pine/15%EC%9E%A5-Writing-Large-Programs-Study</link>
            <guid>https://velog.io/@mi_pine/15%EC%9E%A5-Writing-Large-Programs-Study</guid>
            <pubDate>Thu, 27 Jan 2022 13:04:58 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING으로 C언어를 공부하면서 정리한 내용입니다.
번역본 <a href="https://wikidocs.net/book/2494">https://wikidocs.net/book/2494</a> 을 참고하였습니다.</p>
<hr>
<br>
15.1 Source Files<br>
15.2 Header Files<br> 
15.3 Dividing a Program into Files<br>
15.4 Building a Multiple-File Program<br>
<br>

<hr>
<br>

<h2 id="📍-writing-large-programs">📍 Writing Large Programs</h2>
<p>소스 파일은 함수와 외부 변수의 정의부를 갖고 있고, 헤더 파일은 소스 파일간 공유할 정보를 갖고 있다. 
<br></p>
<hr>
<br>

<h3 id="📍-source-files">📍 Source Files</h3>
<p>C 프로그램은 여러개의 소스 파일로 나뉠 수 있다. 각 소스파일은 주로 함수와 변수들의 정의가 담겨 있으며 소스 파일들 중 하나는 반드시 프로그램의 시작점을 의미하는 main 함수를 가지고 있어야 한다. </p>
<p>🔎 프로그램을 여러 소스 파일로 나눌 때의 장점 </p>
<p>1) 관련이 있는 함수, 변수들을 한 파일로 모으면 프로그램의 구조가 명확해진다.</p>
<p>2) 각 소스 파일들은 따로 컴파일 시킬 수 있어 프로그램의 규모가 크거나 자주 수정되는 경우 시간이 절약된다</p>
<p>3) 함수들을 따로 소스 파일로 정리해주면 다른 프로그램에서도 재사용할 수 있다. main 함수와 다른 함수들을 분리함으로써 다른 프로그램에서 해당 함수 사용이 가능해진다. 
<br></p>
<hr>
<br>

<h3 id="📍-header-files">📍 Header Files</h3>
<p>#include 지시자를 통해 여러 소스 파일 간 함수 원형, 매크로 정의, 형 정의 등과 같은 정보들을 공유할 수 있다. </p>
<p>#include 지시자는 전처리기에게 주어진 파일을 열어 그 내용을 현재 파일에 전부 붙여넣기 만든다. 이런 식으로 추가되는 파일들을 헤더 파일(header file)이라고 부른다. 
<br></p>
<hr>
<br>

<h3 id="📍-the-include-directive">📍 The #include Directive</h3>
<p>🔎 #include 사용</p>
<p>1) C 자체 라이브러리에 존재하는 헤더 파일을 추가할 때</p>
<pre><code class="language-c">#include &lt;filename&gt;</code></pre>
<p>시스템 헤더 파일들이 있는 폴더에서 파일을 찾는다.
<br></p>
<p>2) 직접 작성해준 헤더 파일 등 </p>
<pre><code class="language-c">#include &quot;filename&quot;</code></pre>
<p>이 경우 현재 폴더에서 파일을 찾는다. </p>
<br>

<hr>
<br>

<h3 id="📍-sharing-macro-definitions-and-type-definitions">📍 Sharing Macro Definitions and Type Definitions</h3>
<p>매크로 정의, 행 정의들을 소스 파일들 간에 공유해야 할 때는 헤더파일에 넣어주는 것이 좋다. </p>
<p>그러면 소스 파일마다 일일이 정의를 넣어줄 필요가 없으며 프로그램을 수정하기가 쉬워진다. 또한 소스 파일들이 서로 같은 매크로와 형을 다르게 정의하는 일을 막을 수 있다. 
<br></p>
<hr>
<br>

<h3 id="📍-sharing-function-prototypes">📍 Sharing Function Prototypes</h3>
<p>함수의 원형이 정의되기 전에 호출되는 것을 막기 위해 헤더 파일에 함수의 원형을 넣어주면 된다. </p>
<p>예를 들어 foo.c에 f라는 함수가 선언되어 있다면 foo.h라는 헤더파일을 만들어 f의 함수 원형을 넣고, foo.h를 foo.c와 f를 호출하는 소스 파일에 추가해준다. </p>
<p>🔎 예시 (stack) </p>
<p><img src="https://images.velog.io/images/mi_pine/post/507f568e-26a9-42c0-9e0d-f661d72683dd/image.png" alt=""></p>
<br>

<hr>
<br>

<h3 id="📍-sharing-variable-declarations">📍 Sharing Variable Declarations</h3>
<p>외부변수는 함수와 같은 방법으로 파일끼리 공유할 수 있다. </p>
<p>아래의 경우 i를 선언하고 정의도 해준다. </p>
<pre><code class="language-c">int i</code></pre>
<p>만약 변수를 정의하지 않고 선언만 해주려면 extern 키워드를 이용하면 된다. </p>
<pre><code class="language-c">extern int i;</code></pre>
<p>이 경우 컴파일러는 이러한 파일들에 대해 각각 i의 공간을 전부 할당해주지는 않는다. </p>
<p>변수를 파일 간 공유를 할 땐 동일한 변수가 서로 다른 파일에서 다르게 선언되지 않도록 주의해야 한다. </p>
<p>파일 간 변수를 공유하는 것은 몇 가지 단점들이 있다. </p>
<br>

<hr>
<br>

<h3 id="📍-nested-includes-내부-인클루드">📍 Nested Includes (내부 인클루드)</h3>
<p>헤더 파일에서도 #include 지시자를 이용할 수 있다. 
예를 들어 stack.h에 boolean.h 파일을 추가해줄 수 있다. </p>
<br>

<hr>
<br>

<h3 id="📍-protecting-header-files">📍 Protecting Header Files</h3>
<p>같은 헤더 파일이 두 번 추가되는 것을 막아주어야 한다. 헤더 파일을 보호하기 위해서는 파일의 내용물을 #ifndef-#endif로 감싸주면 된다. </p>
<pre><code class="language-c">#ifndef BOOLEAN_H
#define BOOLEAN_H

#define TRUE 1
#define FALSE 0
typedef int Bool;

#endif</code></pre>
<p>이 파일을 처음 추가할 땐 BOOLEAN_H 매크로가 정의되어있지 않으므로 전처리는 #ifndef와 #endif 사이의 줄들을 그대로 내버려 둘 것이다. 그러나 다음에는 그 사이 줄들을 없앤다. </p>
<br>

<hr>
<br>

<h3 id="📍-error-directives-in-header-files">📍 #error Directives in Header Files</h3>
<p>#error 지시자는 주로 헤더 파일 안에서 헤더 파일이 추가되지 말아야 할 조건들을 확인할  때 사용되곤 한다. </p>
<p>🔎 옛날 비표준 컴파일러 사용 방지</p>
<pre><code class="language-c">#ifndef __STDC__
#error This header requires a Standard C compiler
#endif</code></pre>
<br>

<hr>
<br>

<h3 id="📍-dividing-a-program-into-files">📍 Dividing a Program into Files</h3>
<p>🔎 justify(텍스트 서식 프로그램)에 quote 파일 넣기</p>
<p>justify</p>
<pre><code>   C     is quirky,  flawed,    and  an
enormous   success.      Although accidents of   history
 surely  helped,   it evidently    satisfied   a   need

    for  a   system  implementation    language    efficient
 enough   to  displace         assembly   language,
   yet sufficiently   abstract   and fluent    to describe
  algorithms   and     interactions    in a   wide   variety
of   environments.
                     --      Dennis     M.        Ritchie</code></pre><p>이 프로그램을 UNIX 혹은 Windows 프롬프트에서 실행하기 위해서는 아래 명령어를 실행해야 한다. </p>
<pre><code class="language-c">justify &lt;quote</code></pre>
<p>&lt; (input redirection) : justify가 입력을 키보드에서 받는 대신 OS로 하여금 quote 라는 파일에서부터 받도록 한다. </p>
<p>위 명령어를 실행하고 나면 다음과 같이 출력된다. </p>
<pre><code>C is quirky,  flawed,  and  an  enormous  success.  Although
accidents of history surely helped, it evidently satisfied a
need for a system implementation language  efficient  enough
to displace assembly language, yet sufficiently abstract and
fluent to describe algorithms and  interactions  in  a  wide
variety of environments. -- Dennis M. Ritchie</code></pre><p>output redirection을 이용하면 저장해 줄 수 있다. 
아래 명령어를 통해 newquote에 출력된 내용을 저장할 수 있다. </p>
<pre><code class="language-c">justify &lt;quote &gt;newquote</code></pre>
<p>justify에 대해 추가적으로 알아보면 불필요한 띄어쓰기나 빈 줄이 제거되어 정렬된다. 또한 20개의 문자보다 더 긴 단어를 읽게 되면 20개 이후의 문자들을 무시하고 별표로 대체한다. </p>
<p>예를 들어 antidisestablishmentarianism는 antidisestablishment* 로 출력된다. </p>
<p>이제 프로그램을 설계해보자. 먼저 단어들을 담고 있을 버퍼가 필요하다. 단어와 관련된 함수들을 한 파일(word.c)에 넣고, 줄 버퍼와 관련된 함수들은 다른 파일(line.c)에 넣을 수 있다. 또 justify.c에 main함수를 넣어준다. </p>
<p>🔎 word.c</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

#include &quot;word.h&quot;

int read_char(void)
{
    int ch = getchar();

    if (ch == &#39;\n&#39; || ch == &#39;\t&#39;) {
         return &#39; &#39;;
    }
    return ch;
}

void read_word(char* out_word, int len) {
    int ch = 0;
    int pos = 0;

    while ((ch = read_char()) == &#39; &#39;) {
    }

    while (ch != &#39; &#39; &amp;&amp; ch != EOF) {
        if (pos &lt; len) {
            word[pos++] = ch;
        }
        ch = read_char();
    }
    word[pos] = &#39;\0&#39;;
}</code></pre>
<br>

<p>🔎 line.c</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#include &quot;line.h&quot;

#define MAX_LINE_LEN 60

char g_line[MAX_LINE_LEN + 1];
int g_line_len = 0;
int g_num_words = 0;

void clear_line(void)
{
    g_line[0] = &#39;\0&#39;;
    g_line_len = 0;
    g_num_words = 0;
}

void add_word(const char *word)
{
    if (g_num_words &gt; 0) {
        g_line[g_line_len] = &#39; &#39;;
        g_line[g_line_len+1] = &#39;\0&#39;;
        g_line_len++;
    }
    strcat(g_line, word);
    g_line_len += strlen(word);
    g_num_words++;
}

int get_space_remaining(void)
{
    return MAX_LINE_LEN - g_line_len;
}

void write_line(void)
{
    int extra_spaces = MAX_LINE_LEN - g_line_len;
    int space_to_insert = 0;

    for (int i = 0; i &lt; g_line_len; i++) {
        if (g_line[i] != &#39; &#39;) {
            putchar(g_line[i]);
        } else {
            space_to_insert = extra_spaces / (g_num_words - 1);
            for (int j = 1; j &lt;= space_to_insert + 1; j++) {
                putchar(&#39; &#39;);
            }
            extra_spaces -= space_to_insert;
            g_num_words--;
        }
    }
    putchar(&#39;\n&#39;);
}

void flush_line(void)
{
    if (g_line_len &gt; 0)
        puts(g_line);
}</code></pre>
<br>

<p>🔎 justify.c</p>
<pre><code class="language-c">/* 글자로 이루어진 파일을 정렬해준다. */

#include &lt;string.h&gt;

#include &quot;line.h&quot;
#include &quot;word.h&quot;

#define MAX_WORD_LEN 20

int main(void)
{
    char word[MAX_WORD_LEN + 2];
    int word_len;

    clear_line();
    for (;;) {
        read_word(word, MAX_WORD_LEN+1);
        word_len = strlen(word);
        if (word_len == 0) {
            flush_line();
            return 0;
        }
        if (word_len &gt; MAX_WORD_LEN) {
            word[MAX_WORD_LEN] = &#39;*&#39;;
        }
        if (word_len + 1 &gt; space_remaining()) {
            write_line();
            clear_line();
        }
        add_word(word);
    }
}</code></pre>
<br>

<hr>
<br>

<h3 id="📍-building-a-multiple-file-program">📍 Building a Multiple-File Program</h3>
<p>1) <strong>컴파일</strong> : 프로그램의 각 소스 파일들은 독립적으로 컴파일되어야 한다. (헤더파일은 컴파일할 필요X) 
각 소스 파일마다 컴파일러는 목적 코드를 포함하는 파일인 목적 파일(object file)을 생성한다. windows에서는 .obj 확장자를 가진다. </p>
<p>2) <strong>링크</strong> : 링커는 전 단계에서 생성한 목적 파일을 라이브러리 함수를 위한 코드와 연결해주어 실행 가능한 파일을 생성한다. </p>
<br>

<hr>
<br>

<h3 id="📍-makefiles">📍 Makefiles</h3>
<p>명령 줄에 모든 소스 파일을 적는 것은 시간 낭비 이므로 UNIX는 메이크파일(makefile)이라는 개념을 만들었다. 메이크파일은 파일들을 수록하며, 파일 간의 종속성(dependencies)또한 명시한다. </p>
<p>🔎 justify 프로그램을 위한 UNIX 메이크파일</p>
<pre><code class="language-c">justify: justify.o word.o line.o
    gcc -o justify justify.o word.o line.o

justify.o: justify.c word.h line.h
    gcc -c justify.c

word.o: word.c word.h
    gcc -c word.c

line.o: line.c line.h
    gcc -c line.c</code></pre>
<p>1) <strong>규칙(rule)</strong> : 각 부분 
2) <strong>목표(target)</strong> : 각 규칙의 첫 번째 줄, 그 뒤로 종속성이 있는 파일들 
3) <strong>명령(command)</strong> : 두번째 줄, 목표가 종속되어 있는 파일이 수정되어 목표를 재구축해야 할 경우 실행</p>
<p>예를 들어 첫번째 규칙에서는 justify가 목표이며 justify가 justify.o, word.o, line.o에 종속되어 있다. 프로그램이 가장 최근에 구축되었을 때 이 3가지 중 하나라도 변경사항이 있다면 justify는 재구축을 해야 한다. </p>
<p>추가적으로 gcc -c justify.c에서 <strong>-c</strong>는 justify.c를 목적 파일로 컴파일하되 링크는 하지 말라는 의미이다. </p>
<p>이런식으로 프로그램을 위한 메이크파일을 형성한 이후 make 기능을 이용하여 프로그램을 구축할 수 있다. 각 파일의 시간, 날짜를 확인하여 최신 여부를 판단할 수 있고 재구축이 가능하다. </p>
<p>🔎 make와 관련하여 알아야 하는 사항</p>
<p>1) 메이크 파일의 각 명령은 스페이스가 아닌 탭 문자로 띄어 쓴다. </p>
<p>2) 메이크파일은 보통 Makefile(또는 makefile)이라는 이름으로 저장한다. </p>
<p>3) make를 실행하려면 아래 명령어를 실행하면 된다.</p>
<pre><code>make target </code></pre><p>여기서 target(목표)은 makefile에 수록된 목표중 하나이며 특정 목표를 명시하지 않을 경우 첫 번째 규칙의 목표를 구축한다.</p>
<br>

<hr>
<br>

<h3 id="📍-errors-during-linking">📍 Errors During Linking</h3>
<p>컴파일 때 검출하지 못한 몇몇 오류들은 링크할 때 검출되기도 한다. 특히 프로그램 내에 어떤 함수나 변수의 정의가 생략되어 있을 경우 미정의된 기호(undefined symbol) 또는 미정의도니 참조(undefined reference)와 같은 메시지를 출력한다. </p>
<p>🔎 링커 오류의 흔한 원인</p>
<p>1) 오타 
2) 누락된 파일 
3) 누락된 라이브러리 </p>
<br>

<hr>
<br>

<h3 id="📍-rebuiling-a-program">📍 Rebuiling a Program</h3>
<p>프로그램을 다시 컴파일 하는 경우 시간을 절약하기 위해 오로지 최근 변경 사항에 해당하는 파일들만 재컴파일 해야 한다. </p>
<p>재컴파일해야 하는 파일(변화가 있는 파일)이 몇 개인지 보기 위해서는 두 가지 경우를 고려해야 한다. </p>
<p><strong>1) 변화가 오로지 한 소스 파일에만 영향을 주는 경우</strong>
이 경우 해당 파일만 재컴파일 되어야 한다. </p>
<p><strong>2) 한 헤더 파일에 수정사항이 있을 경우</strong>
이 경우 해당 헤더 파일을 추가한 모든 파일이 영향을 받을 수 있으므로 모두 재컴파일해야 한다. </p>
<p>justify.c word.c는 재컴파일 하고, line.c는 재컴파일 하지 않아도 되는 경우 아래의 명령을 사용하면 된다. (gcc 컴파일러 기준)</p>
<pre><code class="language-c">gcc -o justify justify.c word.c line.o</code></pre>
<p>line.c는 재컴파일 하지 않아도 되므로 line.o 를 사용하였다. </p>
<p>makefile을 사용하는 장점 중 하나는 재구축이 자동으로 처리가 된다는 것이다. 각 파일의 날짜와 시간을 파악한 후 수정된 파일과 종속된 파일들을 모두 재컴파일 한다. </p>
<p>🔎 justify 프로그램을 재구축할 경우 make가 하는 행동</p>
<p>1) justify.c를 컴파일하여 justify.o 구축
2) word.c 컴파일 하여 word.o 구축
3) justify.o, word.o, line.o를 링크하여 justify 구축</p>
<br>

<hr>
<br>

<h3 id="📍-defining-macros-outside-a-program">📍 Defining Macros Outside a Program</h3>
<p>프로그램 파일을 수정하지 않고도 매크로의 값을 수정할 수 있다. </p>
<p>대부분의 컴파일러는 명령 줄에서 매크로의 값을 정할 수 있는 -D 옵션을 지원한다. </p>
<p>gcc -DDEBUG=1 foo.c</p>
<p>이 경우 foo.c에서 DUBUG 매크로의 값이 1로 정의도니다. </p>
<p>또한 매크로를 미정의 시켜주는 -U 옵션도 존재한다. </p>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘14장 The Preprocessor | Study]]></title>
            <link>https://velog.io/@mi_pine/14%EC%9E%A5-The-Preprocessor-Study</link>
            <guid>https://velog.io/@mi_pine/14%EC%9E%A5-The-Preprocessor-Study</guid>
            <pubDate>Tue, 25 Jan 2022 13:37:19 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING으로 C언어를 공부하면서 정리한 내용입니다.
번역본 <a href="https://wikidocs.net/book/2494">https://wikidocs.net/book/2494</a> 을 참고하였습니다.</p>
<hr>
<br>
14.1 How the Preprocessor Works <br>
14.2 Preprocessing Directives <br>
14.3 Macro Definitions<br> 
14.4 Conditional Compilation<br>
14.5 Miscellaneous Directives<br>
<br>

<hr>
<h2 id="📍-the-preproccessor">📍 The Preproccessor</h2>
<p>전처리기는 컴파일 이전에 C 프로그램을 수정해주는 소프트웨어이다. </p>
<hr>
<br>

<h3 id="📍-how-the-preprocessor-works">📍 How the Preprocessor Works</h3>
<p>전처리기의 행동은 전처리 지시자(preprocessing directives)에 의해 제어된다. 
(전처리 지시자 : # 문자로 시작되는 명령어 
ex. #define, #include)</p>
<p>#define 지시자는 매크로(macro)를 정의한다. 
(매크로 : 상수, 자주 사용하는 표현식 등을 의미하는 이름) </p>
<p>#include : 특정 파일을 열고, 그 내용물을 컴파일할 파일에 추가한다. </p>
<p>아래 그림은 컴파일 과정에서 전처리기의 역할을 보여준다.
<img src="https://images.velog.io/images/mi_pine/post/da8d81f8-4a64-408b-9999-68143a73aa4f/image.png" alt=""></p>
<p>전처리기는 주석은 띄어쓰기 문자 하나로 대체한다.</p>
<p>대부분의 C 컴파일러는 전처리기의 출력물을 볼 수 있기 때문에 오류를 찾을 때 유용하다. 
<br></p>
<hr>
<br>

<h3 id="📍-preprocessing-directives">📍 Preprocessing Directives</h3>
<p>🔎 <strong>전처리 지시자들</strong></p>
<p>1) 매크로 정의(macro definition) : ex. #define으로 매크로 정의
2) 파일 추가(file inclusion) : ex. #include로 파일 추가
3) 조건적 컴파일(conditional compilation) : ex. #if, #ifdef, ifndef, #elif, #else, #endif로 코드의 특정 부분을 추가하거나 제외</p>
<p>🔎 <strong>지시자들의 규칙</strong></p>
<p>1) #으로 시작한다.
2) 지시자와 토큰 사이에는 띄어쓰기가 임의의 개수만큼 들어갈 수 있다.</p>
<pre><code class="language-c">#    define         N     (100) (o)</code></pre>
<p>3) 하나의 지시자에서 다음줄로 넘기려면 \로 끝나야 한다.</p>
<pre><code class="language-c">#define DISK_CAPACITY (SIDES *    \
               TRACKS_PER_SIDE)</code></pre>
<p>4) 프로그램 어디서든 등장할 수 있다</p>
<br>

<hr>
<h3 id="📍-macro-definitions">📍 Macro Definitions</h3>
<p>매크로에는 단순(simple)매크로 뿐 아니라 매개변수(parameterized)매크로도 존재한다. </p>
<h4 id="📍-simple-macros">📍 Simple Macros</h4>
<p>🔎** simple macro (또는 object-like macro)**</p>
<pre><code class="language-c">#define identifier replacement-list</code></pre>
<p>전처리기는 식별자가 대체목록을 의미하게 됨을 표시, 파일에 식별자가 등장하면 전처리기는 대체목록으로 대체한다. </p>
<p>단순 매크로는 고유상수를 정의하기 위해 주로 사용된다. </p>
<pre><code class="language-c">#define TRUE (1)
#define FALSE (0)
#define PI (3.14159)
#define MEM_ERR (&quot;Error: not enough memory&quot;)</code></pre>
<p>🔎 <strong>#define의 장점</strong></p>
<p>1) 프로그램의 가독성, 수정성을 높여준다.
2) 일관성 없는 코드나 오타를 방지할 수 있다.</p>
<ul>
<li>#define의 추가적인 사용
조건적 컴파일 제어하기. 아래와 같은 코드로 디버깅 관련 출력을 담당하는 코드도 추가적으로 컴파일 할 수 있게 만들 수 있다. <pre><code class="language-c">#define DEBUG </code></pre>
</li>
</ul>
<p>C에서 매크로를 상수처럼 사용할 땐 이름을 전부 대문자로 작성하는 것이 관습이다. 
<br></p>
<hr>
<br>

<h4 id="📍-parameterized-macros">📍 Parameterized Macros</h4>
<p>🔎** parameterized macro (또는 function-like macro) **</p>
<pre><code class="language-c">#define identifier( X1, X2, ..., Xn ) replacement-list</code></pre>
<p>🔎** 매개변수 매크로(parameterized macro)의 이용 **</p>
<pre><code class="language-c">#define MAX(x,y) ((x) &gt; (y) ? (x) : (y))
#define IS_EVEN(n) ((n) % 2 == 0)

i = MAX(j+k, m-n);
if (IS_EVEN(i)) {
    ++i;
}</code></pre>
<p>위의 예시처럼 매개변수 매크로는 간단한 함수처럼 작동하기도 한다. </p>
<p>좀 더 예시를 살펴보면, </p>
<pre><code class="language-c">#define TOUPPER(c) (&#39;a&#39; &lt;= (c) &amp;&amp; (c) &lt;= &#39;Z&#39; ? (c) - &#39;a&#39; + &#39;A&#39; : (c)) 

#define getchar() getc(stdin)</code></pre>
<p>🔎** 매개변수 매크로(parameterized macro)의 장점 **</p>
<p>1) 프로그램이 조금 더 빨라진다. 함수 호출은 overhead를 필요로 할 수 있는데, 매크로는 그렇지 않다. 
2) 매크로는 일반적(generic)이다. 함수 매개변수와 달리 특정한 형을 가지고 있지 않다. </p>
<p>🔎** 매개변수 매크로(parameterized macro)의 단점 **</p>
<p>1) 컴파일된 코드가 길어진다. 
2) 입력변수의 형을 확인해주지 않는다. 
3) 매크로를 가리키는 포인터를 가질 수 없다. (함수는 함수 포인터가 존재) 
4) 매크로가 입력변수를 한 번 이상 평가할 수 있다. (함수는 한 번만 평가)</p>
<br>

<hr>
<br>

<h4 id="📍-the--operator">📍 The # Operator</h4>
<p>#연산자는 매크로 입력변수를 문자열 리터럴로 치환한다. </p>
<p>🔎** # 연산자 사용 **</p>
<pre><code class="language-c">#define PRINT_INT(n) printf(#n &quot; = %d\n&quot;, n)</code></pre>
<p>에서 n 대신 문자열 리터럴을 넣어준다. </p>
<pre><code class="language-c">PRINT_INT(i/j);</code></pre>
<p>따라서 위 코드는 아래와 같다. </p>
<pre><code class="language-c">printf(&quot;i/j=%d\n&quot;, i/j);</code></pre>
<br>

<hr>
<br>

<h4 id="📍-the--operator-1">📍 The ## Operator</h4>
<p>##연산자는 두 토큰을 하나의 토큰으로 붙여 준다. </p>
<p>🔎** ## 연산자 사용 **</p>
<pre><code class="language-c">#define MK_ID(n) i##n
int MK_ID(1);
int MK_ID(2);</code></pre>
<p>와 같이 작성해주면 전처리 이후에는 </p>
<pre><code>int i1; 
int i2;</code></pre><p>가 된다. </p>
<p>max 함수의 정의를 갖는 매크로를 작성하면, 일일이 만들지 않고 여러 형을 받도록 할 수 있다. </p>
<p>매크로로 두 개 이상의 max함수를 만들 수 없는데, ##연산자로 각 max마다 서로 다른 이름을 만들어 해결해줄 수 있다. </p>
<pre><code class="language-c">#define GENERIC_MAX(type) type type##_max(type x, type y) { return x &gt; y ? x : y; }</code></pre>
<p>만약 float형 변수를 사용한 max함수가 필요하면 아래와 같이 정의할 수 있다. </p>
<pre><code class="language-c">GENERIC_MAX(float)</code></pre>
<p>그럼 전처리기에 의해 아래와 같이 바뀐다</p>
<pre><code class="language-c">float float_max(float x, float y)
{
    return x &gt; y ? x : y;
}</code></pre>
<br>

<hr>
<br>


<h4 id="📍-generic-properties-of-macro">📍 Generic Properties of Macro</h4>
<p>🔎 ** 매크로에 적용되는 규칙 **</p>
<p>1) 매크로 대체 목록이 다른 매크로의 발동을 갖고 있을 수 있다.</p>
<pre><code class="language-c">#define PI (3.14159)
#define TWO_PI (2 * PI)</code></pre>
<p>2) 전처리기는 식별자, 문자형 상수, 문자열 리터럴의 일부인 매크로 이름을 무시한다. </p>
<pre><code class="language-c">#define SIZE (256)
int BUFFER_SIZE;</code></pre>
<p>위 코드의 경우 BUFFER_SIZE는 전처리의 영향을 받지 않는다.</p>
<p>3) 매크로의 정의는 보통 정의된 해당 파일 전체에 적용된다. </p>
<p>4) 똑같게 재정의를 하지 않는 이상 재정의는 불가능하다. </p>
<p>5) #undef 지시자에 의해 미정의 될 수도 있다. 
아래코드는 현재 정의된 매크로 N을 삭제한다. </p>
<pre><code class="language-c">#undef N</code></pre>
<br>

<hr>
<br>

<h4 id="📍-parentheses-in-macro-definitions">📍 Parentheses in Macro Definitions</h4>
<p>매크로에는 소괄호를 많이 써주는것이 좋다. </p>
<p>1) 대체목록에 연산자가 포함되어 있으면 반드시 대체목록을 소괄호로 감싸주어야 한다. </p>
<pre><code class="language-c">#define TWO_PI (2 * 3.14159)</code></pre>
<p>2) 매크로가 매개변수가 있을 경우 매개변수를 모두 괄호로 감싸주어야 한다. </p>
<pre><code class="language-c">#define SCALE(x) ((x)) * 10)</code></pre>
<br>

<hr>
<br>

<h4 id="📍-creating-longer-macros">📍 Creating Longer Macros</h4>
<p>쉼표를 이용해 대체목록을 표현식의 연속체로 만들 수 있다. </p>
<p>🔎 ** 문자열을 읽은 다음 출력을 해주는 매크로 - (1) **</p>
<pre><code class="language-c">#define ECHO(s) (gets(s), puts(s))</code></pre>
<p>이렇게 해서 ECHO를 마치 함수인 것처럼 사용할 수 있다.</p>
<pre><code>ECHO(str); /* (gets(str), puts(str)); 이 된다 */</code></pre><p>그러나 쉼표로는 표현식이 아니라 구문을 연결시킬 수는 없다. 구문을 연결시키려면 do 루프를 이용해야 한다. </p>
<p>🔎 ** 문자열을 읽은 다음 출력을 해주는 매크로 - (2) **</p>
<pre><code class="language-c">
#define ECHO(s)                \
            do{        \
            gets(s);    \
            puts(s);    \
            } while (0)</code></pre>
<p>세미콜론이 없다는 점을 눈여겨 보자. </p>
<p>ECHO를 사용할 땐 반드시 세미콜론을 이용해 do문을 완성시켜주어야 한다.</p>
<pre><code class="language-c">ECHO(str); /* do { gets(str); puts(str); } while (0); 이 된다*/</code></pre>
<br>

<hr>
<br>

<h4 id="📍-predefined-macros">📍 Predefined Macros</h4>
<p>🔎 ** C에서 사전정의된 매크로들 **</p>
<table>
<thead>
<tr>
<th align="center"><strong>이름</strong></th>
<th align="center"><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">_<em>LINE_</em></td>
<td align="center">컴파일하는 파일의 줄 번호</td>
</tr>
<tr>
<td align="center">_<em>FILE_</em></td>
<td align="center">컴파일하는 파일 이름</td>
</tr>
<tr>
<td align="center">_<em>DATE_</em></td>
<td align="center">컴파일 날짜 (&quot;Mmm dd yyyy&quot;의 형식)</td>
</tr>
<tr>
<td align="center">_<em>TIME_</em></td>
<td align="center">컴파일 시간 (&quot;hh:mm:ss&quot;의 형식)</td>
</tr>
<tr>
<td align="center">_<em>STDC_</em></td>
<td align="center">만약 컴파일러가 C표준(C89 혹은 C99)일시 1임</td>
</tr>
</tbody></table>
<br>

<hr>
<br>

<h4 id="📍-additional-predefined-macros-in-c99">📍 Additional Predefined Macros in C99</h4>
<p>🔎 ** C에서 사전정의된 매크로들 **</p>
<table>
<thead>
<tr>
<th align="center"><strong>이름</strong></th>
<th align="center"><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">_<em>STDC_HOSTED_</em></td>
<td align="center">호스트 시행이라면 1, 독립적이라면 0</td>
</tr>
<tr>
<td align="center">_<em>STDC_VERSION_</em></td>
<td align="center">지원하는 C의 표준 버전</td>
</tr>
<tr>
<td align="center">_<em>STDC_</em></td>
<td align="center">IEC 60559 고정소수점 산술을 지원할 시 1</td>
</tr>
<tr>
<td align="center">_<em>TIME_</em></td>
<td align="center">IEC 60559 복소수 산술을 지원할 시 1</td>
</tr>
<tr>
<td align="center">_<em>STDC_</em></td>
<td align="center">wchar_t의 값이 특정 년도와 월이 ISO 10646 표준을 만족할 시 yyyymmL</td>
</tr>
</tbody></table>
<br>

<hr>
<br>

<h4 id="📍-empty-macro-arguments">📍 Empty Macro Arguments</h4>
<p>C99에서는 매크로의 모든 입력변수가 비어있을 수 있다. 이 경우 쉼표를 이용하여 어떤 입력변수가 생략되었는지 표시한다. </p>
<pre><code class="language-c">#define ADD(x,y) (x+y)</code></pre>
<p>위와 같이 선언한 경우</p>
<pre><code class="language-c">i = ADD(, k); </code></pre>
<p>위의 구문은 다음과 같다</p>
<pre><code class="language-c">i = ( + k); </code></pre>
<p>빈 입력변수가 #의 피연산자의 경우에는 &quot;&quot;(빈 문자열)이 된다. </p>
<pre><code class="language-c">#define MK_STR(x) #x
...
char empty_string[] = MK_STR();</code></pre>
<p>위의 구문은 전처리기에 의해 아래와 같이 바뀐다. </p>
<pre><code class="language-c">char empty_string[] = &quot;&quot;;</code></pre>
<p>빈 입력변수가 ##의 피연산자의 경우에는 위치 표시자 토큰으로 대체되어 존재하는 입력변수와 만나면 사라진다.</p>
<pre><code class="language-c">#define JOIN(x, y, z) x##y##z
...
int JOIN(a, b, c);
int JOIN(a, b, );
int JOIN(a, , c);
int JOIN(, , c);</code></pre>
<p>위의 구문은 전처리기에 의해 아래와 같이 바뀐다. </p>
<pre><code class="language-c">int abc;
int ab;
int ac;
int c;</code></pre>
<br>

<hr>
<br>

<h4 id="📍-macris-with-a-variable-number-of-arguments-가변-길이-입력변수-매크로">📍 Macris with a Variable Number of Arguments (가변 길이 입력변수 매크로)</h4>
<p>C99에서는 무제한의 입력변수를 받을 수 있다. 이 기능의 주된 목적은 printf나 scanf와 같이 가변 길이 입력변수들을 받는 함수에 사용해주기 위해서 이다.</p>
<pre><code class="language-c">#define TEST(condition, ...) ((condition) ? \
    printf(&quot;Passed test: %s\n&quot;, #condition) : \
    printf(__VA_ARGS__))</code></pre>
<p>... (생략부호(ellipsis)) : 매크로의 매개변수 목록에서 마지막에 위치하며, 일반적인 매개변수가 존재할 시 생략 부호 이전에 나온다</p>
<p>_<em>VA_ARGS_</em> : 가변 길이 입력변수를 갖는 매크로의 대체목록에서만 나올 수 있는 특수한 식별자이다. 생략부호에 해당하는 모든 입력변수를 의미한다</p>
<p>TEST : 최소한 두 개의 입력변수를 필요로 한다. 
첫번재 입력변수는 condition 매개변수에 대응하고, 나머지 입력변수들은 생략 부호에 대응한다. </p>
<p>🔎 ** TEST 매크로의 실제 용법 **</p>
<pre><code class="language-c">TEST(voltage &lt;= max_voltage, &quot;Voltage %d exceeds %d\n&quot;, voltage, max_voltage);</code></pre>
<p>만약 voltage &lt;= max_voltage라면 
<strong>Passed test : voltage &lt;= max_voltage</strong>를 출력,
voltage &gt; max_voltage 라면
*<em>Voltage 125 exceeds 120 *</em>을 출력할 것이다.</p>
<br>

<hr>
<br>

<h4 id="📍-the-_func_-identifier">📍 The _<em>func_</em> Identifier</h4>
<p>_<em>func_</em> 식별자는 현재 실행되는 함수의 이름을 저장하고 있는 문자열 변수처럼 행동한다. </p>
<pre><code class="language-c">static const char \__func__[] = &quot;function-name&quot;;</code></pre>
<p>이러한 식별자가 존재하기 때문에 다음과 같은 디버깅 매크로를 작성해줄 수 있다.</p>
<pre><code class="language-c">#define FUNCTION_CALLED() printf(&quot;%s가 호출됨\n&quot;, __func__);
#define FUNCTION_RETURNS() printf(&quot;%s가 반환함\n&quot;, __func__);

void f(void)
{
    FUNCTION_CALLED(); /* &quot;f가 호출됨&quot;을 출력함 */
    FUNCTION_RETURNS(); /* &quot;f가 반환함&quot;을 출력함 */
}</code></pre>
<br>

<hr>
<br>

<h3 id="📍-conditional-compilation">📍 Conditional Compilation</h3>
<h4 id="📍-the-if-and-endif-directives">📍 The #if and #endif Directives</h4>
<p>#if - #endif 짝을 이용하면 코드상에서는 남아있더라도 상황에 따라 컴파일 하지 않을 수 있다. </p>
<p>아래 코드의 경우 DEBUG 값이 1이므로 실행되며, DEBUG값을 0으로 바꾸면 컴파일되지 않는다. </p>
<pre><code class="language-c">#define DEBUG 1

#if DEBUG
printf(&quot;i의 값 : %d\n&quot;, i);
printf(&quot;j의 값 : %d\n&quot;, j);
#endif </code></pre>
<br>

<hr>
<br>
#### 📍 The defined Operator

<p>해당 식별자가 정의된 매크로일 경우 defined는 값 1을 내보내고, 아니라면 0을 내보낸다. 
즉 해당 식별자가 매크로로서 정의되었는지를 판단하는 연산자이다.</p>
<pre><code class="language-c">#if define(DEBUG)
…
#endif</code></pre>
<p>만약 DEBUG가 매크로로서 정의가 되어있다면 #if ~ #endif 사이 문이 실행된다. </p>
<br>

<hr>
<br>

<h4 id="📍-the-ifdef-and-ifndef-directives">📍 The #ifdef and #ifndef Directives</h4>
<p>#ifdef 지시자는 식별자가 현재 매크로로 정의되어있는지 여부를 확인한다. </p>
<pre><code class="language-c">#ifdef identifier
...
#endif</code></pre>
<p>식별자가 정의되어 있다면 #ifdef ~ #endif 사이 문이 실행된다. 이는 앞서 본 #if와 defined 연산자를 이용해서도 할 수 있다. </p>
<p>#ifndef는 #ifdef와는 반대로 식별자가 현재 매크로에 정의되어있지 않았는지의 여부를 확인한다.</p>
<br>

<hr>
<br>

<h4 id="📍-the-elif-and-else-directives">📍 The #elif and #else Directives</h4>
<p>#if, #ifdef, #ifndef 등과 더불어 #elif, #else를 사용하여 여러 조건들을 판별해줄 수 있다. </p>
<br>

<hr>
<br>

<h4 id="📍-use-of-conditional-compilation">📍 Use of Conditional Compilation</h4>
<p>🔎 여러 기계나 운영체제와 호환이 되는 프로그램을 작성할 때 </p>
<pre><code class="language-c">#if defined(WIN32)
…
#elif defined(MAC_OS)
…
#elif defined(LINUX)
…
#endif</code></pre>
<p>이를 이용해서 어느 운영체제에서 돌아가는지 표시를 해줄 수 있다. 
<br></p>
<p>🔎 다양한 컴파일러에서 컴파일할 수 있는 프로그램을 작성할 때 </p>
<p>서로 다른 컴파일러들은 서로 다른 C버전을 인식할수 있기 때문에 오래된 컴파일을 사용해야 할 때는 _<em>STDC_</em> 매크로 등을 이용해야 한다. </p>
<pre><code class="language-c">#if __STDC__
Function prototypes
#else
Old-style function declarations
#endif</code></pre>
<p>🔎 매크로에 기본 정의를 내려줄 때 </p>
<p>조건부 컴파일은 매크로가 현재 정의되었는지를 판단하고 그에 따른 행동을 해줄 수 있다. 아래 코드는 BUFFER_SIZE가 정의되지 않았다면 정의를 한다. </p>
<pre><code class="language-c">#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif</code></pre>
<p>🔎 일시적으로 주석을 포함한 코드를 없앨 때 </p>
<pre><code class="language-c">#if 0
Lines containing comments 
#endif</code></pre>
<br>

<hr>
<br>

<h3 id="📍-miscellaneous-directives">📍 Miscellaneous Directives</h3>
<p>#error, #line, #pragma 지시자</p>
<h4 id="📍-the-error-directive">📍 The #error Directive</h4>
<pre><code class="language-c">#error message</code></pre>
<p>전처리기는 #error 지시자를 만나면 message를 반드시 포함하는 오류 문구를 출력한다. 오류 문구의 정확한 서식은 컴파일러마다 다르다. </p>
<p>🔎 #error 사용 예시</p>
<pre><code class="language-c">#if defined(WIN32)
…
#elif defined(MAC_OS)
…
#elif defined(LINUX)
…
#else
#error 특정된 운영체제가 없습니다
#endif</code></pre>
<br>

<hr>
<br>

<h4 id="📍-the-line-directive">📍 The #line Directive</h4>
<p>#line 지시자는 프로그램의 줄에 번호를 부여하는 법을 바꿀 때 사용한다. 또한 컴파일러가 다른 이름의 파일을 읽고 있도록 착각하게 만들 수 있다. </p>
<p>#line은 두 가지 서식을 갖는다.</p>
<p>1) #line 지시자 (1번 서식)
#line n</p>
<p>이 지시자는 프로그램에서 다음 줄들이 n, n+1, n+2 순으로 번호가 변하게 만든다. </p>
<p>2) #line 지시자 (2번 서식)
#line n &quot;file&quot;</p>
<p>지시자 이후에 나온 줄들은 &quot;file&quot;(파일명)을 갖는 파일에서 온 것으로 취급한다. </p>
<p>🔎 #line 사용 예시</p>
<pre><code class="language-c">#line 10 &quot;bar.c&quot;</code></pre>
<p>컴파일러가 foo.c의 5번 줄에서 오류를 탐지한 경우 오류 문구는 foo.c파일의 5번째 줄이 아닌 bar.c 파일의 13번째 줄에서 오류가 났다고 인식한다. </p>
<p>13번째 줄인 이유는 지시자가 foo.c의 1번 줄을 차지하여 foo.c가 2번 줄부터 시작하기 때문이다. </p>
<p>#line 지시자는 주로 C 코드를 출력해주는 프로그램에서 사용한다. 제일 대표적인 예시로는 yacc(컴파일러의 컴파일러)가 있다. </p>
<br>

<hr>
<br>

<h4 id="📍-the-pragma-directive">📍 The #pragma Directive</h4>
<p>#pragma 지시자는 컴파일러가 특정 행동을 하도록 부탁하게 만드는 방법이다. </p>
<h4 id="📍-the-_pragma-directive">📍 The _Pragma Directive</h4>
<p>C99는 #pragma 지시자와 섞어 쓸 수 있는 _Pragma 연산자를 제공한다. 
<br></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[📗13장 Strings | Exercises and  Programming Projects]]></title>
            <link>https://velog.io/@mi_pine/13%EC%9E%A5-Strings-Exercises-and-Programming-Projects</link>
            <guid>https://velog.io/@mi_pine/13%EC%9E%A5-Strings-Exercises-and-Programming-Projects</guid>
            <pubDate>Tue, 18 Jan 2022 11:00:12 GMT</pubDate>
            <description><![CDATA[<p>C Programming, A Modern Approach - K.N.KING 의 Exercises와 Programming Projects를 푼 내용입니다. </p>
<h3 id="📍-exercises">📍 Exercises</h3>
<ol>
<li>(b) %c는 char을 받는다. &quot;\n&quot;은 문자열로 취급됨 
(c) %s는 문자열을 받는다. &#39;\n&#39;은 문자(char) 취급됨
(e) printf 함수는 문자열을 받는다.
(h) putchar함수는 문자(char)을 받는다.
(i) puts함수는 문자열을 받는다. 
<span style="color: rgba(20,60,180,1)">(j) puts는 문자열을 쓴 후 개행문자도 추가로 출력해준다
따라서 개행이 2번 된다.</span><br></li>
<li>(a) putchar은 char값을 받는다.
(d) puts는 포인터(여기서는 char*)을 받는다. <br></li>
<li>(i) 12
(s) abc34
(j) 56<br></li>
<li><pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;ctype.h&gt; // isspace 사용
</code></pre>
</li>
</ol>
<p>int read_line(char str[], int n)
{
    int ch;
    int i = 0;</p>
<pre><code>while (isspace(getchar()));

while ((ch = getchar()) != &#39;\n&#39; &amp;&amp; !isspace(ch)) {
    if (i &lt; n) {
        str[i++] = ch;
    }
}
str[i] = &#39;\0&#39;;
return i;</code></pre><p>}</p>
<pre><code>

5. 
~~~ c
(a) 
void capitalize(char s[])
{
    for (int i = 0; s[i] == &#39;\n&#39;; i++) {
        toupper(s[i]);
    }
}</code></pre><pre><code class="language-c">(b) 
void capitalize(char *s)
{
    for (; *s == &#39;\n&#39;; s++) {
        isupper(*s);
    }
}</code></pre>
<p>6.</p>
<pre><code class="language-c">void censor(char *s)
{
    for (; *s == &#39;\n&#39;; s++) {
        if (*s == &#39;f&#39; &amp;&amp; *(s + 1) == &#39;o&#39; &amp;&amp; *(s + 2) == &#39;o&#39;) {
            *s = *(s+1) = *(s+2) = &#39;x&#39;;
        }
    }
}</code></pre>
<ol start="7">
<li><p>(d) </p>
</li>
<li><p>tired-or-wired?\0</p>
</li>
<li><p>computers\0</p>
</li>
</ol>
<p>10.
<span style="color: rgba(20,60,180,1)"> q가 가리키는 문자열은 함수 범위 밖에서는 접근할 수 없다. </span></p>
<p>11.</p>
<pre><code class="language-c">int strcmp(char* str1, char* str2)
{
    while (*str1 == *str2) {
        if (*str1 == &#39;\0&#39;) {
            return 0;
        }
        str1++; 
        str2++;
    }
    return *str1 - *str2;
}</code></pre>
<p>12.</p>
<pre><code class="language-c">void get_extension(const char* file_name, char* extension)
{
    while (*file_name) {
        if (*file_name++ == &#39;.&#39;) {
            strcpy(extension, file_name);
        }
    }
}</code></pre>
<ol start="13">
<li><pre><code class="language-c">void build_index_url(const char* domain, char* index_url)
{
strcpy(index_url, &quot;http://www&quot;);
strcat(index_url, domain);
strcat(index_url, &quot;/index.html&quot;);
}</code></pre>
</li>
<li><p>Grinch</p>
</li>
<li><p>(a) 3 (b) 2 (c)   </p>
</li>
</ol>
<p>16.</p>
<pre><code class="language-c">int count_space(const char* s)
{
    int count = 0;

    while (*s) {
        if (*s == &#39; &#39;) {
            count++; 
            s++;
        }
    }

    return count; 
}</code></pre>
<p>17.</p>
<pre><code class="language-c">bool text_extension(const char* file_name, const char* extension)
{
    while (*file_name != &#39;.&#39;) {
        file_name++;
    }
    file_name++;

    while (*file_name != &#39;\0&#39; &amp;&amp; *extension != &#39;\0&#39;)
    {
        if (toupper(*extension++) != toupper(*file_name++)) {
            return false;
        }
    }
    return true;
}</code></pre>
<p>18.</p>
<pre><code class="language-c">void remove_filename(char* url)
{
    while(*url++);
    while (*url-- != &#39;/&#39;);
    url++;
    *url = &#39;\0&#39;;
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>