<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tendou Hayase&apos;s Blog</title><description>Blog</description><link>https://blog.ushiohayase.com/</link><language>ko</language><item><title>CUDA의 메모리 구조와 명령어 실행 단위</title><link>https://blog.ushiohayase.com/posts/cuda-memory-structure/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/cuda-memory-structure/</guid><description>CUDA에서 논리적으로 실행되는 방식을 알아보고 그것이 NVIDIA GPU에서 어떻게 물리적으로 실행되는지 알아보기</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;CUDA에서의 논리적 실행단위&lt;/h2&gt;
&lt;p&gt;CUDA C++(.cu, .cuh)에서는 커널 코드는 함수 앞에 &lt;code&gt;__global__&lt;/code&gt;붙여 CPU에서 GPU에 해당 함수를 실행하게 할 수 있습니다.
이 커널 함수의 반환형은 &lt;code&gt;void&lt;/code&gt; 형이여야 합니다.
그렇게 만든 커널 함수는 &lt;code&gt;func&amp;lt;&amp;lt;&amp;lt;grid_size, block_size&amp;gt;&amp;gt;&amp;gt;(args)&lt;/code&gt; 형식으로 호출할 수 있습니다.
만약 CUDA 스트림을 기본 스트림이 아니라 다른 스트림을 사용하고싶다면 &lt;code&gt;func&amp;lt;&amp;lt;&amp;lt;grid_size, block_size, cuda_stream&amp;gt;&amp;gt;&amp;gt;(args)&lt;/code&gt; 형식으로 호출할 수 있습니다.
현재 NVIDIA GPU의 스레드 블록은 1024개의 스레드를 포함할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;그리드와 블록&lt;/h3&gt;
&lt;p&gt;CUDA 코드에서 그리드는 전체 디바이스라 할 수 있고 그리드의 크기만큼 블록이 존재합니다.
CUDA에서 블록은 스레드의 집합이라고 할 수 있고 블록의 크기만큼 스레드가 동일한 커널을 공유하며 실행됩니다.
동일한 블록 내에서 스레드들은 &lt;em&gt;shared memory&lt;/em&gt;를 공유합니다.&lt;/p&gt;
&lt;h2&gt;NVIDIA GPU에서의 물리적 실행 단위&lt;/h2&gt;
&lt;p&gt;위와 같이 생성된 코드는 실제 디바이스에서는 SM(Streaming Multiprocessor)에서 Warp(32개) 단위로 실행됩니다.&lt;/p&gt;
&lt;h3&gt;SM&lt;/h3&gt;
&lt;p&gt;SM 내부는 여러가지 부품으로 구성되어있습니다.&lt;/p&gt;
&lt;p&gt;메모리 관련으로는 &lt;code&gt;레지스터 파일&lt;/code&gt;, &lt;code&gt;공유 메모리&lt;/code&gt;, &lt;code&gt;상수 캐시&lt;/code&gt;, &lt;code&gt;텍스처 캐시&lt;/code&gt;, &lt;code&gt;L1 I-Cache(명령어 캐시)&lt;/code&gt;, &lt;code&gt;L1 데이터 캐시&lt;/code&gt;, &lt;code&gt;LD/ST(Load/Store Unit)&lt;/code&gt;, &lt;code&gt;TMA(Tensor Memory Accelerator)&lt;/code&gt;가 존재합니다.&lt;/p&gt;
&lt;p&gt;연산 관련으로는 &lt;code&gt;FP32 유닛&lt;/code&gt;,&lt;code&gt;INT32 유닛&lt;/code&gt;,&lt;code&gt;FP64 유닛&lt;/code&gt;,&lt;code&gt;텐서 코어&lt;/code&gt;,&lt;code&gt;SFU(Special Function Unit)&lt;/code&gt;,&lt;code&gt;RT 코어&lt;/code&gt;,&lt;code&gt;TMU(Texture Mapping Unit; 텍스처 매핑 유닛)&lt;/code&gt;가 있습니다.&lt;/p&gt;
&lt;p&gt;스케줄링 및 제어 관련으로는 &lt;code&gt;워프 스케줄러&lt;/code&gt;,&lt;code&gt;디스패치 유닛&lt;/code&gt;,&lt;code&gt;pc(program counter)&lt;/code&gt;,&lt;code&gt;Active Mask(활성 마스크)&lt;/code&gt;,&lt;code&gt;분기 스택(Branch Stack)&lt;/code&gt;,&lt;code&gt;Register Base Pointer(레지스터 베이스 포인터)&lt;/code&gt;,&lt;code&gt;Scoreboard(스코어보드)&lt;/code&gt;,&lt;code&gt;Operand Collector(오퍼랜드 콜렉터)&lt;/code&gt;,&lt;code&gt;배리어 및 동기화 하드웨어&lt;/code&gt;이 있습니다.&lt;/p&gt;
&lt;h3&gt;SM 내부의 메모리 관련 부품 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;레지스터 파일 : SM 내의 가장 대역폭이 높은 SRAM 영역으로, 각 활성 스레드의 변수들을 저장합니다. 만약 블록 내 레지스터 사용량이 SM 내부 레지스터 파일에 존재하는 레지스터 개수를 넘을 경우 데이터가 로컬 메모리(Local Memory)로 넘어가는 &lt;strong&gt;레지스터 스필링(Register Spilling)&lt;/strong&gt; 현상이 발생합니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;레지스터 뱅크 : 레지스터 파일은 레지스터 뱅크 구조로 되어있어 워프내 32개의 스레드에 오퍼랜드를 한사이클만에 공급할 수 있습니다.&lt;/li&gt;
&lt;li&gt;레지스터 뱅크 충돌 : 만약 워프 내의 스레드가 &lt;em&gt;같은 뱅크에 존재하는 레지스터&lt;/em&gt;에 접근한다면 각 &lt;em&gt;뱅크는 한사이클에 1개&lt;/em&gt;만 오퍼랜드를 줄 수 있어 지연이 발생합니다. 하지만 일반적으로 컴파일러가 이를 피하여 레지스터를 할당하기에 대부분의 상황에서는 신경쓸 필요가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;공유 메모리 : 블록 내 공유 메모리 또한 레지스터 파일과 같이 뱅크로 이루어져있으며 만약 한 워프에서 같은 공유 메모리 뱅크에 접근한다면 요청이 직렬화되며 지연이 발생합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;상수 캐시 : 읽기 전용 글로벌 메모리 영역의 데이터를 캐싱하며, 동일 주소 참조 시 브로드캐스트를 통해 모든 스레드에 동시에 공급합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;텍스처 캐시 : 공간적 2D/3D 지역성이 높은 데이터 접근에 최적화된 읽기 전용 캐시입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;L1 I-Cache(명령어 캐시) : SM 레벨에서 커널의 전체 바이너리 명령어를 캐싱하며, 서브 코어의 L0 I-Cache로 명령어를 공급합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;L1 데이터 캐시 : 글로벌 메모리 데이터의 지역성을 활용해 캐싱합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Volta 이후 아키텍처에서는 &lt;em&gt;L1 데이터 캐시, 공유 메모리, 텍스처 캐시&lt;/em&gt;가 128KB SRAM으로 융합되었습니다. &lt;code&gt;cudaFuncSetCacheConfig(const void* func, cudaFuncCache cacheConfig)&lt;/code&gt;와&lt;code&gt;cudaFuncCachePreferNone, cudaFuncCachePreferShared, cudaFuncCachePreferL1&lt;/code&gt;과 같은 열거형 변수를 활용해 L1 데이터 캐시와 공유 메모리 중 어디에 더 메모리를 많이 할당할지 우선순위를 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LD/ST(Load/Store) Unit : 워프 단위의 메모리 요청 주소를 계산하고, 연속된 접근을 묶어 단일 트랜잭션으로 처리하는 병합된 메모리 접근(Coalesced  Memory Access)를 하드웨어적으로 수행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;L0 I-Cache(L0 명령어 캐시) : 통합 L1 명령어 캐시와 별개로, SM 내부를 구성하는 4개의 독립된 서브 코어 단위마다 물리적으로 내장된 초고속 명령어 버퍼입니다. 거대한 L1 캐시에 동시에 접근할 때 발생하는 대역폭 병목을 해소하고 명령어 인출 지연을 최소화합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SM 내부의 연산 관련 유닛 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;FP32 유닛 : 단정밀도 부동소수점 연산을 전담합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FP64 유닛 : 배정밀도 부동소수점 연산을 전담합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;INT32 유닛 : 정수 계산, 메모리 주소 계산, 제어 흐름 평가를 담당하며 FP32 유닛과 독립적으로 작동하여 동시 병행 실행이 가능합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SFU(Special Function Unit; 초월함수 유닛) : 삼각함수, 지수함수, 제곱근 등의 복잡한 초월함수를 하드웨어 레벨에서 고속으로 근사 연산합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;텐서 코어(Tensor Core) : 딥러닝과 행렬 연산에 특화되어, 1사이클 내의 혼합 정밀도 기반의 행렬 곱셈-누산(MMA)을 타일 단위로 일괄 처리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RT 코어(Ray Tracing Core) : BVH(Bounding Volume Hierarchy) 트리 순회와 광선-삼각형 교차 테스트를 전용 하드웨어 로직으로 처리하여 SM의 기존 범용 연산 유닛에 가해지는 부하를 제거합니다. 워프가 RT 코어에 명령을 하달하면, RT 코어가 독립적으로 메모리에서 트리를 읽어 연산하고 최종 결과만 반환하므로 그동안 워프는 다른 연산을 병행할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TMU(Texture Mapping Unit; 텍스처 매핑 유닛) : 컴퓨터 그래픽스 및 GPGPU 환경에서 정규화된 텍스처 좌표 입력 시, 소프트웨어적 분기 없이 메모리 경계 처리와 다중 샘플링을 통한 선형 보간 연산을 하드웨어적으로 수행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SM 내부의 스케줄링 및 제어 관련 부품 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;워프 스케줄러(Warp Scheduler) : 매 클럭 사이클마다 SM에 할당된 활성 워프들의 메모리 대기 및 데이터 종속성 상태를 평가하여, 즉시 실행 가능한 워프를 파이프라인에 투입합니다. 이를 통해 메모리 접근으로 인한 유휴 시간을 숨기는 지연 시간 은닉을 달성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;디스패치 유닛(Dispatch Unit) : 워프 스케줄러가 선택한 워프의 명령어를 디코딩하고, 실제 연산을 수행할 하부 실행 유닛(FP32, INT32 등)으로 명령어를 발행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PC(Program Counter) : 실행할 명령어의 메모리 주소를 가리키는 하드웨어 레지스터입니다. Volta 아키텍처 이후 도입된 독립 스레드 스케줄링(Independent Thread Scheduling) 체제에서는 워프 내 32개의 스레드가 각각 독립적인 PC와 호출 스택을 물리적으로 보유하여, 스레드 간의 세밀한 동기화와 병행 실행을 지원합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Mask(활성 마스크) : 32비트 상태 레지스터로, 워프 내 32개 스레드 중 현재 사이클의 명령어 실행에 참여하여 연산 결과를 레지스터나 메모리에 기록할 대상을 결정합니다. 분기문으로 인해 &lt;code&gt;Warp Divergence&lt;/code&gt;가 발생하거나 조기 종료된 스레드의 비트는 0으로 비활성화됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;분기 스택(Branch Stack / SIMT Stack) : 조건 분기로 인한 워프 다이버전스 발생 시, 지연된 분기 경로의 PC와 활성 마스크 상태를 push하고 병합 지점에서 pop하여 실행 흐름을 통제하는 LIFO 구조의 하드웨어 내부 스택입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register Base Pointer(레지스터 베이스 포인터) : 워프가 스케줄링될 때 할당받은 거대한 물리적 레지스터 파일 내의 연속된 주소 블록 시작점을 가리킵니다. 논리적 레지스터 주소를 물리적 주소로 실시간 매핑하며, 메모리 대기 시 이 포인터의 참조 대상만 교체하는 방식으로 &lt;strong&gt;무비용 컨텍스트 스위칭&lt;/strong&gt;을 가능하게 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scoreboard(스코어보드) : 발행 예정인 명령어가 사용할 레지스터의 데이터 종속을 비트 형태로 추적합니다. &lt;strong&gt;데이터 해저드&lt;/strong&gt;를 방지하고 명령어 수준 병렬성을 하드웨어 레벨에서 관리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Operand Collector(오퍼랜드 콜렉터) : 레지스터 파일과 연산 유닛 사이에 위치하여, 레지스터 뱅크 충돌이 발생했을 때 여러 사이클에 걸쳐 피연산자들을 읽어들여 내부 버퍼에 모은 뒤 연산 유닛으로 한 번에 디스패치하여 파이프라인 버블을 방지하는 중재 유닛입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;배리어 및 동기화 하드웨어 : 비동기 연산의 일관성을 제어하기 위한 하드웨어 장치입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MBarrier : 공유 메모리에 상주하며, 비동기 연산의 완료 상태를 &apos;메모리 트랜잭션 바이트 수&apos;의 도달을 기준으로 추적하고 동기화합니다.&lt;/li&gt;
&lt;li&gt;cp.async : 레지스터 파일을 우회하여 글로벌 메모리에서 공유 메모리로 데이터를 직접 라우팅하는 비동기 메모리 복사 파이프라인입니다.&lt;/li&gt;
&lt;li&gt;DSMEM(Distributed Shared Memory) : 클러스터 내의 여러 SM이 고속 네트워크를 통해 서로의 물리적 공유 메모리 공간에 직접 접근하고 동기화(클러스터 배리어)할 수 있도록 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;논리적 계층과 물리적 하드웨어의 매핑 구조&lt;/h2&gt;
&lt;p&gt;작성된 CUDA C++ 코드의 논리적인 단위(그리드, 블록, 스레드)는 커널이 실행될 때 디바이스의 물리적 하드웨어에 다음과 같이 1:1로 매핑되어 작동합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그리드(Grid) $\rightarrow$ GPU 디바이스 : 하나의 커널(그리드)은 전체 GPU 디바이스 단위로 실행됩니다.&lt;/li&gt;
&lt;li&gt;블록(Block) $\rightarrow$ SM (Streaming Multiprocessor) : 논리적으로 묶인 하나의 블록은 물리적으로 단일 SM에 통째로 할당(Dispatch)됩니다.
한 번 특정 SM에 할당된 블록은 실행이 완전히 끝날 때까지 다른 SM으로 이동하지 않으며, 해당 SM 내부의 공유 메모리와 하드웨어 자원을 독점적으로 사용합니다.&lt;/li&gt;
&lt;li&gt;스레드 $\rightarrow$ 워프 : 블록 내의 스레드들은 SM에서 개별적으로 스케줄링되지 않습니다. 32개의 연속된 스레드씩 묶여 워프라는 물리적 실행 단위로 나뉩니다. 워프 단위로 하드웨어 명령어가 인출되고 실행되며, 이를 기반으로 SIMT가 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;글로벌 메모리 접근과 성능 최적화 기법&lt;/h2&gt;
&lt;p&gt;SM 내부에 존재하는 초고속 온칩 메모리(레지스터, 공유 메모리 등)와 달리, 디바이스의 VRAM인 글로벌 메모리(Global Memory)에 접근할 때는 워프 단위의 물리적 동작 방식이 성능에 결정적인 영향을 미칩니다.&lt;/p&gt;
&lt;h3&gt;병합된 메모리 접근 (Coalesced Memory Access)&lt;/h3&gt;
&lt;p&gt;워프 내 32개의 스레드가 글로벌 메모리의 데이터를 읽거나 쓸 때, 하드웨어의 LD/ST 유닛은 스레드들의 메모리 요청 주소를 분석합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 병합 : 만약 32개의 스레드가 요청하는 메모리 주소가 연속적이고 올바르게 &lt;em&gt;정렬&lt;/em&gt;되어 있다면, 메모리 컨트롤러는 이를 개별적으로 처리하지 않고 32바이트, 64바이트, 또는 128바이트 단위의 최소화된 거대한 메모리 트랜잭션으로 병합하여 한 번에 데이터를 가져옵니다.&lt;/li&gt;
&lt;li&gt;비병합 접근(Uncoalesced Access)의 페널티 : 만약 스레드들이 흩어진 주소를 요청하거나, 분기문으로 인해 접근이 불규칙해지면 하드웨어는 데이터를 가져오기 위해 수많은 작은 메모리 트랜잭션을 발행해야 합니다. 이는 요구 데이터 대비 버스 전송량을 급증시켜 막대한 대역폭 낭비를 초래하며, 커널 성능을 수십 배까지 저하시키는 주요 원인이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;병합된 메모리 접근을 위한 데이터 레이아웃 (AoS vs SoA)&lt;/h3&gt;
&lt;p&gt;하드웨어 LD/ST 유닛이 32바이트/128바이트 단위의 단일 메모리 트랜잭션으로 데이터를 가져오기(Coalescing) 위해서는 메모리상에 데이터가 배치된 구조가 매우 중요합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AoS (Array of Structures) : &lt;code&gt;struct { float x, y, z; } point[1024];&lt;/code&gt;와 같이 객체 지향 프로그래밍에서 흔히 사용하는 방식입니다. 연속된 스레드(Thread 0, 1, 2...)가 각자의 &lt;code&gt;point[i].x&lt;/code&gt;에 접근할 때, 메모리상에서 &lt;code&gt;x&lt;/code&gt; 값들은 &lt;code&gt;y, z&lt;/code&gt; 크기만큼 떨어져 존재하게 됩니다. 이 불연속적인 스트라이드 접근 패턴은 비병합 접근을 유발하여 메모리 대역폭을 크게 낭비합니다.&lt;/li&gt;
&lt;li&gt;SoA (Structure of Arrays) : &lt;code&gt;struct { float x[1024], y[1024], z[1024]; } points;&lt;/code&gt;와 같이 동일한 속성들을 연속된 배열로 모아두는 방식입니다. 워프 내 32개의 스레드가 동시에 &lt;code&gt;points.x[threadIdx.x]&lt;/code&gt;에 접근할 때, 요구하는 메모리 주소가 완벽하게 연속적이므로 LD/ST 유닛이 단 한 번의 128바이트 트랜잭션으로 데이터를 병합하여 가져올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SM 외부의 디바이스 메모리 계층: L2 캐시&lt;/h3&gt;
&lt;p&gt;SM 내부의 레지스터, 공유 메모리, L1 캐시를 거쳐, VRAM(글로벌 메모리)에 도달하기 직전의 마지막 온칩 하드웨어 캐시 계층입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;통합 공유 구조 : 독립적인 자원을 갖는 각 SM들과 달리, L2 캐시는 GPU 칩 내의 모든 SM이 공유하는 단일 메모리 공간입니다.&lt;/li&gt;
&lt;li&gt;역할 및 동작 원리 : 모든 글로벌 메모리 읽기/쓰기 요청은 반드시 L2 캐시를 통과합니다. 여러 SM이 글로벌 메모리의 동일한 주소 영역을 참조할 때 메모리 트랜잭션의 중복을 방지하며, 원자적 연산의 직렬화와 가시성 제어가 L2 캐시 레벨에서 이루어집니다.&lt;/li&gt;
&lt;li&gt;캐시 라인 : L2 캐시는 기본적으로 32바이트 단위의 캐시 섹터로 관리됩니다. 따라서 커널이 단 1바이트의 데이터를 요청하더라도 하드웨어는 VRAM에서 32바이트를 읽어옵니다. L1 캐시가 활성화된 상태라면 128바이트 트랜잭션 단위로 인출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;명령어 수준의 실행 및 연산 최적화&lt;/h3&gt;
&lt;p&gt;물리적 연산 유닛(FP32, SFU 등)을 유휴 시간 없이 최대로 구동하기 위해서는 컴파일러에 의존하는 제어 흐름 최적화가 필요합니다.&lt;/p&gt;
&lt;h4&gt;워프 다이버전스 회피 설계&lt;/h4&gt;
&lt;p&gt;앞서 설명한 Active Mask와 Branch Stack의 구조적 한계로 인해, 동일 워프 내에서 조건문으로 인해 실행 경로가 갈라지면 하드웨어는 두 경로를 순차적으로 실행해야 합니다.
이를 회피하기 위해서는 분기 조건이 워프 단위로 동일하게 평가되도록 설계해야 합니다. 예를 들어 &lt;code&gt;if (threadIdx.x &amp;gt; 2)&lt;/code&gt;와 같은 조건은 동일 워프 내에서 다이버전스를 유발하지만, &lt;code&gt;if (threadIdx.x / warpSize &amp;gt; 0)&lt;/code&gt;와 같이 분기 단위를 워프 크기(32)의 배수로 설정하면 워프 내 모든 스레드가 동시에 동일한 경로로 진입하므로 성능 페널티가 발생하지 않습니다.&lt;/p&gt;
&lt;h4&gt;루프 언롤링 (Loop Unrolling)&lt;/h4&gt;
&lt;p&gt;반복문(for, while)은 반복될 때마다 조건 검사, 루프 카운터 증가, 그리고 분기 명령어를 추가로 실행해야 합니다.
CUDA C++에서는 &lt;code&gt;#pragma unroll&lt;/code&gt; 지시어를 통해 컴파일 타임에 루프 본문을 완전히 펼칠 수 있습니다. 이를 통해 제어 흐름 명령어의 오버헤드를 완전히 제거할 수 있을 뿐만 아니라, 독립적인 여러 연산 명령어들이 일렬로 노출되어 스코어보드가 명령어 수준 병렬성을 극대화하여 여러 연산 유닛에 동시에 디스패치할 수 있는 기회를 제공합니다.&lt;/p&gt;
&lt;h4&gt;내장 함수 활용&lt;/h4&gt;
&lt;p&gt;표준 수학 함수(예: &lt;code&gt;sin()&lt;/code&gt;, &lt;code&gt;cos()&lt;/code&gt;, &lt;code&gt;exp()&lt;/code&gt;)는 높은 수치적 정밀도(IEEE-754 표준)와 엣지 케이스 처리를 위해 소프트웨어적으로 수십 개 이상의 서브 명령어로 컴파일되어 FP32/FP64 유닛을 점유합니다.
정밀도보다 연산 속도가 중요한 GPGPU 환경에서는 &lt;code&gt;__sinf()&lt;/code&gt;,&lt;code&gt;__cosf()&lt;/code&gt;와 같은 &lt;em&gt;Intrinsic&lt;/em&gt;를 사용해야 합니다. 이 함수들은 단 1~2 사이클 만에 결과를 도출할 수 있는 SM 내부의 SFU하드웨어로 1:1 직접 매핑되므로 연산 지연 시간을 기하급수적으로 단축시킬 수 있습니다.&lt;/p&gt;
&lt;h3&gt;워프 셔플 명령어&lt;/h3&gt;
&lt;p&gt;블록 내 스레드 간 통신을 위해 일반적으로 공유 메모리를 사용하지만, 공유 메모리 역시 메모리 할당, 뱅크 충돌, 그리고 동기화(&lt;code&gt;__syncthreads()&lt;/code&gt;)에 따른 지연 시간이 발생합니다. 최상의 성능을 내기 위해 이를 하드웨어 레벨에서 우회하는 기법이 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;레지스터 직접 통신 : Kepler 아키텍처부터 도입된 워프 셔플 명령어(&lt;code&gt;__shfl_sync&lt;/code&gt;,&lt;code&gt;__shfl_down_sync&lt;/code&gt; 등)를 사용하면, 동일한 워프 내에 속한 32개의 스레드가 공유 메모리를 거치지 않고 서로의 레지스터 값을 직접 읽고 쓸 수 있습니다.&lt;/li&gt;
&lt;li&gt;동작 원리 및 이점 : 하드웨어의 데이터 경로를 통해 레지스터에서 레지스터로 데이터가 1~2 사이클 만에 즉시 전달됩니다. 공유 메모리를 할당할 필요가 없어 SM 점유율 하락을 막을 수 있고, 동기화 오버헤드가 없으며, 리덕션(Reduction, 배열의 총합이나 최댓값을 구하는 연산)과 같은 알고리즘을 구현할 때 대역폭과 속도를 극단적으로 끌어올릴 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원자적 연산과 하드웨어 직렬화&lt;/h3&gt;
&lt;p&gt;여러 스레드가 동일한 메모리 주소에 동시에 값을 읽고 쓰는 작업을 수행할 때, 데이터의 무결성을 보장하기 위해 원자적 연산(&lt;code&gt;atomicAdd()&lt;/code&gt;, &lt;code&gt;atomicMax()&lt;/code&gt; 등)을 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하드웨어 구현 위치 : 원자적 연산은 SM 내부의 연산 유닛이 처리하는 것이 아니라, 타겟 메모리가 위치한 메모리 컨트롤러(L2 캐시 또는 공유 메모리 뱅크) 레벨에서 하드웨어적으로 처리됩니다.&lt;/li&gt;
&lt;li&gt;직렬화 병목 : 수백 개의 스레드가 글로벌 메모리의 단일 주소에 동시에 atomicAdd를 요청하면, L2 캐시 컨트롤러는 이 요청들을 병렬로 처리하지 못하고 큐에 넣어 한 번에 하나씩 직렬로 처리해야 합니다. 이를 원자적 충돌이라고 하며, 병렬 처리의 이점을 완전히 상실하게 만들어 심각한 성능 저하를 유발합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 원자적 연산의 사용을 최소화하거나, 글로벌 메모리 대신 지연 시간이 짧은 공유 메모리 레벨에서 원자적 연산을 1차적으로 수행한 후 그 결과만 글로벌 메모리에 한 번 원자적으로 반영하는 방식으로 병목을 분산시켜야 합니다.&lt;/p&gt;
&lt;h3&gt;커널 실행 오버헤드&lt;/h3&gt;
&lt;p&gt;일반적으로 호스트에서 &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;...&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; 문법이나 &lt;code&gt;cudaLaunchKernel()&lt;/code&gt;을 통해 GPU 커널을 실행할 때마다 약 5~10 마이크로초의 CPU 오버헤드가 발생합니다. 커널의 연산 시간이 매우 짧고 반복 횟수가 많은 워크로드(예: 딥러닝 추론, 반복적인 물리 시뮬레이션)에서는 GPU가 연산을 끝내고 다음 명령을 기다리는 유휴 시간이 발생하여 전체 성능이 CPU에 의해 병목되는 현상이 나타납니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그래프 기반 실행 모델 : CUDA Graphs는 수천 개의 커널 실행, 메모리 복사(&lt;code&gt;cudaMemcpy&lt;/code&gt;), 동기화 노드들을 하나의 거대한 &apos;작업 그래프(DAG)&apos;로 사전에 정의합니다.&lt;/li&gt;
&lt;li&gt;하드웨어 직접 디스패치 : 런타임에 이 그래프를 통째로 1번만 실행하면, 이후의 작업 스케줄링과 노드 간의 종속성 해결은 CPU의 개입 없이 GPU 하드웨어 내부의 스케줄러(Grid Management Unit)가 직접 처리합니다. 이를 통해 커널 실행 오버헤드를 극단적으로 줄이고 GPU 활용률을 극대화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;글로벌 메모리와 로컬 메모리&lt;/h2&gt;
&lt;h3&gt;글로벌 메모리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;논리적 접근 범위 : GPU 디바이스의 메인 VRAM입니다. 실행 중인 모든 그리드, 블록, 스레드에서 자유롭게 읽고 쓸 수 있으며, 호스트와 데이터를 주고받는 메인 통로 역할을 합니다.&lt;/li&gt;
&lt;li&gt;물리적 실체 : SM 외부에 존재하는 오프칩(Off-chip) 메모리이므로, 용량이 가장 큰 대신 대역폭이 가장 낮고 접근 지연 시간이 수백 사이클에 달할 정도로 매우 느립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;로컬 메모리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;논리적 접근 범위 : 각 스레드에게 개별적이고 독립적으로 할당되는 논리적 메모리 공간입니다. 스레드 내부에서 선언된 변수나 배열이 너무 커서 레지스터 스필링 발생시 임시 저장소로 사용됩니다.&lt;/li&gt;
&lt;li&gt;물리적 실체 : 로컬 메모리는 SM 내부에 존재하는 독립적인 물리적 공간이 아닌 가장 느린 글로벌 메모리(VRAM)의 특정 영역을 스레드별로 분할하여 사용하는 것에 불과합니다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>C++에서의 값 종류</title><link>https://blog.ushiohayase.com/posts/value-type/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/value-type/</guid><description>C++에서 우측값과 좌측값이 뭔지 알아보고 move, forward까지 알아보기</description><pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;기본적인 값 범주&lt;/h2&gt;
&lt;p&gt;C++의 표현식(expression)은 2개의 독립된 속성인 타입(type)과 값 범주(value category)로 나눌 수 있습니다.
이 표현식은 3가지의 값 범주인 &lt;code&gt;prvalue&lt;/code&gt;, &lt;code&gt;xvalue&lt;/code&gt;, &lt;code&gt;lvalue&lt;/code&gt; 중 하나에 반드시 속합니다.&lt;/p&gt;
&lt;p&gt;값 범주는 이 세가지의 기본적인 값 범주와 이것들을 부분집합으로 가지는 값 범주인 &lt;code&gt;glvalue&lt;/code&gt;, &lt;code&gt;rvalue&lt;/code&gt;가 있습니다.
이 값 범주들은 다음과 같이 기준을 가지고 정의됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;identity : 해당 표현식이 메모리 주소를 가져 참조할 수 있는가?&lt;/li&gt;
&lt;li&gt;movability : 해당 표현식의 자원을 다른 곳으로 안전하게 이동할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;have identity&lt;/th&gt;
&lt;th&gt;have not identity&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;can move&lt;/td&gt;
&lt;td&gt;xvalue&lt;/td&gt;
&lt;td&gt;prvalue&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;glvalue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cannot move&lt;/td&gt;
&lt;td&gt;lvalue&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;↓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;rvalue&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;prvalue&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;prvalue&lt;/code&gt;는 내장 연산자의 피연산자로 사용되기위한 순수한 연산의 결과 값이거나 객체를 초기화하는 표현식의 값 범주로 메모리 주소 참조가 불가능하고 (identity = false), 이동이 가능합니다.(movability = true)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prvalue&lt;/code&gt;의 특징으로는 컴파일시 메모리가 할당되지않고 레지스터에만 머물거나 어셈블리 명령어에 즉치 피연산자(Immediate Operand; 어셈블리에 상수가 직접 포함됌)로 하드코딩되기에 메모리의 주소값을 취할 수 없습니다.
또한 &lt;code&gt;prvalue&lt;/code&gt;는 불완전한 타입이나 추상 클래스를 가질 수 업고 원시 자료형(primitive type)의 경우 const와 volatile 한정자도 가질 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prvalue&lt;/code&gt;는 C++17 이전엔 즉각적으로 임시 객체를 생성하는 것으로 간주되어 불필요한 복사/이동 생성자를 호출할 가능성을 내포했지만 C++17부터는 &quot;객체를 초기화하기위한 방법&quot;으로 변경되었습니다.
따라서 &lt;code&gt;prvalue&lt;/code&gt;는 그 자체로 객체가 아니고 초기화 문맥에 도달할 때까지 실제 메모리에 객체를 생성하지 않고 &lt;code&gt;prvalue&lt;/code&gt;를 참조에 바인딩하거나, 클래스의 &lt;code&gt;prvalue&lt;/code&gt; 멤버에 접근할때 비로소 실체화됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prvalue&lt;/code&gt;는 다음과 같은 것들이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리터럴(문자열 리터럴 제외) : &lt;code&gt;42&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;-17&lt;/code&gt;, &lt;code&gt;84.f&lt;/code&gt;, &lt;code&gt;-23.0&lt;/code&gt;, &lt;code&gt;&apos;a&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;후위 증감 연산자의 결과 : &lt;code&gt;x++&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;값이 아닌 타입을 반환하는 함수 또는 연산자의 호출의 결과 : &lt;code&gt;str.substr(1, 2)&lt;/code&gt;, &lt;code&gt;str1 + str2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;산술, 논리, 비트 연산자의 결과 : &lt;code&gt;a + b&lt;/code&gt;, &lt;code&gt;a &amp;amp;&amp;amp; b,&lt;/code&gt; &lt;code&gt;a &amp;lt; b&lt;/code&gt;, &lt;code&gt;a &amp;amp; b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;this&lt;/li&gt;
&lt;li&gt;상수 템플릿 표현식의 스칼라 값&lt;/li&gt;
&lt;li&gt;람다 표현식 그 자체 : &lt;code&gt;[](int x){ return x * x; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;참조 타입이 아닌 것으로 캐스팅하는 표현식 : &lt;code&gt;static_cast&amp;lt;double&amp;gt;(x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;주소를 취한 표현식 : &lt;code&gt;&amp;amp;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;클래스의 열거형 변수(enumerator)나 &lt;code&gt;static&lt;/code&gt;이 아닌 메서드 참조(데이터 멤버 x) : &lt;code&gt;a.m&lt;/code&gt;, &lt;code&gt;p-&amp;gt;m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;열거형 변수(enumerator)&lt;/li&gt;
&lt;li&gt;클래스의 멤버 함수 포인터 역참조 : &lt;code&gt;a.*mp&lt;/code&gt; &lt;code&gt;a-&amp;gt;*mp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;콤마 연산자와 삼항 연산자의 우측에 존재하는 표현식들 : &lt;code&gt;a, b(b가 prvalue)&lt;/code&gt;, &lt;code&gt;a ? b : c(b, c가 prvalue)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;xvalue&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xvalue&lt;/code&gt;는 곧 수명이 다할 임시객체이거나 프로그래머가 임의로 성질을 바꾼 것으로 메모리 상의 주소를 가지고 있어(identity = true) &lt;code&gt;lvalue&lt;/code&gt;처럼 취급될 수 있으면서도, 동시에 이동이 가능한(movability = true) 값 범주입니다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code&gt;xvalue&lt;/code&gt;는 그 정의상 곧 사라질 객체이거나 자원을 뺏겨도 괜찮은 객체이기에 주소 연산자(&amp;amp;)로 주소를 취할 순 없습니다.
&lt;code&gt;xvalue&lt;/code&gt;의 성질 중 특이한 것은 &lt;code&gt;xvalue&lt;/code&gt; 표현식이 함수나 연산자에 들어갈때 우측값 참조(T&amp;amp;&amp;amp;) 매개변수을 가진 버전을 최우선적으로 타겟팅하고 만약 없다면 상수 좌측값 참조(const T&amp;amp;)를 우선적으로 타겟팅한다는 것입니다.
&lt;code&gt;xvalue&lt;/code&gt;는 다른 모든 &lt;code&gt;rvalue&lt;/code&gt;와 마찬가지로 우측값 참조에 바인딩될 수 있고 다른 &lt;code&gt;glvalue&lt;/code&gt;와 마찬가지로 다형성을 가질 수 있으며 클래스가 아닌 &lt;code&gt;xvalue&lt;/code&gt;는 const와 volatile을 가질 수 있습니다.&lt;/p&gt;
&lt;p&gt;이러한 &lt;code&gt;xvalue&lt;/code&gt;에는 다음과 같은 것이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rvalue 객체의 static이 아닌 객체 데이터 참조 : &lt;code&gt;a.m(a는 rvalue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;객체 표현식의 멤버 포인터 : &lt;code&gt;a.*mp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;콤마 연산자와 삼항 연산자의 우측에 존재하는 표현식들 : &lt;code&gt;a, b(b가 xvalue)&lt;/code&gt;, &lt;code&gt;a ? b : c(b, c가 xvalue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;우측값 참조를 반환하는 함수나 연산자의 표현식 : &lt;code&gt;std::move(x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;rvalue 배열 첨자 연산자 : &lt;code&gt;a[n](a는 rvalue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;우측값 참조로 캐스팅한 표현식 : &lt;code&gt;static_cast&amp;lt;char&amp;amp;&amp;amp;&amp;gt;(x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;prvalue가 실체화되는 타이밍 (C++17 이상)&lt;/li&gt;
&lt;li&gt;이동 가능한 표현식(&lt;code&gt;return 문&lt;/code&gt;, &lt;code&gt;co_return 문&lt;/code&gt;(C++20 이상), &lt;code&gt;throw 문&lt;/code&gt;(C++17 이상); C++23 이상)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;lvalue&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;lvalue&lt;/code&gt;는 메모리 상에서 명확하게 주소를 가지고 있으며(identity = true), 지속적으로 접근할 수 있지만, 자원을 임의로 빼앗을 순 없는(movability = false) 값 범주입니다.
&lt;code&gt;lvalue&lt;/code&gt;는 다형성을 가지고 주소 연산자(&lt;code&gt;&amp;amp;&lt;/code&gt;)를 취할 수 있으며 좌측값 참조와 상수 좌측값 참조를 초기화하는데 사용할 수 있고, const가 아닌 lvalue는 대입 연산자의 좌측에 위치할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이러한 &lt;code&gt;lvalue&lt;/code&gt;는 다음과 같은 것이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이름있는 변수, 함수, 템플릿 파라미터 오브젝트(C++20 이상) 또는 데이터 멤버, 우측값 참조 변수&lt;/li&gt;
&lt;li&gt;좌측값 참조를 반환하는 함수나 내장되지 않은 연산자의 표현식 : &lt;code&gt;std::cout &amp;lt;&amp;lt; 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;모든 내장된 대입 연산자 표현식 : &lt;code&gt;a = b&lt;/code&gt;, &lt;code&gt;a += b&lt;/code&gt;, &lt;code&gt;a %= b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;전위증감연산자 표현식 : &lt;code&gt;++x&lt;/code&gt;, &lt;code&gt;--x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내장된 역참조 표현식 : &lt;code&gt;*p&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내장된 배열 첨자 연산자(C++11 이후는 a[n]이 좌측값일때)&lt;/li&gt;
&lt;li&gt;우측값, 열거형 변수, static이 아닌 메서드, a가 우측값이고 m이 static이 아닌 데이터 멤버일때를 제외한 객체 멤버 표현식 : &lt;code&gt;a.m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;열거형 변수나 static이 아닌 메서드를 제외한 객체 멤버 포인터 접근 표현식 : &lt;code&gt;p-&amp;gt;m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lvalue&lt;/code&gt; 객체의 멤버 접근후 데이터 변수 역참조 : &lt;code&gt;a.*mp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내장된 포인터 객체 멤버 접근후 데이터 변수 역참조 : &lt;code&gt;p-&amp;gt;*mp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내장된 콤마 연산자와 삼항 연산자의 우측에 존재하는 표현식들 : &lt;code&gt;a, b(b가 lvalue)&lt;/code&gt;, &lt;code&gt;a ? b : c(b, c가 lvalue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;문자열 리터럴 : &lt;code&gt;&quot;Hello, world!&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;좌측값 참조 캐스팅 : &lt;code&gt;static_cast&amp;lt;int&amp;amp;&amp;gt;(x)&lt;/code&gt;, &lt;code&gt;static_cast&amp;lt;const int&amp;amp;&amp;gt;(x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;좌측값 참조 템플릿 매개변수 상수&lt;/li&gt;
&lt;li&gt;우측값 참조를 반환하는 함수를 반환값으로 가지는 함수나 연산자 표현식(C++11 이상)&lt;/li&gt;
&lt;li&gt;우측값 참조를 반환하는 함수로 캐스팅하는 표현식 : &lt;code&gt;static_cast&amp;lt;void(&amp;amp;&amp;amp;)(int)&amp;gt;(x)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;const T&amp;amp;&lt;/h3&gt;
&lt;p&gt;좌측값 참조는 참조를 하는 대상을 변경할 수 없기에 prvalue, xvalue, lvalue 모두를 대상으로 가질 수 있습니다.
따라서 좌측값 참조는 참조를 하는 대상을 자신의 수명과 같게 강제로 연장시킵니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;glvalue와 rvalue&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;glvalue&lt;/code&gt;와 &lt;code&gt;rvalue&lt;/code&gt;는 위의 기본적인 값 범주들 중 특징이 같은것끼리 모아두고 이름을 붙인 것으로 &lt;code&gt;glvalue&lt;/code&gt;는 &lt;code&gt;lvalue&lt;/code&gt;와 &lt;code&gt;xvalue&lt;/code&gt;를 포함하고 &lt;code&gt;rvalue&lt;/code&gt;는 &lt;code&gt;prvalue&lt;/code&gt;와 &lt;code&gt;xvalue&lt;/code&gt;를 포함합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glvalue&lt;/code&gt;의 특징적인 점으로는 다형성을 가질 수 있고, 완전하지 않은 타입도 가질 수 있습니다.
또한 &lt;code&gt;glvalue&lt;/code&gt;는 &lt;code&gt;lvalue&lt;/code&gt;에서 &lt;code&gt;rvalue&lt;/code&gt;로 배열에서 포인터로 함수에서 포인터로 암시적으로 전환될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rvalue&lt;/code&gt;는 내장된 주소 연산자를 취하지 못하고 내장된 연산자의 좌측에 오지 못하며 우측값 참조 변수를 초기화하고 자신의 수명을 그 변수의 수명까지 늘릴 수 있습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;참조 축약 규칙(Reference Collapsing Rules)과 완벽한 전달(Perfect Forwarding)&lt;/h2&gt;
&lt;h3&gt;참조 축약 규칙&lt;/h3&gt;
&lt;p&gt;C++ 컴파일러는 문법적으로 참조에 대한 참조를 선언하는 것을 금지합니다.
하지만 &lt;code&gt;typedef&lt;/code&gt;, &lt;code&gt;using&lt;/code&gt;, &lt;code&gt;decltype&lt;/code&gt;을 사용하며 참조에 대한 참조가 발생하는 경우가 있기에 다음과 같은 규칙으로 참조가 축약됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;T&amp;amp;&lt;/code&gt; + &lt;code&gt;&amp;amp;&lt;/code&gt; = &lt;code&gt;T&amp;amp;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T&amp;amp;&lt;/code&gt; + &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; = &lt;code&gt;T&amp;amp;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt; + &lt;code&gt;&amp;amp;&lt;/code&gt; = &lt;code&gt;T&amp;amp;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt; + &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; = &lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 참조 축약 규칙이 많이 적용되는 공간은 템플릿 매개변수 &lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt;으로 이 인자에 좌측값이 전달되면 컴파일러는 &lt;code&gt;T&lt;/code&gt;를 &lt;code&gt;A&amp;amp;&lt;/code&gt;로 추론하여 &lt;code&gt;A&amp;amp; &amp;amp;&amp;amp;&lt;/code&gt;, 최종적으론 &lt;code&gt;A&amp;amp;&lt;/code&gt;로 평가됩니다.
거꾸로 우측값이 전달된 경우는 컴파일러는 &lt;code&gt;T&lt;/code&gt;를 &lt;code&gt;A&lt;/code&gt;로 추론하여 최종적으론 &lt;code&gt;A&amp;amp;&amp;amp;&lt;/code&gt;로 평가됩니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;std::move, std::forward&lt;/h3&gt;
&lt;p&gt;C++11에서 도입된 std::move와 std::forward는 잘 사용할시 오버헤드를 크게 줄일 수 있는 함수들입니다.
std::move는 인자로 전달된 표현식의 값 범주를 &lt;code&gt;xvalue&lt;/code&gt;로 변환하여 반환합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt;
constexpr std::remove_reference_t&amp;lt;T&amp;gt;&amp;amp;&amp;amp; move(T&amp;amp;&amp;amp; arg) noexcept {
    return static_cast&amp;lt;std::remove_reference_t&amp;lt;T&amp;gt;&amp;amp;&amp;amp;&amp;gt;(arg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 std::move 함수를 사용하면 좌측값을 &lt;code&gt;xvalue&lt;/code&gt; 표현식으로 바꿔낼 수 있게되고 그에 따라 컴파일러가 호출할 클래스의 복사 생성자/복사 대입 연산자(const T&amp;amp;) 대신 이동 생성자/이동 대입 연산자(T&amp;amp;&amp;amp;)를 선택하도록 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;이런 std::move 연산의 효과를 잘 보여주는 것이 바로 swap하는 상황입니다.&lt;/p&gt;
&lt;p&gt;과거에는 아래같은 함수가 있었으면 복사 생성자/복사 대입 연산자가 호출되지만 현재에는 std::move를 이용하면 이동 생성자/이동 대입 연산자를 호출하여 객체를 복사하지 않고도 swap할 수 있게됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
void swap(T&amp;amp; lhs, T&amp;amp; rhs)
{
    T tmp(lhs); // 복사 생성자
    lhs = rhs; // 복사 대입 연산자
    rhs = lhs; // 복사 대입 연산자
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
void swap(T&amp;amp; lhs, T&amp;amp; rhs)
{
    T tmp(std:move(lhs)); // 이동 생성자
    lhs = rhs; // 이동 대입 연산자
    rhs = lhs; // 이동 대입 연산자
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;복사 생략(Copy Elision)&lt;/h3&gt;
&lt;p&gt;복사 생략(Copy Elision)은 RVO(Return Value Optimization)[^rvo]와 NRVO(Named Return Value Optimization)[^nrvo]를 이용해 함수가 객체를 반환할때 이동/복사 생성자 호출을 생략하는 최적화입니다.&lt;/p&gt;
&lt;p&gt;이 때 주의할 것으로 함수가 반환값을 돌려줄 때 아래와 같이 std::move()를 이용하여 이동으로 반환값을 줘 오버헤드를 줄이자는 생각을 할 수도 있는데
&lt;code&gt;xvalue&lt;/code&gt;로 바뀌면 &lt;code&gt;lvalue&lt;/code&gt;여야 작동하는직 NRVO를 사용하지 못하기에 오히려 속도가 느려질 수 있습니다.&lt;/p&gt;
&lt;p&gt;[^rvo]: RVO - &lt;code&gt;return Object()&lt;/code&gt;와 같이 &lt;code&gt;prvalue&lt;/code&gt;가 반환될 경우 임시 객체를 생성하지않고 함수를 호출하여 반환값을 수신하는 객체의 메모리에 객체를 직접 생성하는 최적화 기술, 함수의 반환 타입과 &lt;code&gt;return&lt;/code&gt; 문의 명시된 객체의 타입이 정확히 일치하고 반환되는 객체가 &lt;code&gt;prvalue&lt;/code&gt;이면 적용됩니다.&lt;/p&gt;
&lt;p&gt;[^nrvo]: NRVO - 위의 RVO를 확장하여 함수 내부의 지역변수를 반환할 때 해당 지역변수를 함수의 스택이 아닌 반환값을 수신하는 객체의 메모리에 직접 객체를 생성하는 최적화 기술, 함수의 반환 타입과 &lt;code&gt;return&lt;/code&gt; 문의 명시된 객체의 타입이 정확히 일치하고 반환되는 객체가 지역변수이며 함수의 모든 실행 경로에서 동일한 지역 변수가 반환되거나 컴파일러가 반환될 변수의 메모리 주소를 추적할 수 있어야 적용됩니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;완벽한 전달(Perfect Forwarding)&lt;/h2&gt;
&lt;p&gt;템플릿 프로그래밍에서는 외부로부터 인자를 받아 내부 함수로 전달하는 래퍼(wrapper) 함수를 작성할 때 값 범주에 대한 문제가 생깁니다.
아래와 같은 함수가 있을 때 인자에 &lt;code&gt;prvalue&lt;/code&gt;를 전달했더라도 내부에서 &lt;code&gt;arg&lt;/code&gt;라는 이름이 부여되어 &lt;code&gt;lvalue&lt;/code&gt;로 바뀌어 버립니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt;
void wrapper(T arg) {
    internal_func(arg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 상황을 막기위해 호출자가 넘겨준 인자의 특성을 보존하여 내부 함수로 전달하는 기법이 &lt;em&gt;완벽한 전달&lt;/em&gt;입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt;
constexpr T&amp;amp;&amp;amp; forward(std::remove_reference_t&amp;lt;T&amp;gt;&amp;amp; arg) noexcept {
    return static_cast&amp;lt;T&amp;amp;&amp;amp;&amp;gt;(arg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;std::forward 함수는 위와 같은 형태로 std::move가 무조건적인 &lt;code&gt;rvalue&lt;/code&gt; 캐스팅을 하는것에 반해 조건부로 캐스팅을 수행합니다.
호출자가 &lt;code&gt;lvalue&lt;/code&gt;를 넘겼을 때는 &lt;code&gt;std::forward&amp;lt;T&amp;amp;&amp;gt;&lt;/code&gt;가 호출이 되고 &lt;code&gt;static_cast&amp;lt;T&amp;amp; &amp;amp;&amp;amp;&amp;gt;&lt;/code&gt;로 치환되어 최종적으론 &lt;code&gt;static_cast&amp;lt;T&amp;amp;&amp;gt;&lt;/code&gt;가 됩니다.
호출자가 &lt;code&gt;rvalue&lt;/code&gt;를 넘기면 &lt;code&gt;std::forward&amp;lt;T&amp;gt;&lt;/code&gt;가 호출이 되고 최종적으론 &lt;code&gt;static_cast&amp;lt;T&amp;amp;&amp;amp;&amp;gt;&lt;/code&gt;가 호출되어 이동 관련 함수를 호출할 수 있게 됩니다.&lt;/p&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;p&gt;cppreference - &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/value_category&quot;&gt;link&lt;/a&gt;&lt;br /&gt;
모두의 코드 - &lt;a href=&quot;https://modoocode.com/189&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>C++ 내 가상 상속과 다중 상속에서의 메서드 호출</title><link>https://blog.ushiohayase.com/posts/method_call_in_multiple_and_virtual_inheritance/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/method_call_in_multiple_and_virtual_inheritance/</guid><description>다중 상속과 가상 상속에서의 vtable이 어떻게 호출되는지 알아보기</description><pubDate>Mon, 05 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;다중 상속&lt;/h2&gt;
&lt;h3&gt;다중 상속에서 메모리 구조&lt;/h3&gt;
&lt;p&gt;다중 상속이란 하나의 클래스가 두 개 이상의 부모 클래스를 상속받는 구조입니다.
이 때 객체 내부의 부모 클래스들은 선언된 순서대로 배치됩니다.&lt;/p&gt;
&lt;p&gt;각 부모 클래스가 가상 함수를 가지고 있다면 자식 클래스 안에는 부모의 수만큼 vptr(가상 함수 테이블 포인터)가 생성됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base1 { void f1() {} virtual void funcA() {} int b1; };
class Base2 { virtual void funcB() {} int b2; };
// Base1과 Base2를 동시에 상속받음
class Derived : public Base1, public Base2 { int d; void funcA() override; void funcB() override; void fNew(); };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예시로 위와 같은 상속에서 &lt;code&gt;Derived&lt;/code&gt; 객체는 아래와 같은 메모리 구조를 갖습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ Derived 객체의 시작 주소 (Ex: 0x1000) ]
+----------------------------+ &amp;lt;--- (A) Base1* 로 가리킬 때의 주소
| [vptr_Primary] (8 bytes)     | -&amp;gt; Primary vtable을 가리킴
| Base1의 멤버변수 b1 (4 bytes)|
+----------------------------+ &amp;lt;--- (B) Base2* 로 가리킬 때의 주소 (Ex: 0x100C)
| [vptr_Base2] (8 bytes)     | -&amp;gt; Base2의 vtable을 가리킴
| Base2의 멤버변수 b2 (4 bytes)|
+----------------------------+
| Derived의 멤버변수 d (4 bytes)|
+----------------------------+

[ Derived의 Primary VTable ] (0x2000 번지라고 가정)
+-----------------------+
| RTTI (Derived 정보)    |
+-----------------------+
| [0]: Base1::f1()      | &amp;lt;--- Base1에서 상속 (변경 없음)
| [1]: Derived::funcA()    | &amp;lt;--- Base1 슬롯을 Derived가 오버라이드 (주소 교체)
+-----------------------+
| [2]: Derived::funcB()    | &amp;lt;--- Base2에서 온 함수를 최적화를 위해 추가 (Direct Call용)
| [3]: Derived::fNew()  | &amp;lt;--- Derived가 새로 만든 가상 함수
+-----------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 그림에서 중요하게 볼 지점은 Base1으로 가르킬 때의 주소와 Base2로 가르킬 때의 주소가 다르다는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Derived* d = new Derived();&lt;/code&gt;라고 할 때는 &lt;code&gt;d&lt;/code&gt;는 당연히 Derived 객체의 시작 주소인 &lt;code&gt;0x1000&lt;/code&gt;을 가르킵니다.
또 &lt;code&gt;Base1* b1 = d;&lt;/code&gt;와 같이 가장 먼저 상속된 클래스의 자료형으로 가르킬 때도 가장 앞에 있으니 똑같이 &apos;0x1000&lt;code&gt;을 가르킵니다. 하지만 &lt;/code&gt;Base2* b2 = d;&lt;code&gt;와 같이 두 번쨰 이상으로 상속된 클래스의 자료형으로 가르킬 때는 컴파일러가 &lt;/code&gt;d&lt;code&gt;가 가르키는 주소에 &lt;/code&gt;sizeof(Base1)&lt;code&gt;만큼 오프셋을 더해&lt;/code&gt;0x100C`를 가르키게 합니다.&lt;/p&gt;
&lt;h3&gt;Thunk&lt;/h3&gt;
&lt;p&gt;위와 같이 다중 상속을 했을 경우 중 가상 함수를 오버라이딩한 경우 아래와 같은 상황이 나타날 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Base2* b2 = new Derived();&lt;/code&gt;와 같이 &lt;code&gt;Derived&lt;/code&gt;가 &lt;code&gt;Base2&lt;/code&gt;의 &lt;code&gt;funcB()&lt;/code&gt;를 오버라이드했다고 가정했을때 &lt;code&gt;funcB()&lt;/code&gt;를 &lt;code&gt;Base2&lt;/code&gt;의 포인터를 통해 호출하려는 상황입니다.
하지만 이 때 &lt;code&gt;Base2*&lt;/code&gt;가 가르키는 주소는 &lt;code&gt;Derived*&lt;/code&gt;로 가르킬 때 주소보다 &lt;code&gt;sizeof(Base1)&lt;/code&gt;만큼 커진 &lt;code&gt;0x100C&lt;/code&gt;를 가르키는 상태입니다.
하지만 실제 구현인 &lt;code&gt;Derived::funcB()&lt;/code&gt;는 객체의 실제 시작점인 &lt;code&gt;0x1000&lt;/code&gt;을 기대합니다.
이를 해결하기 위해 &lt;code&gt;Base2&lt;/code&gt;용 &lt;code&gt;vtable&lt;/code&gt;에는 &lt;code&gt;funcB()&lt;/code&gt;가 들어갈 슬롯에 함수 주소 대신 &lt;code&gt;Thunk&lt;/code&gt;라는 코드 조각을 가르킵니다.
이 &lt;code&gt;Thunk&lt;/code&gt;는 실제 함수 주소가 아닌 &lt;code&gt;this&lt;/code&gt;의 주솟값에서 &lt;code&gt;sizeof(Base1)&lt;/code&gt;만큼을 뺀 뒤에 실제 함수 주소로 점프하는 명령을 가집니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[상황: Base2 포인터(p2)가 Derived 객체의 Base2 영역을 가리키고 있음]

   p2 (주소 0x100C)
      |
      V
+-&amp;gt; [ vptr_Base2 ] -&amp;gt; [ Base2용 vtable ]
|                      +---------------------+
|                      | ...                 |
|                      | funcB의 슬롯         | --&amp;gt; [ Thunk (조정자 코드) ] 로 점프!
(현재 this)            +---------------------+             |
                                                          | (1. this 포인터에서 오프셋 0x0C를 뺌)
                                                          | (2. 이제 this는 0x1000을 가리킴)
                                                          V
                                                    [ 진짜 Derived::funcB() 함수 본체 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;가상 상속&lt;/h2&gt;
&lt;p&gt;C++ 클래스의 다중 상속에서는 일명 &lt;code&gt;죽음의 다이아몬드(Dreadful Diamond)&lt;/code&gt;와 같은 상황이 일어날 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+------------+
      |   Base     |  &amp;lt;-- 최상위 클래스 (예: Animal)
      | (member x) |
      +------------+
       /          \
      /            \
+------------+      +------------+
|  DerivedA  |      |  DerivedB  |  &amp;lt;-- 중간 클래스 (예: Tiger, Lion)
|            |      |            |
+------------+      +------------+
      \            /
       \          /
      +------------+
      |   Final    |  &amp;lt;-- 최하위 다중 상속 클래스 (예: Liger)
      |            |
      +------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 상황에서는 &lt;code&gt;Final&lt;/code&gt; 클래스의 인스턴스가 &lt;code&gt;Base&lt;/code&gt; 클래스에 정의된 메서드나 변수에 접근할 때 &lt;code&gt;DerivedA&lt;/code&gt;를 통할지 &lt;code&gt;DerivedB&lt;/code&gt;를 통할지 판단할 수 없습니다.
따라서 이 상황을 해결하기위해 C++은 가상 상속을 지원합니다.&lt;/p&gt;
&lt;p&gt;가상 상속은 중간 단계 클래스(Derived...)들이 상속받을때 &lt;code&gt;virtual&lt;/code&gt; 키워드를 사용하면, &lt;code&gt;Final&lt;/code&gt; 클래스에서 &lt;code&gt;Base&lt;/code&gt; 클래스의 인스턴스를 1개만 유지합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base { int g; };
class Derived1 : virtual public Base { int p1; }; // 가상 상속
class Derived2 : virtual public Base { int p2; }; // 가상 상속
class Final : public Derived1, public Derived2 { int c; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 가상 상속을 사용할 시 Final 클래스의 메모리 구조는 공유되는 부모는 가장 뒤로 빼는 식으로 다음과 같이 변합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ Final 객체의 시작 주소 ]
+-----------------------------------+
| [vptr_P1] (가상 함수용)            |
| [vbptr_P1] (가상 기본 클래스 포인터)| ---&amp;gt; [ Derived1의 vbtable ]
| Parent1의 멤버 p1                  |      (Grand까지 오프셋: +24 저장됨)
+-----------------------------------+
| [vptr_P2]                         |
| [vbptr_P2]                        | ---&amp;gt; [ Derived2의 vbtable ]
| Derived2의 멤버 p2                  |      (Grand까지 오프셋: +12 저장됨)
+-----------------------------------+
| Final의 멤버 c                     |
+===================================+ &amp;lt;--- 여기가 분기점!
| ** 공유된 Base 객체 영역 ** | (객체의 가장 마지막에 단 하나만 존재)
| Base의 멤버 g                     |
+-----------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 때 Base 멤버를 접근하면 다음과 같은 과정을 거칩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 영역의 vbptr을 읽는다.&lt;/li&gt;
&lt;li&gt;vbptr이 가리키는 테이블(vbtable)로 간다.&lt;/li&gt;
&lt;li&gt;테이블에서 Base까지의 오프셋(거리) 값을 읽는다.&lt;/li&gt;
&lt;li&gt;현재 주소에 그 오프셋을 더해서 실제 Base의 위치를 찾아간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;객체 슬라이싱&lt;/h2&gt;
&lt;p&gt;위와 같은 상황들은 모두 자료형이 포인터형인 경우였습니다.
하지만 값 형태로 객체를 받으면 데이터가 달라집니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Base2 b2 = *new Derived(); // 혹은 Derived d; Base2 b2 = d;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 포인터 형태가 아닌 값 형태로 받을 경우 위 Derived 객체의 멤버 중 Base2가 아닌 요소는 모두 잘려나가고 Base2 부분의 데이터만 복사해서 스택에 할당합니다.&lt;/p&gt;
</content:encoded></item><item><title>C++ 상속과 캐스팅</title><link>https://blog.ushiohayase.com/posts/c-%EC%83%81%EC%86%8D%EA%B3%BC-%EC%BA%90%EC%8A%A4%ED%8C%85/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/c-%EC%83%81%EC%86%8D%EA%B3%BC-%EC%BA%90%EC%8A%A4%ED%8C%85/</guid><description>private, protected, public 상속과 업, 다운 캐스팅</description><pubDate>Sat, 15 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;C++의 상속의 종류&lt;/h2&gt;
&lt;p&gt;C++에서는 상속을 다음과 같이 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Derived : [virtual] [access-specifier] Base1, 
[virtual] [access-specifier] Base2, ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 자식 클래스가 부모 클래스를 상속할 때 액세스 지정자에는 3가지 종류가 있다.
public, protected, private 상속이 있는데 단순하게 생각하면 객체지향 관점에서 상속은 모두 is-a,
그러니까 &quot;Base는 Derived이다&quot;라고 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 C++에서는 아니다.&lt;/p&gt;
&lt;p&gt;C++에서는 public 상속, 그러니까 &lt;code&gt;class Derived : public Base&lt;/code&gt;는 &quot;is-a&quot;가 맞지만
나머지 private, protected 상속은 &quot;is-a&quot;가 아닌 &quot;is-implemented-in-terms-of&quot;, 그러니까
&lt;em&gt;Base로 구현된 Derived&lt;/em&gt;라는 것이다.&lt;/p&gt;
&lt;p&gt;이 관계는 캐스팅에도 영향을 끼친다.&lt;/p&gt;
&lt;p&gt;public 상속은 &lt;code&gt;Base -&amp;gt; Derived&lt;/code&gt;, &lt;code&gt;Derived -&amp;gt; Base&lt;/code&gt; 두 방향 모두 어디서든 가능하지만 private, protected 상속은
Derived 클래스의 메서드 내에서를 제외하면 외부에서는 양 방향 캐스팅 모두 허용하지 않는다.&lt;/p&gt;
&lt;p&gt;따라서 외부에서 캐스팅을 할 필요가 있다면 public으로 상속을 해야한다.&lt;/p&gt;
</content:encoded></item><item><title>C10K 에코 서버 제작 - 2</title><link>https://blog.ushiohayase.com/posts/c10k-echo-server-2/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/c10k-echo-server-2/</guid><description>동시에 1만개의 연결이 가능한 에코 서버 제작</description><pubDate>Tue, 26 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;동기에서 비동기로&lt;/h2&gt;
&lt;p&gt;저번에 만들었던 에코 서버/클라이언트는 클라이언트에서 송수신하는 부분을 epoll을 사용하지않고 동기적으로 제작하여 총 연결 개수가 클라이언트의 스레드 개수를 넘지 못하게 제작되었습니다.
이제 그 부분을 수정하여 초당 1만개의 연결을 통신할 수 있도록 수정하였습니다.&lt;/p&gt;
&lt;h2&gt;과정&lt;/h2&gt;
&lt;p&gt;먼저 가장 잘 보이는 클라이언트측의 send와 recv 사이의 딜레이를 줄이기 위해서 클라이언트도 epoll을 도입하고 송신부분과 수신부분을 분리하였습니다.&lt;/p&gt;
&lt;h3&gt;서버 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int EVENT_SIZE = 1024;
constexpr int BUF_SIZE = 1024;
constexpr int THREAD_COUNT = 17;

sockaddr_in server_addr;
socklen_t sock_len = sizeof(sockaddr);
std::atomic&amp;lt;int&amp;gt; i{0};
int ev_cnt;
epoll_event ev[EVENT_SIZE];

void worker(int epoll_fd, int listen_sock)
{
    while (true)
    {
        for (; i &amp;lt; ev_cnt; ++i)
        {
            if (ev[i].data.fd == listen_sock &amp;amp;&amp;amp; ev[i].events &amp;amp; EPOLLIN)
            {
                sockaddr_in client_addr;

                int acpt_sock =
                    accept(listen_sock, (sockaddr*)&amp;amp;client_addr, &amp;amp;sock_len);

                epoll_event cur_ev;
                cur_ev.data.fd = acpt_sock;
                cur_ev.events = EPOLLIN;

                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acpt_sock, &amp;amp;cur_ev);
            }
            else if (ev[i].events &amp;amp; EPOLLIN)
            {
                int acpt_sock = ev[i].data.fd;
                char buf[BUF_SIZE];
                int recv_bytes = recv(acpt_sock, buf, BUF_SIZE, 0);
                if (recv_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data recving error&quot; &amp;lt;&amp;lt; std::endl;

                int send_bytes = send(acpt_sock, buf, BUF_SIZE, 0);
                if (send_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data sending error&quot; &amp;lt;&amp;lt; std::endl;

                close(acpt_sock);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, acpt_sock, nullptr);
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_port = htons(8888);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = 0;
    memset(&amp;amp;(server_addr.sin_zero), 0, 8);

    int epoll_fd = epoll_create1(0);

    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == -1) std::cerr &amp;lt;&amp;lt; &quot;socket craeting error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket creating&quot; &amp;lt;&amp;lt; std::endl;

    int bind_res = bind(listen_sock, (sockaddr*)&amp;amp;server_addr, sock_len);
    if (bind_res == -1) std::cerr &amp;lt;&amp;lt; &quot;binding error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket binding&quot; &amp;lt;&amp;lt; std::endl;

    int listen_res = listen(listen_sock, EVENT_SIZE);
    if (listen_res == -1) std::cerr &amp;lt;&amp;lt; &quot;listening error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket listening start&quot; &amp;lt;&amp;lt; std::endl;

    epoll_event listen_ev;
    listen_ev.data.fd = listen_sock;
    listen_ev.events = EPOLLIN;

    int ep_res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &amp;amp;listen_ev);
    if (ep_res == -1) std::cerr &amp;lt;&amp;lt; &quot;epoll registing error&quot; &amp;lt;&amp;lt; std::endl;

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i)
        thread_pool.emplace_back(worker, epoll_fd, listen_sock);

    while (true)
    {
        ev_cnt = epoll_wait(epoll_fd, ev, EVENT_SIZE, -1);
        if (i &amp;gt;= ev_cnt) i = 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;클라이언트 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;chrono&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int BUF_SIZE = 1024;
constexpr int MAX_EVENTS = 128;
constexpr int THREAD_COUNT = 17;
constexpr int MAX_CONNECT = 12000;

std::atomic&amp;lt;int&amp;gt; connection_count{0};
std::atomic&amp;lt;int&amp;gt; event_counter{0};
int ev_cnt, epoll_fd;
epoll_event events[MAX_EVENTS];
sockaddr_in server_addr;

void worker()
{
    std::string str = &quot;Hello,World!&quot;;
    char buf[BUF_SIZE];
    std::fill(buf, buf + BUF_SIZE, &apos;\0&apos;);

    for (int i = 0; i &amp;lt; str.size(); ++i) buf[i] = str[i];

    while (true)
    {
        for (; event_counter &amp;lt; ev_cnt; ++event_counter)
        {
            if (events[event_counter].events &amp;amp; EPOLLIN)
            {
                char recv_buf[BUF_SIZE];

                int sock = events[event_counter].data.fd;

                int recv_res = recv(sock, recv_buf, BUF_SIZE, 0);
                if (recv_res == -1) std::cerr &amp;lt;&amp;lt; &quot;recving error&quot; &amp;lt;&amp;lt; std::endl;

                close(sock);

                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, nullptr);

                --connection_count;
            }
        }

        if (connection_count &amp;lt; MAX_CONNECT)
        {
            int sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;socket creating error&quot; &amp;lt;&amp;lt; std::endl;
                continue;
            }

            ++connection_count;

            int con_res =
                connect(sock, (sockaddr*)&amp;amp;server_addr, sizeof(sockaddr));
            if (con_res == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;connecting error&quot; &amp;lt;&amp;lt; std::endl;
                close(sock);
                continue;
            }

            int res = send(sock, buf, BUF_SIZE, 0);
            if (res == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;sending error&quot; &amp;lt;&amp;lt; std::endl;
                close(sock);
                continue;
            }

            epoll_event ev;
            ev.data.fd = sock;
            ev.events = EPOLLIN;

            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &amp;amp;ev);
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);

    if (inet_pton(AF_INET, &quot;127.0.0.1&quot;, &amp;amp;server_addr.sin_addr) &amp;lt;= 0)
    {
        std::cerr &amp;lt;&amp;lt; &quot;\nInvalid address/ Address not supported \n&quot;;
    }

    epoll_fd = epoll_create1(0);

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i) thread_pool.emplace_back(worker);

    auto start = std::chrono::system_clock::now();

    while (true)
    {
        auto end = std::chrono::system_clock::now();
        if ((end - start) &amp;gt; std::chrono::seconds(1))
        {
            std::cerr &amp;lt;&amp;lt; connection_count &amp;lt;&amp;lt; std::endl;
            start = end;
        }
        if (event_counter &amp;gt; MAX_EVENTS)
        {
            ev_cnt = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
            event_counter = 0;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;고칠점&lt;/h3&gt;
&lt;p&gt;일단 위에처럼 코드를 작성하여 WSL상에서 초당 10000개의 연결을 하고 있는 것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;하지만 스레드 간의 경쟁 상태를 유발하는 코드가 있고 기타 개선할 점이 있어 코드를 수정하였습니다.&lt;/p&gt;
&lt;p&gt;기존의 &lt;code&gt;events&lt;/code&gt; 배열에 인덱스를 체크하는 변수는 atomic이였지만 실제 접근은 그렇지 않아 문제가 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;따라서 이것은 각 스레드가 모두 events 배열를 가지는 것으로 수정해 해결하였습니다.&lt;/p&gt;
&lt;p&gt;또 새로운 연결 요청이 들어오면 epoll_wait에서 대기중이던 모든 스레드가 일어나는 문제가 있는데 이것은 listen_sock을 epoll에 등록할 때 EPOLLEXCLUSIVE 플래그를 추가하였습니다.&lt;/p&gt;
&lt;p&gt;수정한 코드는 다음과 같습니다.&lt;/p&gt;
&lt;h3&gt;서버 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int EVENT_SIZE = 1024;
constexpr int BUF_SIZE = 1024;
constexpr int THREAD_COUNT = 17;

sockaddr_in server_addr;
socklen_t sock_len = sizeof(sockaddr);

void worker(int epoll_fd, int listen_sock)
{
    while (true)
    {
        epoll_event ev[EVENT_SIZE];
        int ev_cnt = epoll_wait(epoll_fd, ev, EVENT_SIZE, -1);

        for (int i = 0; i &amp;lt; ev_cnt; ++i)
        {
            if (ev[i].data.fd == listen_sock &amp;amp;&amp;amp; ev[i].events &amp;amp; EPOLLIN)
            {
                sockaddr_in client_addr;

                int acpt_sock =
                    accept(listen_sock, (sockaddr*)&amp;amp;client_addr, &amp;amp;sock_len);

                epoll_event cur_ev;
                cur_ev.data.fd = acpt_sock;
                cur_ev.events = EPOLLIN;

                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acpt_sock, &amp;amp;cur_ev);
            }
            else if (ev[i].events &amp;amp; EPOLLIN)
            {
                int acpt_sock = ev[i].data.fd;
                char buf[BUF_SIZE];
                int recv_bytes = recv(acpt_sock, buf, BUF_SIZE, 0);
                if (recv_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data recving error&quot; &amp;lt;&amp;lt; std::endl;

                int send_bytes = send(acpt_sock, buf, BUF_SIZE, 0);
                if (send_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data sending error&quot; &amp;lt;&amp;lt; std::endl;

                close(acpt_sock);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, acpt_sock, nullptr);
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_port = htons(8888);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = 0;
    memset(&amp;amp;(server_addr.sin_zero), 0, 8);

    int epoll_fd = epoll_create1(0);

    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == -1) std::cerr &amp;lt;&amp;lt; &quot;socket craeting error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket creating&quot; &amp;lt;&amp;lt; std::endl;

    int bind_res = bind(listen_sock, (sockaddr*)&amp;amp;server_addr, sock_len);
    if (bind_res == -1) std::cerr &amp;lt;&amp;lt; &quot;binding error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket binding&quot; &amp;lt;&amp;lt; std::endl;

    int listen_res = listen(listen_sock, EVENT_SIZE);
    if (listen_res == -1) std::cerr &amp;lt;&amp;lt; &quot;listening error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket listening start&quot; &amp;lt;&amp;lt; std::endl;

    epoll_event listen_ev;
    listen_ev.data.fd = listen_sock;
    listen_ev.events = EPOLLIN | EPOLLEXCLUSIVE;

    int ep_res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &amp;amp;listen_ev);
    if (ep_res == -1) std::cerr &amp;lt;&amp;lt; &quot;epoll registing error&quot; &amp;lt;&amp;lt; std::endl;

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i)
        thread_pool.emplace_back(worker, epoll_fd, listen_sock);

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i) thread_pool[i].join();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;클라이언트 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;chrono&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int BUF_SIZE = 1024;
constexpr int MAX_EVENTS = 128;
constexpr int THREAD_COUNT = 17;
constexpr int MAX_CONNECT = 12000;

std::atomic&amp;lt;int&amp;gt; connection_count{0};
std::atomic&amp;lt;int&amp;gt; event_counter{0};
int epoll_fd;

sockaddr_in server_addr;

void worker()
{
    std::string str = &quot;Hello,World!&quot;;
    char buf[BUF_SIZE];
    std::fill(buf, buf + BUF_SIZE, &apos;\0&apos;);

    for (int i = 0; i &amp;lt; str.size(); ++i) buf[i] = str[i];

    while (true)
    {
        if (connection_count &amp;lt; MAX_CONNECT)
        {
            int sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;socket creating error&quot; &amp;lt;&amp;lt; std::endl;
                continue;
            }

            ++connection_count;

            int con_res =
                connect(sock, (sockaddr*)&amp;amp;server_addr, sizeof(sockaddr));
            if (con_res == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;connecting error&quot; &amp;lt;&amp;lt; std::endl;
                close(sock);
                continue;
            }

            int res = send(sock, buf, BUF_SIZE, 0);
            if (res == -1)
            {
                std::cerr &amp;lt;&amp;lt; &quot;sending error&quot; &amp;lt;&amp;lt; std::endl;
                close(sock);
                continue;
            }

            epoll_event ev;
            ev.data.fd = sock;
            ev.events = EPOLLIN;

            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &amp;amp;ev);
        }

        epoll_event events[MAX_EVENTS];
        int ev_cnt = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);

        for (int event_counter = 0; event_counter &amp;lt; ev_cnt; ++event_counter)
        {
            epoll_event events[MAX_EVENTS];
            if (events[event_counter].events &amp;amp; EPOLLIN)
            {
                char recv_buf[BUF_SIZE];

                int sock = events[event_counter].data.fd;

                int recv_res = recv(sock, recv_buf, BUF_SIZE, 0);
                if (recv_res == -1) std::cerr &amp;lt;&amp;lt; &quot;recving error&quot; &amp;lt;&amp;lt; std::endl;

                close(sock);

                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, nullptr);

                --connection_count;
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);

    if (inet_pton(AF_INET, &quot;127.0.0.1&quot;, &amp;amp;server_addr.sin_addr) &amp;lt;= 0)
    {
        std::cerr &amp;lt;&amp;lt; &quot;\nInvalid address/ Address not supported \n&quot;;
    }

    epoll_fd = epoll_create1(0);

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i) thread_pool.emplace_back(worker);

    auto start = std::chrono::system_clock::now();

    while (true)
    {
        auto end = std::chrono::system_clock::now();
        if ((end - start) &amp;gt; std::chrono::seconds(1))
        {
            std::cerr &amp;lt;&amp;lt; connection_count &amp;lt;&amp;lt; std::endl;
            start = end;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;고칠점&lt;/h2&gt;
&lt;p&gt;여기서 connection_count가 중복으로 세지는 문제와 같은 파일 디스크럽터를 여러 스레드가 동시에 접근하는 걸 막기위해 클라이언트는 한번에 소켓을 만든뒤 유지하는 걸로 바꿨습니다.&lt;/p&gt;
&lt;p&gt;서버쪽에서도 경쟁 상태가 일어나는 부분이 있었는데 그 부분을 소켓을 epoll에 등록할 때 EPOLLONESHOT과 EPOLLET를 주는 것으로 해결했습니다.&lt;/p&gt;
&lt;h3&gt;서버 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int EVENT_SIZE = 1024;
constexpr int BUF_SIZE = 1024;
constexpr int THREAD_COUNT = 17;

sockaddr_in server_addr;
socklen_t sock_len = sizeof(sockaddr);

void worker(int epoll_fd, int listen_sock)
{
    while (true)
    {
        epoll_event ev[EVENT_SIZE];
        int ev_cnt = epoll_wait(epoll_fd, ev, EVENT_SIZE, -1);

        for (int i = 0; i &amp;lt; ev_cnt; ++i)
        {
            if (ev[i].data.fd == listen_sock)
            {
                sockaddr_in client_addr;

                int acpt_sock =
                    accept(listen_sock, (sockaddr*)&amp;amp;client_addr, &amp;amp;sock_len);

                epoll_event cur_ev;
                cur_ev.data.fd = acpt_sock;
                cur_ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;

                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acpt_sock, &amp;amp;cur_ev);
            }
            else if (ev[i].events &amp;amp; EPOLLIN)
            {
                int acpt_sock = ev[i].data.fd;
                char buf[BUF_SIZE];
                int recv_bytes = recv(acpt_sock, buf, BUF_SIZE, 0);
                if (recv_bytes &amp;gt; 0)
                {
                    int send_bytes = send(acpt_sock, buf, BUF_SIZE, 0);
                    if (send_bytes == -1)
                        std::cerr &amp;lt;&amp;lt; &quot;data sending error: &quot; &amp;lt;&amp;lt; strerror(errno)
                                  &amp;lt;&amp;lt; std::endl;
                }
                else
                {
                    if (recv_bytes == -1)
                        std::cerr &amp;lt;&amp;lt; &quot;data recving error: &quot; &amp;lt;&amp;lt; strerror(errno)
                                  &amp;lt;&amp;lt; std::endl;

                    close(acpt_sock);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, acpt_sock, nullptr);
                }
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_port = htons(8888);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = 0;
    memset(&amp;amp;(server_addr.sin_zero), 0, 8);

    int epoll_fd = epoll_create1(0);

    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == -1) std::cerr &amp;lt;&amp;lt; &quot;socket craeting error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket creating&quot; &amp;lt;&amp;lt; std::endl;

    int bind_res = bind(listen_sock, (sockaddr*)&amp;amp;server_addr, sock_len);
    if (bind_res == -1) std::cerr &amp;lt;&amp;lt; &quot;binding error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket binding&quot; &amp;lt;&amp;lt; std::endl;

    int listen_res = listen(listen_sock, EVENT_SIZE);
    if (listen_res == -1) std::cerr &amp;lt;&amp;lt; &quot;listening error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket listening start&quot; &amp;lt;&amp;lt; std::endl;

    epoll_event listen_ev;
    listen_ev.data.fd = listen_sock;
    listen_ev.events = EPOLLIN;

    int ep_res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &amp;amp;listen_ev);
    if (ep_res == -1) std::cerr &amp;lt;&amp;lt; &quot;epoll registing error&quot; &amp;lt;&amp;lt; std::endl;

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i)
        thread_pool.emplace_back(worker, epoll_fd, listen_sock);

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i) thread_pool[i].join();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;클라이언트 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;chrono&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;mutex&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int BUF_SIZE = 1024;
constexpr int MAX_EVENTS = 1024;
constexpr int THREAD_COUNT = 16;
constexpr int MAX_CONNECT = 12000;

std::atomic&amp;lt;int&amp;gt; connection_count{0};
int epoll_fd;

sockaddr_in server_addr;

void worker()
{
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1)
    {
        std::cerr &amp;lt;&amp;lt; &quot;worker epoll_create1 failed: &quot; &amp;lt;&amp;lt; strerror(errno)
                  &amp;lt;&amp;lt; std::endl;
        return;
    }

    std::string str = &quot;Hello,World!&quot;;
    char send_buf[BUF_SIZE];
    std::fill(send_buf, send_buf + BUF_SIZE, 0);
    memcpy(send_buf, str.c_str(), str.length());

    for (int i = 0; i &amp;lt; MAX_CONNECT / THREAD_COUNT; ++i)
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock == -1)
        {
            std::cerr &amp;lt;&amp;lt; &quot;socket creating error: &quot; &amp;lt;&amp;lt; strerror(errno)
                      &amp;lt;&amp;lt; std::endl;
            continue;
        }
        if (connect(sock, (sockaddr*)&amp;amp;server_addr, sizeof(sockaddr)) == -1)
        {
            std::cerr &amp;lt;&amp;lt; &quot;connecting error: &quot; &amp;lt;&amp;lt; strerror(errno) &amp;lt;&amp;lt; std::endl;
            close(sock);
            continue;
        }
        if (send(sock, send_buf, BUF_SIZE, 0) == -1)
        {
            std::cerr &amp;lt;&amp;lt; &quot;sending error: &quot; &amp;lt;&amp;lt; strerror(errno) &amp;lt;&amp;lt; std::endl;
            close(sock);
            continue;
        }

        epoll_event ev;
        ev.data.fd = sock;
        ev.events = EPOLLIN;
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &amp;amp;ev);
        connection_count++;
    }

    epoll_event events[MAX_EVENTS];
    while (true)
    {
        int ev_cnt = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i &amp;lt; ev_cnt; ++i)
        {
            if (events[i].events &amp;amp; EPOLLIN)
            {
                int sock = events[i].data.fd;
                char recv_buf[BUF_SIZE];
                int recv_res = recv(sock, recv_buf, BUF_SIZE, 0);

                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, nullptr);
                close(sock);
                --connection_count;

                int new_sock = socket(AF_INET, SOCK_STREAM, 0);
                if (new_sock == -1) continue;
                if (connect(new_sock, (sockaddr*)&amp;amp;server_addr,
                            sizeof(sockaddr)) == -1)
                {
                    close(new_sock);
                    continue;
                }
                if (send(new_sock, send_buf, BUF_SIZE, 0) == -1)
                {
                    close(new_sock);
                    continue;
                }
                epoll_event ev;
                ev.data.fd = new_sock;
                ev.events = EPOLLIN;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_sock, &amp;amp;ev);
                ++connection_count;
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);

    if (inet_pton(AF_INET, &quot;127.0.0.1&quot;, &amp;amp;server_addr.sin_addr) &amp;lt;= 0)
    {
        std::cerr &amp;lt;&amp;lt; &quot;\nInvalid address/ Address not supported \n&quot;;
    }

    epoll_fd = epoll_create1(0);

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i)
    {
        thread_pool.emplace_back(worker);
    }

    auto start = std::chrono::system_clock::now();

    while (true)
    {
        auto end = std::chrono::system_clock::now();
        if ((end - start) &amp;gt; std::chrono::seconds(1))
        {
            std::cerr &amp;lt;&amp;lt; connection_count &amp;lt;&amp;lt; std::endl;
            start = end;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Ushio-Hayase/c10k-Echo-Server&quot;&gt;github&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>C10K 에코 서버 제작 - 1</title><link>https://blog.ushiohayase.com/posts/c10k-echo-server-1/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/c10k-echo-server-1/</guid><description>동시에 1만개의 연결이 가능한 에코 서버 제작</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;리눅스 소켓 프로그래밍 학습&lt;/h2&gt;
&lt;p&gt;리눅스에서 소켓을 이용해 통신을 할 때는 주로 아래 7개의 함수를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** 
 * linux clang 18.1.3 x86_64-pc-linux-gnu 컴파일러의 헤더에서 발췌
 * */
1. int socket (int __domain, int __type, int __protocol)
2. int bind (int __fd,__CONST_SOCKADDR_ARG __addr, socklen_t__len)
3. int connect (int __fd,__CONST_SOCKADDR_ARG __addr, socklen_t__len)
4. ssize_t send (int __fd, const void *__buf, size_t __n, int__flags)
5. ssize_t recv (int __fd, void*__buf, size_t __n, int__flags)
6. int listen (int __fd, int__n)
7. int accept (int __fd,__SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 함수들은 &lt;code&gt;&amp;lt;sys/socket.h&amp;gt;&lt;/code&gt; 헤더에 들어있습니다.&lt;/p&gt;
&lt;p&gt;먼저 &lt;code&gt;socket()&lt;/code&gt; 함수는 순서대로 소켓의 주소 종류, 연결 타입, 소켓의 프로토콜을 인자로 받습니다.&lt;/p&gt;
&lt;p&gt;첫 번째 인자가 받을 수 있는 인자로는 다음과 같은 매크로 상수가 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이름&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AF_UNIX, AF_LOCAL&lt;/td&gt;
&lt;td&gt;운영체제 내 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_INET&lt;/td&gt;
&lt;td&gt;IPv4 인터넷 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_INET6&lt;/td&gt;
&lt;td&gt;IPv6 인터넷 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_IPX&lt;/td&gt;
&lt;td&gt;IPX  Novell 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_NETLINK&lt;/td&gt;
&lt;td&gt;커널과 유저 공간 사이의 정보전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_X25&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/X.25&quot;&gt;ITU-T X.25&lt;/a&gt; / 프로토콜 스위칭 데이터 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_AX25&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/AX.25&quot;&gt;AX.25 protocol&lt;/a&gt; / X.25의 레이어 2에서 파생된 데이터 링크 계층 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_ATMPVC&lt;/td&gt;
&lt;td&gt;ATM(&lt;a href=&quot;https://en.wikipedia.org/wiki/Asynchronous_Transfer_Mode&quot;&gt;Asynchronous Transfer Mode&lt;/a&gt;) 네트워크에서 사용되는 가상 회선(Virtual Circuit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_APPLETALK&lt;/td&gt;
&lt;td&gt;애플에서 사용하던 네트워킹 프로토콜 &lt;a href=&quot;https://en.wikipedia.org/wiki/AppleTalk&quot;&gt;wikipedia&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_PACKET&lt;/td&gt;
&lt;td&gt;저수준의 패킷 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AF_ALG&lt;/td&gt;
&lt;td&gt;커널의 crypto API 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;두 번째 인자가 받을 수 있는 인자로는 다음 두 종류의 매크로 상수가 있고 bitwise OR 연산자로 두 종류를 동시에 사용할 수 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이름&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SOCK_STREAM&lt;/td&gt;
&lt;td&gt;데이터의 순서가 보장되고 데이터에 신뢰성있는 바이트 기반의 연결(TCP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCK_DGRAM&lt;/td&gt;
&lt;td&gt;데이터 그램을 지원하고 데이터에 신뢰성이 없는 비연결 지향의 통신(UDP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCK_SEQPACKET&lt;/td&gt;
&lt;td&gt;순서가 보장되고 데이터에 신뢰성있는 데이터 그램 기반의 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCKET_RAW&lt;/td&gt;
&lt;td&gt;네트워크 프로토콜을 직접 제어할 수 있게하는 옵션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCKET_RDM&lt;/td&gt;
&lt;td&gt;순서가 보자오디지않는 신뢰할 수 있는 데이터 그램 기반의 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCKET_PACKET&lt;/td&gt;
&lt;td&gt;저수준의 네트워크 인터페이스에 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이름&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SOCK_NONBLOCK&lt;/td&gt;
&lt;td&gt;connect(), accept(), send(), recv()를 논블로킹으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOCK_CLOEXEC&lt;/td&gt;
&lt;td&gt;exec() 관련 시스템 콜이 새 프로그램을 실행할 때 닫히도록 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;세 번째 인자는 특정한 프로토콜 숫자를 받습니다. 이 프로토콜에 사용될  숫자는 첫 번째 인자에 의해 특정됩니다. 단일 프로토콜만 존재할 경우 0으로 지정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;두 번째 &lt;code&gt;bind()&lt;/code&gt; 함수는 순서대로 소켓 fd, 소켓 주소 구조체, 구조체의 크기를 입력받습니다.
소켓 주소 구조체의 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct sockaddr {
    sa_family_t sa_family;
    char        sa_data[14];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 구조체는 다양한 주소 컨테이너를 제공할 수 있도록 설계되었습니다.
IPv4와 IPv6에서는 sockaddr_in 구조체와 sockaddr_in6 구조체에 내용을 채워넣고 주소를 sockaddr로 캐스팅하는 식으로 전달합니다.&lt;/p&gt;
&lt;p&gt;세 번째 &lt;code&gt;connect()&lt;/code&gt; 또한 &lt;code&gt;bind()&lt;/code&gt; 함수와 동일한 인수 타입을 받습니다.&lt;/p&gt;
&lt;p&gt;네 번째와 다섯 번째 &lt;code&gt;send()&lt;/code&gt;와 &lt;code&gt;recv()&lt;/code&gt; 함수(&lt;code&gt;read()&lt;/code&gt;와 &lt;code&gt;write()&lt;/code&gt; 함수 또한(전자의 함수와는 flags 유무 차이))는 buffer에 있는 데이터를 n바이트의 길이만큼 보내고 수신합니다.
flags에는 MSG_OOB(out-of-bound 데이터를 전송), MSG_WAITTALL(요청한 크기가 모두 차야 함수를 반환), MSG_DONTWAIT(논블로킹으로 작동)을 넣어줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;여섯 번째 &lt;code&gt;listen()&lt;/code&gt; 함수는 &lt;code&gt;SOCK_STREAM or SOCK_SEQPACKET&lt;/code&gt; 타입의 소켓 파일 디스크럽터를 첫 번째 인자로 받고 sockfd에 연결 대기할 큐의 길이를 두번째 인자로 받습니다.&lt;/p&gt;
&lt;p&gt;마지막 &lt;code&gt;accept()&lt;/code&gt; 함수 또한 &lt;code&gt;bind()&lt;/code&gt;, &lt;code&gt;connect()&lt;/code&gt;와 같은 타입의 인자를 받습니다. 받은 인자 중 sockaddr에는 연결한 상대의 정보가 채워지고 반환값으론 연결된 소켓의 파일 디스크럽터가 반환됩니다.&lt;/p&gt;
&lt;h2&gt;epoll&lt;/h2&gt;
&lt;p&gt;epoll에 대해 자세히 알려면 만들어진 원인부터 알아보면 더 쉽게 알 수 있습니다.
epoll이전에는 select와 poll이라는 것이 있어 이것들로 여러개의 I/O 작업을 관리했습니다.&lt;/p&gt;
&lt;p&gt;select는 감시할 FD의 개수가 FD_SETSIZE(보통 1024)로 제한되고 fd_set이라는 비트맵 형태의 자료구조를 사용하는데 매 호출시마다 이 구조체를 유저 공간에서 커널 공간으로 복사해야했습니다.
또한 커널은 이벤트 발생 여부와 무관하게 모든 FD를 순차적으로 스캔하여 확인해 감시 대상 수-N에 비례하는 O(N)의 시간 복잡도를 가졌습니다.&lt;/p&gt;
&lt;p&gt;poll도 비슷하게 감시할 FD 숫자만 해결했지 나머지 문제는 비슷했습니다.&lt;/p&gt;
&lt;p&gt;epoll은 이 문제들을 해결하기 위해 상태 관리를 커널에 위임하고 유저는 결과만 통보받도록 바꿨습니다.&lt;/p&gt;
&lt;p&gt;epoll을 사용하기 위해선 먼저 &lt;code&gt;epoll_create1()&lt;/code&gt;을 이용해 epoll 인스턴스를 생성합니다.
이 epoll 인스턴스는 내부적으로 레드-블랙 트리로 구성된 Interest List(관심 목록)과 링크드 리스트로 구성된 Ready List(준비 목록)으로 관리합니다.&lt;/p&gt;
&lt;p&gt;이 epoll 인스턴스의 관심 목록을 관리하기 위해서 &lt;code&gt;epoll_ctl()&lt;/code&gt;을 이용해 관심 목록에서 FD를 추가, 수정, 삭제합니다.&lt;/p&gt;
&lt;p&gt;네트워크 디바이스 드라이버가 패킷을 수신하는 등 특정 FD에 이벤트가 발생하면, 커널은 해당 FD가 어느 epoll 인스턴스의 Interest List에 등록되어 있는지 확인합니다.
등록된 FD라면, 커널은 해당 FD를 epoll 인스턴스의 Ready List에 추가하는 콜백 함수를 실행합니다.
이 모든 과정은 커널 내부에서 비동기적으로 일어나며, 유저 프로그램은 관여하지 않습니다.&lt;/p&gt;
&lt;p&gt;유저 프로그램은 &lt;code&gt;epoll_wait()&lt;/code&gt;를 호출하여 Ready List가 비어있지 않을 때까지 대기한 뒤 Ready List에 있는 FD 목록만 유저공간으로 복사하여 반환합니다.
함수가 반환된 뒤, 이벤트가 발생한 소켓의 정보를 담고 있는 &lt;code&gt;events&lt;/code&gt; 배열을 확인하여 어떤 FD에서 어떤 종류의 이벤트가 발생했는지 알 수 있습니다.&lt;/p&gt;
&lt;h2&gt;epoll을 이용한 멀티스레드 동기 에코 서버/클라이언트&lt;/h2&gt;
&lt;p&gt;이 서버와 클라이언트를 제작하면서 epoll이 정확히 어떤 역할을 맡는지 헷갈려 여러번 수정하며 시도했었는데 그 결과, 이벤트가 발생한 FD가 들어있는 &lt;code&gt;events&lt;/code&gt; 객체를 반환한다는 걸 어떤건지 이해했습니다.
그래서 그걸 이용한 멀티 스레드 서버를 만들때 원자적 연산을 어떻게 이용해야하는지 이해하고 프로그램을 제작하였습니다.&lt;/p&gt;
&lt;h3&gt;코드 - 서버&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int EVENT_SIZE = 1024;
constexpr int BUF_SIZE = 1024;
constexpr int THREAD_COUNT = 17;

sockaddr_in server_addr;
socklen_t sock_len = sizeof(sockaddr);
std::atomic&amp;lt;int&amp;gt; i{0};
int ev_cnt;
epoll_event ev[EVENT_SIZE];

void worker(int epoll_fd, int listen_sock)
{
    while (true)
    {
        for (; i &amp;lt; ev_cnt; ++i)
        {
            if (ev[i].data.fd == listen_sock &amp;amp;&amp;amp; ev[i].events &amp;amp; EPOLLIN)
            {
                sockaddr_in client_addr;

                int acpt_sock =
                    accept(listen_sock, (sockaddr*)&amp;amp;client_addr, &amp;amp;sock_len);

                epoll_event cur_ev;
                cur_ev.data.fd = acpt_sock;
                cur_ev.events = EPOLLIN;

                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acpt_sock, &amp;amp;cur_ev);
            }
            else
            {
                int acpt_sock = ev[i].data.fd;
                char buf[BUF_SIZE];
                int recv_bytes = recv(acpt_sock, buf, BUF_SIZE, 0);
                if (recv_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data recving error&quot; &amp;lt;&amp;lt; std::endl;

                int send_bytes = send(acpt_sock, buf, BUF_SIZE, 0);
                if (send_bytes == -1)
                    std::cerr &amp;lt;&amp;lt; &quot;data sending error&quot; &amp;lt;&amp;lt; std::endl;

                close(acpt_sock);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, acpt_sock, nullptr);
            }
        }
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_port = htons(8888);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = 0;
    memset(&amp;amp;(server_addr.sin_zero), 0, 8);

    int epoll_fd = epoll_create1(0);

    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == -1) std::cerr &amp;lt;&amp;lt; &quot;socket craeting error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket creating&quot; &amp;lt;&amp;lt; std::endl;

    int bind_res = bind(listen_sock, (sockaddr*)&amp;amp;server_addr, sock_len);
    if (bind_res == -1) std::cerr &amp;lt;&amp;lt; &quot;binding error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket binding&quot; &amp;lt;&amp;lt; std::endl;

    int listen_res = listen(listen_sock, EVENT_SIZE);
    if (listen_res == -1) std::cerr &amp;lt;&amp;lt; &quot;listening error&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;socket listening start&quot; &amp;lt;&amp;lt; std::endl;

    epoll_event listen_ev;
    listen_ev.data.fd = listen_sock;
    listen_ev.events = EPOLLIN;

    int ep_res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &amp;amp;listen_ev);
    if (ep_res == -1) std::cerr &amp;lt;&amp;lt; &quot;epoll registing error&quot; &amp;lt;&amp;lt; std::endl;

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i)
        thread_pool.emplace_back(worker, epoll_fd, listen_sock);

    while (true)
    {
        int ev_cnt = epoll_wait(epoll_fd, ev, EVENT_SIZE, -1);
        if (i &amp;gt;= ev_cnt) i = 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;코드 - 클라이언트&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;mutex&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int BUF_SIZE = 1024;
constexpr int MAX_EVENTS = 128;
constexpr int THREAD_COUNT = 17;

std::atomic&amp;lt;int&amp;gt; connection_count{0};
epoll_event events[MAX_EVENTS];
sockaddr_in server_addr;

void worker()
{
    std::string str = &quot;Hello,World!&quot;;
    char buf[BUF_SIZE];
    std::fill(buf, buf + BUF_SIZE, &apos;\0&apos;);

    for (int i = 0; i &amp;lt; str.size(); ++i) buf[i] = str[i];

    while (true)
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock == -1) std::cerr &amp;lt;&amp;lt; &quot;socket creating error&quot; &amp;lt;&amp;lt; std::endl;

        int con_res = connect(sock, (sockaddr *)&amp;amp;server_addr, sizeof(sockaddr));
        if (con_res == -1) std::cerr &amp;lt;&amp;lt; &quot;connecting error&quot; &amp;lt;&amp;lt; std::endl;

        ++connection_count;

        int res = send(sock, buf, BUF_SIZE, 0);
        if (res == -1) std::cerr &amp;lt;&amp;lt; &quot;sending error&quot; &amp;lt;&amp;lt; std::endl;

        char recv_buf[BUF_SIZE];

        int recv_res = recv(sock, recv_buf, BUF_SIZE, 0);
        if (recv_res == -1) std::cerr &amp;lt;&amp;lt; &quot;recving error&quot; &amp;lt;&amp;lt; std::endl;

        close(sock);

        --connection_count;
    }
}

int main()
{
    std::vector&amp;lt;std::thread&amp;gt; thread_pool;

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);

    if (inet_pton(AF_INET, &quot;127.0.0.1&quot;, &amp;amp;server_addr.sin_addr) &amp;lt;= 0)
    {
        std::cerr &amp;lt;&amp;lt; &quot;\nInvalid address/ Address not supported \n&quot;;
    }

    for (int i = 0; i &amp;lt; THREAD_COUNT; ++i) thread_pool.emplace_back(worker);

    clock_t start = clock();

    while (true)
    {
        clock_t end = clock();
        if ((end - start) / (double)CLOCKS_PER_SEC &amp;gt; 1)
        {
            std::cerr &amp;lt;&amp;lt; connection_count &amp;lt;&amp;lt; std::endl;
            start = end;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linuxhowtos.org/C_C++/socket.htm&quot;&gt;linux howtos socket 문서&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>priority_queue에서 커스텀 비교 함수 넘기기</title><link>https://blog.ushiohayase.com/posts/priority_queue%EC%97%90%EC%84%9C_%EC%BB%A4%EC%8A%A4%ED%85%80_%EB%B9%84%EA%B5%90_%ED%95%A8%EC%88%98_%EB%84%98%EA%B8%B0%EA%B8%B0/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/priority_queue%EC%97%90%EC%84%9C_%EC%BB%A4%EC%8A%A4%ED%85%80_%EB%B9%84%EA%B5%90_%ED%95%A8%EC%88%98_%EB%84%98%EA%B8%B0%EA%B8%B0/</guid><description>priority_queue에서 커스텀 비교 람다/함수 넘기기</description><pubDate>Thu, 31 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;priority_queue의 선언&lt;/h2&gt;
&lt;p&gt;priority_queue의 선언은 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt; class T, 
class Container = std::vector&amp;lt;T&amp;gt;, 
class Compare = std::less&amp;lt;typename Container::value_type&amp;gt; &amp;gt; 
class priority_queue;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;이유&lt;/h2&gt;
&lt;p&gt;priority_queue의 세번째 템플릿 매개변수는 타입만을 받습니다.&lt;/p&gt;
&lt;p&gt;즉 실제 비교 연산을 할 때 람다함수/함수를 사용한다면 호출할 객체는 따로 넘겨줘야합니다.&lt;/p&gt;
&lt;p&gt;왜냐하면 구조체나 클래스는 기본 생성자가 있어 아래 코드의 객체를 초기화할때 기본적으로 초기화가 가능하지만 람다함수/함수는 그게 안되기 때문입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;class T, class Container, class Compare&amp;gt;
class priority_queue {
private:
    Compare comp;  // 비교 객체를 멤버로 저장
    
public:
    priority_queue(const Compare&amp;amp; c) : comp(c) {}  // 생성자에서 초기화
    
    void push(const T&amp;amp; value) {
        // comp를 실제로 호출
        // comp(a, b) 형태로 사용
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compare에 넘길때 함수에 decltype을 사용하려면 호출이 불가능한 함수 타입이 아닌 함수 포인터 타입을 넘겨야합니다.
&lt;code&gt;decltype(cmp)&lt;/code&gt; - X , &lt;code&gt;decltype(&amp;amp;cmp)&lt;/code&gt; - O&lt;/p&gt;
</content:encoded></item><item><title>두 직선의 교점구하는 알고리즘 - 매개변수 방정식 이용</title><link>https://blog.ushiohayase.com/posts/%EB%91%90-%EC%A7%81%EC%84%A0%EC%9D%98-%EA%B5%90%EC%A0%90%EA%B5%AC%ED%95%98%EA%B8%B0/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/%EB%91%90-%EC%A7%81%EC%84%A0%EC%9D%98-%EA%B5%90%EC%A0%90%EA%B5%AC%ED%95%98%EA%B8%B0/</guid><pubDate>Mon, 23 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;두 선분이 있을 때 교점의 좌표를 구하는 알고리즘 중 직선의 방정식을 기울기와 y절편을 이용해 구하는 방법과 매개변수 방정식을 이용해 나타내어 구하는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;기울기와 y절편을 이용하는 방법은 초중고동안 비슷한 문제를 많이 풀며 익숙하지만 기울기가 무한, 즉 직선이 y축에 평행할 때 따로 조건을 분기해서 처리를 해야하는 번거로움이 있습니다.&lt;/p&gt;
&lt;p&gt;또한 이 방법들은 직선의 방정식을 이용하는 방법들이기에 교점이 선분안에 존재하는지 확인해야합니다.&lt;/p&gt;
&lt;p&gt;그래서 두 양 끝점을 종점으로 두는 벡터를 두고 그 벡터의 합을 이용해 직선을 표현하는 방식으로 교점을 구할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;방법&lt;/h2&gt;
&lt;p&gt;먼저 두 선분의 양 끝점 좌표를 각각 P1:x1,y1/P2:x2,y2/P3:x3,y3/P4:x4,y4라고 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이 때 두 선분을 포함하는 직선의 방정식을 매개변수 t와 s를 사용하여 나타내면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$P(t) = (1-t)P_1+tP_2$$
$$P(s) = (1-s)P_3+sP_4$$&lt;/p&gt;
&lt;p&gt;이 때 선분을 나타내려면 t와 s는 0에서 1까지의 값을 가져야합니다.&lt;/p&gt;
&lt;p&gt;두 선의 교점은 두 선이 모두 그 점을 지나므로 위 2개의 식을 아래와 같이 쓸 수 있습니다.&lt;/p&gt;
&lt;p&gt;$$(1-t)P_1+tP_2 = (1-s)P_3+sP_4$$&lt;/p&gt;
&lt;p&gt;이 식을 x좌표와 y좌표로 분해해보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$x_1+t(x_2-x_1)=x_3+s(x_4-x_3)\y_1+t(y_2-y_1)=y_3+s(y_4-y_3)$$&lt;/p&gt;
&lt;p&gt;위 식을 t와 s에 대한 식으로 정리합니다.&lt;/p&gt;
&lt;p&gt;$$(x_2-x_1)t-(x_4-x_3)s=x_3-x_1\(y_2-y_1)t-(y_4-y_3)s=y_3-y_1$$&lt;/p&gt;
&lt;p&gt;이 연립방정식을 행렬로 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\begin{pmatrix}x_2-x_1&amp;amp;-(x_4-x_3)\y_2-y_1&amp;amp;-(y_4-y_3)\end{pmatrix}\begin{pmatrix}t\s\end{pmatrix}=\begin{pmatrix}x_3-x_1\y_3-y_1\end{pmatrix}$$&lt;/p&gt;
&lt;p&gt;이제 이 식을 크레이머 공식을 이용해 t와 s에 대해 정리하면 다음값이 나옵니다.&lt;/p&gt;
&lt;p&gt;$$ t = \frac{(x_1-x_3)(y_3-y_4)-(y_1-y_3)(x_3-x_4)}{(x_1-x_2)(y_3-y_4)-(y_1-y_2)(x_3-x_4)}$$&lt;/p&gt;
&lt;p&gt;$$s = \frac{(x_2-x_1)(y_1-y_3)-(y_2-y_1)(x_1-x_3)}{(y_4-y_3)(x_2-x_1)-(x_4-x_3)(y_2-y_1)}$$&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;크라메르 공식은 행렬식을 이용하여 연립방정식의 해를 구하는 공식으로 일반화된 공식은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\bold A\bold x=\bold B$$&lt;/p&gt;
&lt;p&gt;위와 같은 조건일 때 $\bold x$의 각 원소의 해는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$x_i = \frac{\det A_i}{\det A}$$&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이제 t와 s를 모두 구했으므로 처음 직선의 방정식에 대입해보면 아래와 같이 답을 구할 수 있습니다.&lt;/p&gt;
&lt;p&gt;$$x=x_1+t(x_2-x_1)$$
$$y=y_1+t(y_2-y_1)$$&lt;/p&gt;
&lt;p&gt;이렇게 구했을때 구하는 도중 분모가 0이 되면 두 선은 평행하단 의미이고 분자,분모가 동시에 0이면 두 직선은 동일선상에 있습니다.&lt;/p&gt;
&lt;p&gt;s와 t가 &lt;strong&gt;0에서 1사이가 아닌 경우 두 선분은 교차하지 않습니다.&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>테트리스를 깨는 AI 제작 - 6 [본격 라이브러리 구현 1]</title><link>https://blog.ushiohayase.com/posts/tetris-ai-6/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-6/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Wed, 21 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;내용&lt;/h2&gt;
&lt;h3&gt;Tensor 클래스 구현&lt;/h3&gt;
&lt;p&gt;텐서는 신경망에서 모든 연산의 바탕으로 모든 연산은 텐서를 중심으로 이루어집니다.&lt;/p&gt;
&lt;p&gt;제가 경험하길 Tensorflow와 Pytorch를 써본 결과 텐서라는 데이터를 모델에 흐르게 하므로써 작동했으므로 이 라이브러리에서도 텐서를 중심으로 작동하도록 설계했습니다.&lt;/p&gt;
&lt;p&gt;텐서를 메모리에 저장할때 신경망은 성능을 최대한 끌어다 써야하기 때문에 당연히 1차원 배열형태로 저장했습니다.&lt;/p&gt;
&lt;p&gt;하지만 텐서는 개념적으로 1차원, 2차원, 3차원, 개념적으로는 n차원도 가능하기에 &lt;code&gt;strides_&lt;/code&gt;(각 차원까지 칸수)와 &lt;code&gt;shapes_&lt;/code&gt;(차원) 멤버 변수를 저장하고 관련 메소드를 지원함으로써 이 개념을 구현하였습니다.&lt;/p&gt;
&lt;p&gt;텐서는 각 텐서마다 고유한 이름과 uid를 가져야 하기에 &lt;code&gt;name_&lt;/code&gt;과 &lt;code&gt;uid_&lt;/code&gt; 멤버 변수를 가지고 데이터 타입을 위한 변수 &lt;code&gt;data_type_&lt;/code&gt;을 가집니다.&lt;/p&gt;
&lt;p&gt;이 때 일반적으로 신경망에서 텐서를 가장 많이 쓰는 가중치는 32비트 부동소수점으로 관리되기 때문에 float형으로 기본적으로 저장되게 설계했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    std::vector&amp;lt;int64_t&amp;gt; shape_;
    mutable std::vector&amp;lt;int64_t&amp;gt; strides_;
    cudnn_frontend::DataType_t data_type_ = cudnn_frontend::DataType_t::FLOAT;
    int64_t uid_ = tensor_uid_counter.fetch_add(1);
    std::string name_;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 왜 &lt;code&gt;strides_&lt;/code&gt;가 mutable로 선언되어있냐고 말할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;이유는 개념적으로 내부 변수가 변경되면 안될 &lt;code&gt;void calculate_strides_if_needed() const&lt;/code&gt;에서 계산을 할 때 &lt;code&gt;strides_&lt;/code&gt; 변수의 업데이트가 필요해서입니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;신경망은 병렬처리가 매우 핵심인 특성상 GPU를 많이 사용합니다.&lt;/p&gt;
&lt;p&gt;따라서 제 라이브러리도 CUDA와 cuDNN을 사용하는 것이고 따라서 이 텐서도 GPU에 저장될 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;그것을 위해 &lt;code&gt;unique_ptr&lt;/code&gt;, 스마트 포인터와 커스텀 소멸자로 메모리를 관리했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct CudaDeleter
{
    void operator()(void* ptr) const
    {
        if (ptr)
        {
            CUDA_CHECK(cudaFree(ptr));
        }
    }
};

struct HostDeleter
{
    void operator()(void* ptr) const
    {
        if (ptr)
        {
            delete[] static_cast&amp;lt;char*&amp;gt;(ptr); 
        }
    }
};

std::unique_ptr&amp;lt;char[], HostDeleter&amp;gt; h_data_ptr_;  // CPU 데이터 (바이트 배열로 관리)
std::unique_ptr&amp;lt;void, CudaDeleter&amp;gt; d_data_ptr_;    // GPU 데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 여기서 문제가 발생합니다.&lt;/p&gt;
&lt;p&gt;사용자(라이브러리 사용자, 프로그램 사용자 모두 포함)가 GPU나 CPU에 있는 텐서를 서로 다른 쪽으로 보낼 때 동기화 문제입니다.&lt;/p&gt;
&lt;p&gt;단순하게 생각하면 그냥 상대 장치에 공간을 할당하고 복사하고 기존 공간을 지우면 되는거 아니냐고 생각할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 자료들을 찾아본 결과 기존에 있는 공간을 유지해서 필요할 때 양쪽에 있는 두 공간을 동기화 시키는 방식도 있다는 걸 알았습니다.&lt;/p&gt;
&lt;p&gt;이 방식의 장점은 데이터를 CPU와 GPU 사이에 자주 이동시킬 때 효율적으로 동작하고 사용자에게 더 많은 최적화 가능성을 줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 단점은 구현이 복잡해지고 메모리가 낭비될 수 있다는 것입니다.&lt;/p&gt;
&lt;p&gt;저는 생각해본 결과 일단 지금은 이동하는 방식을 택하고 나중에 좀 더 많은 지식을 습득하고 이 쪽 분야에 더 익숙해지면 이런 동기화 방식을 적용해보는 것으로 선택했습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;텐서의 device, 그러니까 GPU쪽은 안의 데이터가 GPU 내에서만 수정되고 cuda 관련 함수들은 모두 void 포인터를 인자로 받기 때문에 void 스마트 포인터로 멤버 변수를 가지게 했습니다.&lt;/p&gt;
&lt;p&gt;하지만 host, CPU쪽은 데이터가 수정될 가능성이 조금이라도 있고 void 포인터로 받는 함수나 그런것도 없습니다.&lt;/p&gt;
&lt;p&gt;따라서 CPU 쪽 스마트 포인터는 char형 배열의 스마트 포인터로 관리해 바이트 단위의 배열로 기본적으로 저장을 하고 &lt;code&gt;data_type_&lt;/code&gt; 멤버 변수에 따라 런타임에 타입을 바꿔주어 수정할 수 있도록 설계했습니다.&lt;/p&gt;
&lt;h3&gt;Layer 클래스 구현&lt;/h3&gt;
&lt;p&gt;레이어는 Pytorch로 치면 Linear, conv2d와 같은 신경망의 층, 말 그대로 레이어입니다.&lt;/p&gt;
&lt;p&gt;이 레이어는 레이어 클래스를 가장 최상위 부모로 두고 그걸 상속해서 아래에 다른 실제 레이어들을 두는 것으로 설계했습니다.&lt;/p&gt;
&lt;p&gt;레이어 클래스에는 모든 레이어에 공통적으로 들어갈 이름, &lt;code&gt;name_&lt;/code&gt; 멤버 변수와 그에 관련된 메소드들이 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 신경망에서 모든 레이어들이 정의해야할 순전파, 역전파 관련 메소드들을 상속한 클래스에서 오버라이드해서 사용할 수 있도록 순수 가상 함수로 선언해뒀습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 순전파 그래프 구성 순수 가상 함수
    virtual std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt; add_forward_to_graph(
        std::shared_ptr&amp;lt;fe::graph::Graph&amp;gt;&amp;amp; graph,
        const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; input_tensor_graph_ref) = 0;

    // 역전파 그래프 구성 순수 가상 함수
    virtual std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt; add_backward_to_graph(
        std::shared_ptr&amp;lt;fe::graph::Graph&amp;gt;&amp;amp; graph,
        const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; output_grad_graph_ref,
        const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; fwd_input_graph_ref,  // 순전파 시의 입력
        const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp;
            fwd_output_graph_ref) = 0; 
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;저는 일단 Q 함수를 근사하는 신경망으로 CNN이 아닌 MLP를 사용해볼 것이기 때문에 컨볼루션 레이어는 아직 구현을 하진 않았고 Tensorflow의 명칭으론 Dense 레이어, Pytorch 명칭으로는 Linear 레이어를 구현했습니다.&lt;/p&gt;
&lt;p&gt;DenseLayer 클래스에서는 부모, Layer 클래스의 메소드들을 오버라이드해서 구현을 하고 그에 필요한 가중치와 편향, 그에 대응하는 기울기 멤버 함수들을 가지게 하였습니다.&lt;/p&gt;
&lt;p&gt;순전파와 역전파를 구성하는 메소드들은 모두 cuDNN의 그래프를 이용하여 구현하였습니다.&lt;/p&gt;
&lt;p&gt;정확히는 cuDNN의 그래프 API 중 frontend API 만 사용하여 구현하였습니다.&lt;/p&gt;
&lt;p&gt;이유는 cuDNN의 최신 버전이 9.10.0 버전이였는데 9버전 들어와서 백엔드쪽 API가 많은 API가 Deprecated되고 추가되고 삭제되는 등 뭔가 변화가 진행중인것 같아서였습니다.&lt;/p&gt;
&lt;p&gt;다시 돌아와서 순전파와 역전파 모두 cuDNN의 graph에 추가해서 연산하는 식으로 동작하게 설계했습니다.&lt;/p&gt;
&lt;p&gt;순전파에서는 Dense 레이어의 공식인 $XW+b=Y$에 따라 행렬 곱과 덧셈 연산을 정의하는 그래프 속성 객체를 만들고 그래프에 각 텐서를 등록하고 가상 텐서로 만들어 실행하는 구조를 가지게 하였습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt; DenseLayer::add_forward_to_graph(
    std::shared_ptr&amp;lt;fe::graph::Graph&amp;gt;&amp;amp; graph,
    const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; input_tensor_graph_ref)
{
    auto matmul_operation = fe::graph::Matmul_attributes().set_compute_data_type(weights_.get_data_type());
    auto add_operation = fe::graph::Pointwise_attributes()
                             .set_compute_data_type(bias_.get_data_type())
                             .set_mode(fe::PointwiseMode_t::ADD);

    auto weights_tensor_attributes = weights_.create_graph_tensor_attributes(graph);
    auto bias_tensor_attributes = bias_.create_graph_tensor_attributes(graph);

    // 가중치 행렬곱
    auto wegihts_output_tensor_attrubutes =
        graph-&amp;gt;tensor(graph-&amp;gt;matmul(input_tensor_graph_ref, weights_tensor_attributes, matmul_operation)
                          -&amp;gt;set_is_virtual(true)
                          .set_name(name_ + &quot;_weights_matmul_out&quot;));

    // 편향 덧셈
    auto output_tensor_attributes =
        graph-&amp;gt;tensor(graph-&amp;gt;pointwise(wegihts_output_tensor_attrubutes, bias_tensor_attributes, add_operation)
                          -&amp;gt;set_is_virtual(true)
                          .set_name(&quot;_bias_add_out&quot;));

    return output_tensor_attributes;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;역전파에서는 전에 증명했듯이 가중치의 손실함수에 대한 미분은 다음 층에서 흘러들어온 기울기에 입력 텐서를 전치한걸 행렬 곱 해준 값을 저장해줬고 편향에 대한 미분은 앞쪽에서 하진 않았지만 흘러들어온 기울기를 배치축에 대해 합산하는 것입니다.&lt;/p&gt;
&lt;p&gt;입력에 대한 손실함수의 미분도 앞서 한것처럼 흘러들어온 기울기의 전치와 가중치의 행렬곱이기에 행렬곱 연산 객체를 생성하고 그래프에 추가하고 구성했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt; DenseLayer::add_backward_to_graph(
    std::shared_ptr&amp;lt;fe::graph::Graph&amp;gt;&amp;amp; graph,
    const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; output_grad_graph_ref,
    const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; fwd_input_tensor_ref,  // 순전파 시의 입력
    const std::shared_ptr&amp;lt;fe::graph::Tensor_attributes&amp;gt;&amp;amp; fwd_output_tensor_ref)

{
    auto matmul_operation = fe::graph::Matmul_attributes().set_compute_data_type(weights_grad_.get_data_type());
    auto weights_grad_tensor_attributes = weights_grad_.create_graph_tensor_attributes(graph);

    // 순전파 입력 행렬 전치
    auto fwd_input_tensor_ref_shape = fwd_input_tensor_ref-&amp;gt;get_dim();
    auto fwd_input_tensor_ref_shape_size = fwd_input_tensor_ref_shape.size();
    std::swap(fwd_input_tensor_ref_shape[fwd_input_tensor_ref_shape_size - 1],
              fwd_input_tensor_ref_shape[fwd_input_tensor_ref_shape_size - 2]);

    auto fwd_input_graph_ref_T =
        graph-&amp;gt;tensor(fwd_input_tensor_ref-&amp;gt;set_dim(fwd_input_tensor_ref_shape).set_is_virtual(true));

    // 손실함수에 대한 가중치의 기울기 계산
    weights_grad_tensor_attributes =
        graph-&amp;gt;tensor(graph-&amp;gt;matmul(fwd_input_graph_ref_T, output_grad_graph_ref, matmul_operation)
                          -&amp;gt;set_is_virtual(true)
                          .set_name(name_ + &quot;_weights_matmul_out_bwd&quot;));

    // reduction 작업 attributes 정의
    auto reduction_operation = fe::graph::Pointwise_attributes()
                                   .set_compute_data_type(bias_grad_.get_data_type())
                                   .set_mode(fe::PointwiseMode_t::ADD)
                                   .set_axis(0);
    auto bias_grad_tensor_attributes = bias_grad_.create_graph_tensor_attributes(graph);

    // 손실함수에 대한 편향의 기울기 계산
    bias_grad_tensor_attributes = graph-&amp;gt;tensor(graph-&amp;gt;pointwise(output_grad_graph_ref, reduction_operation)
                                                    -&amp;gt;set_is_virtual(true)
                                                    .set_name(name_ + &quot;_bias_add_out_bwd&quot;));

    // 가중치 행렬 전치
    auto weights_tensor_attribute = weights_.create_graph_tensor_attributes(graph);
    auto weights_tensor_shape = weights_grad_tensor_attributes-&amp;gt;get_dim();
    auto weights_tensor_shape_size = weights_tensor_shape.size();
    std::swap(weights_tensor_shape[weights_tensor_shape_size - 1], weights_tensor_shape[weights_tensor_shape_size - 2]);

    auto weights_tensor_T = graph-&amp;gt;tensor(weights_tensor_attribute-&amp;gt;set_dim(weights_tensor_shape).set_is_virtual(true));

    // 손실함수에 대한 입력의 기울기 계산
    return graph-&amp;gt;tensor(graph-&amp;gt;matmul(output_grad_graph_ref, weights_tensor_T, matmul_operation)
                             -&amp;gt;set_is_virtual(true)
                             .set_name(name_ + &quot;_output_bwd&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;순전파 출력 텐서는 필요없는데 왜 받았냐고 할 수 있는데 그것은 가상 함수를 정의할 때 아직 다른 레이어는 미분을 유도해보지 않아서 잘 몰라 다른 레이어 클래스에서 사용할까봐 남겨놨습니다.&lt;/p&gt;
&lt;h2&gt;회고 및 앞으로 할 일&lt;/h2&gt;
&lt;p&gt;이 텐서 클래스와 레이어 클래스를 만들 때 GPU를 쉽게 사용하기 위해 cuDNN을 사용했는데 cuDNN이 개편되고 있어서 자료가 마구 섞여있는데다가 애초에 자료 자체도 많이 없어서 거의 공식 깃헙 예제와 공식 문서로만 알아내야해서 그 점이 힘들었습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>0-1 bfs [시간복잡도가 O(E+V)인 다익스트라]</title><link>https://blog.ushiohayase.com/posts/0-1-bfs/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/0-1-bfs/</guid><description>가중치의 0 또는 1일 때 시간 복잡도가 O(E+V)인 다익스트라 알고리즘의 변형 알고리즘</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;다익스트라 알고리즘은 간선의 개수가 $E$이고 정점의 개수가 $V$일 때 시간복잡도가 $O((E+V)\log V)$입니다.&lt;/p&gt;
&lt;p&gt;하지만 가중치가 0과 1(정확히는 양의 정수)만 존재할 시 이것을 더 최적화 할 수 있는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;그것이 바로 0-1 bfs입니다.&lt;/p&gt;
&lt;p&gt;어떻게 이게 가능하냐면 일반적인 다익스트라 알고리즘은 우선순위 큐를 사용하지만 이 알고리즘은 덱 자료구조를 이용하여 우선순위 큐를 대체하기 때문입니다.&lt;/p&gt;
&lt;h2&gt;동작 순서&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;덱(deque)을 사용합니다.&lt;/li&gt;
&lt;li&gt;덱에서 더 이상 꺼낼 노드가 없을 때까지 다음 과정을 반복합니다.
&lt;ol&gt;
&lt;li&gt;덱의 front에서 현재 노드를 꺼냅니다.&lt;/li&gt;
&lt;li&gt;연결된 인접 노드를 살펴봅니다.&lt;/li&gt;
&lt;li&gt;현재 노드까지의 거리 + 그 노드까지의 가중치 &amp;lt; 그 노드까지의 거리면 거리를 갱신합니다.
&lt;ol&gt;
&lt;li&gt;현재 노드에서 다음 노드로 가는 간선의 가중치가 0이면, 다음 노드를 덱의 &lt;strong&gt;앞&lt;/strong&gt;(front)에 넣습니다.&lt;/li&gt;
&lt;li&gt;가중치가 1 (또는 다른 양의 상수 W)이면, 다음 노드를 덱의 &lt;strong&gt;뒤&lt;/strong&gt;(back)에 넣습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;동작 원리&lt;/h2&gt;
&lt;p&gt;가중치가 0인 간선은 &quot;비용 없이&quot; 이동하는 것과 같습니다.&lt;/p&gt;
&lt;p&gt;따라서 이런 간선들은 최대한 먼저 탐색해도 현재까지의 누적 거리가 늘어나지 않습니다.&lt;/p&gt;
&lt;p&gt;덱의 앞에 넣음으로써 이들을 우선적으로 처리합니다.&lt;/p&gt;
&lt;p&gt;가중치가 1인 간선은 비용을 1 증가시킵니다.&lt;/p&gt;
&lt;p&gt;따라서 이들은 0인 간선들을 모두 처리한 후에 꺼냅니다.&lt;/p&gt;
&lt;p&gt;결과적으로, 덱에서 노드를 꺼낼 때 항상 현재까지의 누적 거리가 가장 작은 (또는 같은 거리 중에서는 먼저 탐색된) 노드를 꺼내게 되어 다익스트라 알고리즘과 유사한 효과를 냅니다.&lt;/p&gt;
&lt;h2&gt;시간복잡도 분석&lt;/h2&gt;
&lt;p&gt;아래 코드를 보면 알겠지만 정점($V$)동안 최악의 경우 모든 간선($E$)를 시간 복잡도 $O(1)$로 접근해서 탐색하므로 최종 시간복잡도는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$O(V+E)$$&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// C++ program to implement single source
// shortest path for a Binary Graph
#include&amp;lt;bits/stdc++.h&amp;gt;
using namespace std;

/* no.of vertices */
#define V 9

// a structure to represent edges
struct node
{
    // two variable one denote the node
    // and other the weight
    int to, weight;
};

// vector to store edges
vector &amp;lt;node&amp;gt; edges[V];

// Prints shortest distance from given source to
// every other vertex
void zeroOneBFS(int src)
{
    // Initialize distances from given source
    int dist[V];
    for (int i=0; i&amp;lt;V; i++)
        dist[i] = INT_MAX;

    // double ende queue to do BFS.
    deque &amp;lt;int&amp;gt; Q;
    dist[src] = 0;
    Q.push_back(src);

    while (!Q.empty())
    {
        int v = Q.front();
        Q.pop_front();

        for (int i=0; i&amp;lt;edges[v].size(); i++)
        {
            // checking for the optimal distance
            if (dist[edges[v][i].to] &amp;gt; dist[v] + edges[v][i].weight)
            {
                dist[edges[v][i].to] = dist[v] + edges[v][i].weight;

                // Put 0 weight edges to front and 1 weight
                // edges to back so that vertices are processed
                // in increasing order of weights.
                if (edges[v][i].weight == 0)
                    Q.push_front(edges[v][i].to);
                else
                    Q.push_back(edges[v][i].to);
            }
        }
    }

    // printing the shortest distances
    for (int i=0; i&amp;lt;V; i++)
        cout &amp;lt;&amp;lt; dist[i] &amp;lt;&amp;lt; &quot; &quot;;
}

void addEdge(int u, int v, int wt)
{
   edges[u].push_back({v, wt});
   edges[v].push_back({u, wt});
}

// Driver function
int main()
{
    addEdge(0, 1, 0);
    addEdge(0, 7, 1);
    addEdge(1, 7, 1);
    addEdge(1, 2, 1);
    addEdge(2, 3, 0);
    addEdge(2, 5, 0);
    addEdge(2, 8, 1);
    addEdge(3, 4, 1);
    addEdge(3, 5, 1);
    addEdge(4, 5, 1);
    addEdge(5, 6, 1);
    addEdge(6, 7, 1);
    addEdge(7, 8, 1);
    int src = 0;//source node
    zeroOneBFS(src);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/0-1-bfs-shortest-path-binary-graph/&quot;&gt;code from GeekforGeeks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;백준 예시 문제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13549&quot;&gt;13649번 숨바꼭질 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>다이얼 알고리즘 [시간복잡도가 O(E+WV)인 다익스트라]</title><link>https://blog.ushiohayase.com/posts/dials-algorithm/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/dials-algorithm/</guid><description>가중치의 최댓값이 W일 때 시간 복잡도가 O(E+WV)인 다익스트라 알고리즘의 변형 알고리즘</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;다익스트라 알고리즘은 간선의 개수가 $E$이고 정점의 개수가 $V$일 때 시간복잡도가 $O((E+V)\log V)$입니다.&lt;/p&gt;
&lt;p&gt;하지만 가중치의 최댓값이 더 작을 때 이걸 더 최적화할 수 있는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;그것이 바로 다이얼 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;이 알고리즘은 다익스트라 알고리즘과 비슷한 조건, 음수가 아닌 &lt;strong&gt;정수&lt;/strong&gt; 가중치 간선일 때 동작하고 특히 간선의 가중치가 작을 때 매우 효율적으로 동작합니다.&lt;/p&gt;
&lt;p&gt;어떻게 이게 가능하냐면 일반적인 다익스트라 알고리즘은 우선순위 큐를 사용하지만 이 알고리즘은 배열 형태의 자료구조를 이용하여 우선순위 큐를 대체하기 때문입니다.&lt;/p&gt;
&lt;h2&gt;작동 원리&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;버킷(B)이라고 부르는 배열 형태의 자료구조를 가중치의 최댓값의 크기의 길이 x 간선의 개수로 준비합니다. (이 때 각 인덱스는 거리를 의미합니다. &lt;em&gt;&lt;strong&gt;중요!!&lt;/strong&gt;&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;거리 배열(dist)을 준비하고 시작 정점의 거리는 0, 나머지 정점의 거리는 무한대로 초기화합니다.&lt;/li&gt;
&lt;li&gt;시작 정점을 버킷에 추가합니다.&lt;/li&gt;
&lt;li&gt;다음을 모든 정점이 처리되거나 버킷이 빌 때까지 반복합니다.
&lt;ol&gt;
&lt;li&gt;현재 가장 작은 인덱스를 가진, 비어있지 않은 버킷(B[i])을 찾습니다.&lt;/li&gt;
&lt;li&gt;버킷에서 정점 하나(u)를 꺼냅니다.&lt;/li&gt;
&lt;li&gt;정점 u에 연결된 모든 간선 (u, v)에 대해 다음을 수행합니다.
&lt;ol&gt;
&lt;li&gt;만약 u를 거쳐 v로 가는 경로가 기존에 알려진 경로보다 짧다면
&lt;ol&gt;
&lt;li&gt;거리배열(dist[v])를 업데이트(dist[u]+u에서 v까지의 가중치)합니다.&lt;/li&gt;
&lt;li&gt;v를 방문처리합니다.&lt;/li&gt;
&lt;li&gt;B[dist[v]]에 v를 추가합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;그림&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%981.jpg&quot; alt=&quot;dial1&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%982.jpg&quot; alt=&quot;dial2&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%983.jpg&quot; alt=&quot;dial3&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%984.jpg&quot; alt=&quot;dial4&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%985.jpg&quot; alt=&quot;dial5&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%986.jpg&quot; alt=&quot;dial6&quot; /&gt;
&lt;img src=&quot;./%EB%8B%A4%EC%9D%B4%EC%96%BC%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%987.jpg&quot; alt=&quot;dial7&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;limits&amp;gt;
#include &amp;lt;list&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

vector&amp;lt;int&amp;gt; dist;
vector&amp;lt;list&amp;lt;int&amp;gt;&amp;gt; buckets;
vector&amp;lt;list&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt; adj_list;
int num_vertices = 6;
int max_edge_w = 5;
int source = 0;
int max_weight = 7;

const int INF = numeric_limits&amp;lt;int&amp;gt;::max();

vector&amp;lt;int&amp;gt; dialAlgorithm()
{
    dist[source] = 0;

    int max_path_sum = (num_vertices - 1) * max_weight;

    buckets.resize(max_path_sum + 1);

    for (int i = 0; i &amp;lt; max_path_sum; ++i)
    {
        while (!buckets[i].empty())
        {
            int cur_vertex = buckets[i].front();
            buckets[i].pop_front();

            if (dist[cur_vertex] &amp;lt; i) continue;

            for (const auto&amp;amp; edge : adj_list[cur_vertex])
            {
                int next_vertex = edge.first;
                int edge_weight = edge.second;

                if (dist[cur_vertex] + edge_weight &amp;lt; dist[next_vertex])
                {
                    dist[next_vertex] = dist[cur_vertex] + edge_weight;
                    buckets[dist[next_vertex]].push_back(next_vertex);
                }
            }
        }
    }

    return dist;
}

int main()
{
    adj_list.resize(num_vertices);

    adj_list[0].push_back({1, 2});
    adj_list[0].push_back({2, 5});
    adj_list[1].push_back({0, 2});
    adj_list[1].push_back({2, 1});
    adj_list[1].push_back({3, 7});
    adj_list[2].push_back({0, 5});
    adj_list[2].push_back({1, 1});
    adj_list[2].push_back({3, 3});
    adj_list[2].push_back({4, 2});
    adj_list[3].push_back({1, 7});
    adj_list[3].push_back({2, 3});
    adj_list[3].push_back({4, 1});
    adj_list[3].push_back({5, 4});
    adj_list[4].push_back({2, 2});
    adj_list[4].push_back({3, 1});
    adj_list[4].push_back({5, 6});
    adj_list[5].push_back({3, 4});
    adj_list[5].push_back({4, 6});

    vector&amp;lt;int&amp;gt; shortest_distances = dialAlgorithm();

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;장점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;$W$가 작을 때: 힙을 사용하는 다익스트라 알고리즘($O(E log V)$ 또는 $O((E+V)log V)$)보다 빠를 수 있습니다. 특히, 그래프가 조밀(dense)하고 $W$가 작을 때 효과적입니다.&lt;/li&gt;
&lt;li&gt;구현: 개념적으로 힙보다 간단하게 느껴질 수 있습니다 (배열과 리스트만 사용).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;단점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;$W$가 클 때: $V\times W$ 항 때문에 성능이 매우 나빠질 수 있습니다. 이 경우 힙 기반 다익스트라가 훨씬 효율적입니다.&lt;/li&gt;
&lt;li&gt;음수 가중치: 다익스트라와 마찬가지로 음수 가중치 간선이 있으면 사용할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/dials-algorithm-optimized-dijkstra-for-small-range-weights&quot;&gt;dial&apos;s-algorithm GeekforGeeks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>테트리스를 깨는 AI 제작 - 5 [강화학습 알고리즘 선택 및 클래스 재설계]</title><link>https://blog.ushiohayase.com/posts/tetris-ai-5/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-5/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;오늘 한 것&lt;/h2&gt;
&lt;p&gt;강화학습 알고리즘 선택&lt;/p&gt;
&lt;p&gt;클래스 재설계&lt;/p&gt;
&lt;h2&gt;내용&lt;/h2&gt;
&lt;h3&gt;DQN(Deep Q-Network)&lt;/h3&gt;
&lt;p&gt;DQN을 이해하려면 먼저 강화학습을 알아야합니다.&lt;/p&gt;
&lt;p&gt;강화학습은 사람이 무언가 학습하는 과정과 유사합니다.&lt;/p&gt;
&lt;p&gt;어떤 행동을 하고 그에 대해 깨달음을 얻는 것처럼, 강화학습의 &lt;em&gt;에이전트&lt;/em&gt;(Agent)는 주어진 &lt;em&gt;환경&lt;/em&gt;(Environment)안에서 다양한 &lt;em&gt;행동&lt;/em&gt;(Action)을 시도합니다.&lt;/p&gt;
&lt;p&gt;각 &lt;em&gt;행동&lt;/em&gt;의 결과로 &lt;em&gt;환경&lt;/em&gt;은 새로운 &lt;em&gt;상태&lt;/em&gt;(State)로 변하고 ,&lt;em&gt;에이전트&lt;/em&gt;는 그 &lt;em&gt;행동&lt;/em&gt;이 얼마나 좋았는지 혹은 나빴는지를 나타내는 &lt;em&gt;보상&lt;/em&gt;(Reward)을 받습니다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;에이전트&lt;/em&gt;의 궁극적인 목표는 단순히 즉각적인 &lt;em&gt;보상&lt;/em&gt;이 아니라, 장기적으로 누적될 총 &lt;em&gt;보상&lt;/em&gt;을 최대화하는 최적의 행동 전략, 즉 &lt;em&gt;정책&lt;/em&gt;(Policy)을 학습하는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스에 이 개념을 적용해 보면, 에이전트는 떨어지는 블록을 조작하는 AI 프로그램이 됩니다.&lt;/p&gt;
&lt;p&gt;환경은 테트리스 게임 보드와 규칙 그 자체이며, 상태는 현재 보드 판에 블록이 쌓인 모습, 다음에 나올 블록의 종류 등을 포함할 수 있습니다.&lt;/p&gt;
&lt;p&gt;AI가 취할 수 있는 행동은 블록을 왼쪽이나 오른쪽으로 옮기거나, 회전시키거나, 아래로 빠르게 내리는 것들이죠. 그리고 라인을 한 줄 없앨 때마다 높은 점수(긍정적 보상)를, 게임이 끝나면 큰 감점(부정적 보상)을, 혹은 블록을 하나 놓을 때마다 작은 감점을 주는 식으로 보상 체계를 설계할 수 있습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이 때 AI는 좋은 행동과 나쁜 행동을 판단하기 위해 &lt;strong&gt;Q-러닝&lt;/strong&gt;(Q-Learning)과 &lt;strong&gt;Q 함수&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;Q 함수, $Q(s, a)$는 현재 상태 $s$에서 특정 행동 $a$를 취했을 때, 그 이후부터 게임이 끝날 때까지 받을 것으로 기대되는 총 보상을 수치화한 것입니다.&lt;/p&gt;
&lt;p&gt;만약 우리가 모든 가능한 상태와 행동 조합에 대한 최적의 Q 값, 즉 &lt;strong&gt;최적 Q 함수&lt;/strong&gt;, $Q*(s, a)$를 알 수만 있다면, AI는 매 순간 Q*(s, a)를 가장 크게 만드는 행동을 선택함으로써 완벽한 플레이를 펼칠 수 있을 것입니다.&lt;/p&gt;
&lt;p&gt;이 최적 Q 함수는 &lt;strong&gt;벨만 최적 방정식&lt;/strong&gt;(Bellman Optimality Equation)이라는 재귀적 관계로 표현됩니다.&lt;/p&gt;
&lt;h3&gt;벨만 방정식&lt;/h3&gt;
&lt;p&gt;처음에 보상은 에이전트가 한 행동에 대한 환경의 피드백이라고 했습니다.&lt;/p&gt;
&lt;p&gt;이 때 $s$ 상태에서 $a$ 행동을 했을 때 보상을 함수로 나타내면 아래와 같다고 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;$$r(s,a)$$&lt;/p&gt;
&lt;p&gt;보상함수는 $t$시간, 현재 상태 $s$에서 행동 $a$를 했을 때 얻을 수 있는 보상 $R_{t+1}$의 기댓값이라고 말합니다.&lt;/p&gt;
&lt;p&gt;그러면 어떤 행동을 했을 때 최종적으로 얻는 보상을 나타내면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$G_t = R_{t+1} + R_{t+2}+...+R_T$$&lt;/p&gt;
&lt;p&gt;하지만 이러한 식은 다음과 같은 단점이 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 받는 보상과 미래의 받는 보상을 구분하지 못하여 현재 이익이 큰 상태로 가는 행동을 취하지 못함&lt;/li&gt;
&lt;li&gt;한 번에 받는 보상과 여러번 나눠 받는 보상을 구분하지 못함&lt;/li&gt;
&lt;li&gt;시간이 무한대가 될 경우 보상의 합을 수치적으로 구분하지 못함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 문제점을 해결하기위해 미래에 받은 보상을 현재의 시점에서 고려할 때 감가하는 비율을 말하는 감가율을 각 항에 뒤로 갈수록 차수를 높여가며 곱해줍니다.&lt;/p&gt;
&lt;p&gt;$$G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2R_{t+3}+...+\gamma^{T-t-1}R_T$$&lt;/p&gt;
&lt;p&gt;이제 이 $G_t$를 반환값이라고 합니다.&lt;/p&gt;
&lt;p&gt;우리는 에이전트가 특정 상태에서 행동을 선택하는 기준인 가치함수를 이제 거의 다 찾았습니다.&lt;/p&gt;
&lt;p&gt;이제 가치함수는 반환값의 기댓값으로 정의됩니다. &lt;em&gt;(아직 원리를 이해못해서 추후에 추가예정)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;위에서 설명한 가치함수는 상태의 가치를 판단하는 &apos;상태 가치함수&apos;입니다.&lt;/p&gt;
&lt;p&gt;상태 가치함수를 통해 다음 상태들의 가치를 판단할 수 있고, 이를 바탕으로 더 높은 가치를 가지고 있는 다음 상태로 가기 위한 행동을 선택하여 상태를 이동시킬 것입니다.&lt;/p&gt;
&lt;p&gt;하지만 그러기 위해선 다음 상태에 대한 정보를 알아야하고, 그 상태로 가기위한 행동을 선택해도 그 상태에서 더 못가는 상태가 될 수도 있습니다.&lt;/p&gt;
&lt;p&gt;따라서 상태말고 행동에 대한 가치 함수도 구할 수 있어야 합니다.&lt;/p&gt;
&lt;p&gt;이것이 Q함수입니다.&lt;/p&gt;
&lt;p&gt;Q함수는 다음과 같이 정의되고  특정 상태 s에서 특정 행동 a를 취했을 때 받을 반환값에 대한 기댓값이라 합니다.&lt;/p&gt;
&lt;p&gt;$$q_\pi(s,a)=\it E_\pi[G_t|S_t=s,A_t=a]$$&lt;/p&gt;
&lt;p&gt;이제 벨만 방정식의 2개중 1개인 벨만 기대 방정식을 서술할 수 있습니다.&lt;/p&gt;
&lt;p&gt;벨만 기대 방정식은 가치함수식에서 유도되는 것인데 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\begin{aligned} v(s)&amp;amp;= \it E[G_t|S_t=s] \&amp;amp; = E[R_{t+1}+\gamma R_{t+2}+\gamma^2R_{t+3}+...|S_t=s] \&amp;amp; = E[R_{t+1}+\gamma(R_{t+2}+\gamma R_{t+3}+...)|S_t=s] \&amp;amp; = E[R_{t+1}+\gamma G_{t+1}|S_t=s] \&amp;amp; = E[R_{t+1}+\gamma v(S_{t+1})|S_t=s] \end{aligned} $$&lt;/p&gt;
&lt;p&gt;Q함수 또한 변형하면 다음과 같은 식이 유도됩니다.&lt;/p&gt;
&lt;p&gt;$$q_\pi(s,a) = E_\pi[R_{t+1}+\gamma q_\pi(s_{t+1}, A_{t+1})|s_t=s,A_t=a]$$&lt;/p&gt;
&lt;p&gt;이 수식들을 살펴보면 다음 상태의 가치함수를 통해 현재 상태의 가치함수를 도출한다는 걸 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;위에서 설명한 가치함수로부터 기댓값을 계산하려면 앞으로 받을 모든 보상에 대해 고려해야 하므로 비효율적입니다.&lt;/p&gt;
&lt;p&gt;따라서 하나의 수식으로 풀어내는 이 방법보단 여러 번의 연속적인 계산으로 가치함수의 참 값을 알아내는 방법이 효율적입니다.&lt;/p&gt;
&lt;p&gt;이 방법에 사용하는 수식이 바로 벨만 기대 방정식입니다.&lt;/p&gt;
&lt;p&gt;그럼 두번째 벨만 방정식인 벨만 최적 방정식에 대해 설명하기위해 최적 가치함수를 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;최적 가치함수란 현재 상태에서 앞으로 가장 많은 보상을 받을 정책을 따랐을 때의 가치함수입니다.&lt;/p&gt;
&lt;p&gt;즉, 현재 환경에서 취할 수 있는 가장 높은 값의 보상 총합입니다.&lt;/p&gt;
&lt;p&gt;벨만 최적 방정식은 위에서 설명한 최적 가치함수를 벨만 기대 방정식처럼 현재 상태의 최적 가치함수와 다음 상태의 최적 가치함수 사이의 관계로 나타낸 식입니다.&lt;/p&gt;
&lt;p&gt;$$v^\star(s) = max_aE[R_{t+1}+\gamma v^\star(S_{t+1})|S_t=s,A_t=a],\ q^\star(s,a) = max_aE[R_{t+1}+\gamma q^\star(S_{t+1},a^\prime)|S_t=s,A_t=a]$$&lt;/p&gt;
&lt;h3&gt;다시 DQN&lt;/h3&gt;
&lt;p&gt;Q-러닝은 이 벨만 방정식을 바탕으로 Q 값을 점진적으로 업데이트해 나갑니다.&lt;/p&gt;
&lt;p&gt;AI가 행동 $a$를 취해 상태 $s$에서 $s&apos;$로 이동하고 보상 $r$을 받으면, $Q(s, a)$ 값을 &quot;실제로 받은 보상 $r$과 다음 상태 s&apos;에서의 예상되는 최대 미래 가치 합&quot;에 더 가깝도록 조금씩 수정하는 것입니다.&lt;/p&gt;
&lt;p&gt;이 과정을 수없이 반복하면 $Q(s, a)$는 점차 $Q^\star(s, a)$에 수렴하게 됩니다.&lt;/p&gt;
&lt;p&gt;초기 Q-러닝은 이러한 Q 값들을 거대한 표(테이블)에 저장하고 관리하는 테이블 방식을 사용했습니다.&lt;/p&gt;
&lt;p&gt;상태와 행동의 종류가 적다면 이 방식도 효과적이지만, 테트리스처럼 가능한 보드 상태가 거의 무한대에 가까운 게임에서는 테이블 방식은 현실적으로 불가능합니다.&lt;/p&gt;
&lt;p&gt;테이블 방식 Q-러닝의 명확한 한계, 즉 방대한 상태 공간 문제를 해결하기 위해 등장한 것이 바로 &lt;strong&gt;DQN&lt;/strong&gt;(Deep Q-Network)입니다.&lt;/p&gt;
&lt;p&gt;DQN의 핵심 아이디어는 Q 함수 자체를 거대한 표 대신, &lt;strong&gt;신경망&lt;/strong&gt;(Neural Network)을 사용하여 근사하는 것입니다.&lt;/p&gt;
&lt;p&gt;즉, $Q(s, a; θ) ≈ Q*(s, a)$ 와 같이, 신경망이 상태 s를 입력으로 받아 각 행동 $a$에 대한 Q 값을 출력하도록 학습시키는 것입니다.&lt;/p&gt;
&lt;p&gt;여기서 $θ$는 신경망을 구성하는 수많은 연결 가중치와 편향 같은 학습 가능한 파라미터들을 의미합니다.&lt;/p&gt;
&lt;p&gt;이 때 신경망은 손실함수를 이용해 경사하강법과 역전파 알고리즘으로 Q함수를 근사해나가게 됩니다.&lt;/p&gt;
&lt;p&gt;단순히 신경망으로 Q 함수를 근사하는 것만으로는 안정적인 학습이 어렵습니다. DQN이 획기적인 성공을 거둘 수 있었던 배경에는 다음과 같은 핵심적인 기법들이 자리 잡고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;경험 리플레이 (Experience Replay): 사람이 과거의 경험을 곱씹으며 배우듯, DQN도 에이전트가 겪었던 과거의 경험, 즉 (현재 상태, 했던 행동, 받은 보상, 다음 상태)의 묶음을 리플레이 메모리라는 곳에 차곡차곡 쌓아둡니다. 신경망을 학습시킬 때는 이 메모리에서 무작위로 여러 개의 경험을 꺼내와 한 묶음(미니배치)으로 만들어 사용합니다. 이렇게 하면 몇 가지 중요한 이점이 생깁니다. 첫째, 하나의 경험을 여러 번 학습에 재사용할 수 있어 데이터 효율성이 크게 높아집니다. 둘째, 시간 순서대로 경험을 학습할 때 발생하는 데이터 간의 높은 상관관계를 줄여 학습을 더 안정적으로 만듭니다. 마치 교과서의 문제를 순서대로 풀기보다 여러 단원의 문제를 섞어서 푸는 것이 더 효과적인 것과 비슷합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;타겟 네트워크 분리 (Fixed Q-Targets / Target Network): DQN 학습에서 손실을 계산할 때 사용되는 &apos;목표 Q 값&apos; 역시 학습 중인 신경망 자신을 통해 계산된다면, 목표가 계속 흔들리면서 학습이 불안정해질 수 있습니다. 축구에서 움직이는 골대에 공을 차 넣으려는 것과 비슷하죠. 이 문제를 해결하기 위해 DQN은 두 개의 신경망을 사용합니다. 하나는 실제 행동을 결정하고 주로 학습되는 &lt;strong&gt;주 네트워크&lt;/strong&gt;(main network)이고, 다른 하나는 목표 Q 값을 계산하는 데만 사용되는 &lt;strong&gt;타겟 네트워크&lt;/strong&gt;(target network)입니다. 타겟 네트워크의 가중치는 주 네트워크의 가중치로 주기적으로 (예: 몇천 번의 학습 스텝마다) 한 번씩 업데이트(복사)됩니다. 이렇게 타겟 값을 일정 기간 동안 고정시킴으로써 학습 과정을 훨씬 안정적으로 만들 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 외에도 오차가 너무 클 때 학습이 불안정해지는 것을 막기 위해 &lt;strong&gt;오차 클리핑&lt;/strong&gt;(Error Clipping)을 사용하거나, 다양한 게임 환경에 유연하게 대응하기 위해 보상 클리핑(Reward Clipping) 같은 기법들이 사용되기도 합니다.&lt;/p&gt;
&lt;p&gt;이 모든 요소들이 어우러져 DQN의 학습 과정은 다음과 같이 진행됩니다.&lt;/p&gt;
&lt;p&gt;먼저, 주 네트워크와 타겟 네트워크를 (보통 무작위 값으로) 초기화하고, 경험 리플레이 메모리도 비어있는 상태로 시작합니다.&lt;/p&gt;
&lt;p&gt;AI 에이전트는 테트리스 게임을 시작하여 현재 상태를 관찰하고, Epsilon-Greedy 전략에 따라 행동을 선택합니다. 선택한 행동을 수행하면 게임 환경으로부터 보상과 다음 상태를 전달받고, 이 한 번의 경험 (상태, 행동, 보상, 다음 상태)을 리플레이 메모리에 저장합니다. 리플레이 메모리에 충분한 경험이 쌓이면, 여기서 무작위로 미니배치만큼의 경험들을 꺼내옵니다.&lt;/p&gt;
&lt;p&gt;각 경험에 대해 타겟 네트워크를 사용하여 목표 Q 값을 계산하고, 주 네트워크가 예측한 Q 값과의 차이(MSE 손실)를 구합니다.&lt;/p&gt;
&lt;p&gt;이 손실을 줄이기 위해 주 네트워크의 가중치를 역전파 알고리즘으로 업데이트합니다.&lt;/p&gt;
&lt;p&gt;그리고 일정 주기마다 주 네트워크의 가중치를 타겟 네트워크로 복사합니다.&lt;/p&gt;
&lt;h2&gt;텐서와 레이어 클래스&lt;/h2&gt;
&lt;h3&gt;ushionn::Tensor 클래스&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;주요 멤버 변수:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;std::vector&amp;lt;int64_t&amp;gt; shape_: 텐서의 각 차원 크기를 저장 (예: {배치, 채널, 높이, 너비}).&lt;/li&gt;
&lt;li&gt;mutable std::vector&amp;lt;int64_t&amp;gt; strides_: NCHW 또는 NHWC 같은 메모리 레이아웃에 따른 각 차원의 스트라이드 값.&lt;/li&gt;
&lt;li&gt;get_strides() 호출 시 필요에 따라 계산 후 캐싱.&lt;/li&gt;
&lt;li&gt;cudnn_frontend::DataType_t data_type_: 텐서 데이터의 타입 (예: FLOAT, HALF, INT8).&lt;/li&gt;
&lt;li&gt;int64_t uid_: UshioNN 라이브러리 내에서 각 텐서를 고유하게 식별하는 ID.&lt;/li&gt;
&lt;li&gt;std::string name_: 디버깅 및 로깅을 위한 텐서의 이름 (선택적).&lt;/li&gt;
&lt;li&gt;bool is_virtual_: cuDNN 연산 그래프 API 사용 시, 그래프 내 중간 결과물로 실제 메모리 할당이 없을 수 있는 가상 텐서 여부. (DQN 구현 시 개별 연산 위주라면 항상 false일 수 있음).&lt;/li&gt;
&lt;li&gt;std::unique_ptr&amp;lt;char[], HostDeleter&amp;gt; h_data_ptr_: CPU 호스트 메모리에 대한 스마트 포인터. char[]로 관리하여 다양한 데이터 타입을 바이트 단위로 다룰 수 있게 하고, HostDeleter는 delete[] 호출.&lt;/li&gt;
&lt;li&gt;std::unique_ptr&amp;lt;void, CudaDeleter&amp;gt; d_data_ptr_: GPU 디바이스 메모리에 대한 스마트 포인터. void*로 일반성을 확보하고, CudaDeleter는 cudaFree 호출.&lt;/li&gt;
&lt;li&gt;DataLocation current_location_: enum class DataLocation { NONE, HOST, DEVICE } 중 하나의 값을 가지며, 현재 데이터가 유일하게 존재하는 위치를 명시.&lt;/li&gt;
&lt;li&gt;bool strides_dirty_: 스트라이드 재계산 필요 여부 플래그.&lt;/li&gt;
&lt;li&gt;size_t size_in_bytes_cache_: 텐서 데이터의 총 바이트 크기 (계산 후 캐싱).&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;주요 public 메소드:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Tensor(const std::vector&amp;lt;int64_t&amp;gt;&amp;amp; shape, ...): 다양한 생성자 (shape만, CPU 데이터로 초기화 등).&lt;/li&gt;
&lt;li&gt;const std::vector&amp;lt;int64_t&amp;gt;&amp;amp; get_shape() const: 텐서의 shape 반환.&lt;/li&gt;
&lt;li&gt;const std::vector&amp;lt;int64_t&amp;gt;&amp;amp; get_strides() const: 텐서의 strides 반환 (내부적으로 계산).&lt;/li&gt;
&lt;li&gt;cudnn_frontend::DataType_t get_data_type() const: 데이터 타입 반환.&lt;/li&gt;
&lt;li&gt;int64_t get_uid() const: UID 반환.&lt;/li&gt;
&lt;li&gt;size_t get_num_elements() const: 총 원소 개수 반환.&lt;/li&gt;
&lt;li&gt;size_t get_size_in_bytes() const: 총 바이트 크기 반환.&lt;/li&gt;
&lt;li&gt;DataLocation get_data_location() const: 현재 데이터 위치 반환.&lt;/li&gt;
&lt;li&gt;bool is_on_host() const: 데이터가 CPU에 있는지 확인.&lt;/li&gt;
&lt;li&gt;bool is_on_device() const: 데이터가 GPU에 있는지 확인.&lt;/li&gt;
&lt;li&gt;void* get_mutable_host_ptr(): CPU 데이터에 대한 수정 가능한 포인터 반환. (데이터가 CPU에 없으면 에러).&lt;/li&gt;
&lt;li&gt;const void* get_host_ptr() const: CPU 데이터에 대한 읽기 전용 포인터 반환. (데이터가 CPU에 없으면 에러).&lt;/li&gt;
&lt;li&gt;void* get_mutable_device_ptr(): GPU 데이터에 대한 수정 가능한 포인터 반환. (데이터가 GPU에 없으면 에러).&lt;/li&gt;
&lt;li&gt;const void* get_device_ptr() const: GPU 데이터에 대한 읽기 전용 포인터 반환. (데이터가 GPU에 없으면 에러).&lt;/li&gt;
&lt;li&gt;void to_device(cudaStream_t stream = nullptr): CPU에 있는 데이터를 GPU로 &quot;이동&quot;. 기존 CPU 메모리는 해제되고 current_location_은 DEVICE로 변경. 이미 GPU에 있다면 아무 작업 안 함.&lt;/li&gt;
&lt;li&gt;void to_host(cudaStream_t stream = nullptr): GPU에 있는 데이터를 CPU로 &quot;이동&quot;. 기존 GPU 메모리는 해제되고 current_location_은 HOST로 변경. 이미 CPU에 있다면 아무 작업 안 함.&lt;/li&gt;
&lt;li&gt;void fill_from_host(const void* host_data_ptr, size_t num_bytes): 외부 CPU 데이터로 텐서 내용을 채움. 데이터는 CPU에 위치하게 되며, 만약 이전에 GPU에 있었다면 GPU 메모리는 해제.&lt;/li&gt;
&lt;li&gt;void print_meta_info(const std::string&amp;amp; header = &quot;&quot;) const: 텐서의 메타 정보(shape, data type, location 등) 출력 (디버깅용).&lt;/li&gt;
&lt;li&gt;std::shared_ptr&amp;lt;cudnn_frontend::graph::Tensor&amp;gt; create_graph_tensor_attributes(...)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ushionn::Layer (추상 베이스 클래스)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;주요 멤버 변수 (protected):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;std::string name_: 레이어의 이름 (디버깅 및 식별용).&lt;/li&gt;
&lt;li&gt;bool trainable_: 이 레이어가 학습 가능한 파라미터를 가졌는지 여부.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;주요 순수 가상 메소드 (public):&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;virtual Tensor forward(const Tensor&amp;amp; input) = 0;: 순전파 연산을 수행. 입력 Tensor를 받아 처리한 후 출력 Tensor를 반환. 모든 데이터 연산은 GPU에서 수행되는 것을 목표로 함.&lt;/li&gt;
&lt;li&gt;virtual Tensor backward(const Tensor&amp;amp; output_gradient) = 0;: 역전파 연산을 수행. 출력층으로부터 전달된 그래디언트(output_gradient)를 받아 입력층으로 전달할 그래디언트를 계산하고, 내부 파라미터가 있다면 파라미터에 대한 그래디언트도 계산하여 저장.&lt;/li&gt;
&lt;li&gt;virtual std::vector&amp;lt;Tensor*&amp;gt; get_parameters(): 학습 가능한 파라미터 Tensor들의 포인터 리스트 반환 (예: 가중치, 편향). 파라미터가 없으면 빈 리스트 반환.&lt;/li&gt;
&lt;li&gt;virtual std::vector&amp;lt;Tensor*&amp;gt; get_gradients(): get_parameters()에 대응되는 그래디언트 Tensor들의 포인터 리스트 반환.&lt;/li&gt;
&lt;li&gt;virtual void initialize_parameters(unsigned long long seed = 0): 학습 가능한 파라미터 초기화 (예: Xavier, He 초기화).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;회고 및 앞으로 할 일&lt;/h2&gt;
&lt;p&gt;아직 이해못한 부분도 많고 부족한 부분도 많은데 계속 열심히 해나가야겠습니다. 이 클래스는 추후 언제든 설계가 바뀔 수 있습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>테트리스를 깨는 AI 제작 - 4 [경사하강법을 이용한 역전파 알고리즘 수학적 계산]</title><link>https://blog.ushiohayase.com/posts/tetris-ai-4/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-4/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;오늘 한 것&lt;/h2&gt;
&lt;p&gt;역전파 수학적 정의 알아보기&lt;/p&gt;
&lt;p&gt;행렬 형태로 확장해서 알아보기&lt;/p&gt;
&lt;p&gt;행렬의 미분 형태로 확장해서 알기&lt;/p&gt;
&lt;p&gt;코드로 구현하기&lt;/p&gt;
&lt;h2&gt;역전파 알고리즘 정리&lt;/h2&gt;
&lt;p&gt;기본적으로 경사하강법을 이용한 역전파(Backpropagation) 알고리즘은 손실함수(오차함수; Loss Function, Cost Funcion)에
대해 각 가중치 값을 미분해 기울기 값을 구한다음 원래 가중치에서 빼주는 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;일단 이 블로그에서는 기본적으로 고등학교 수준의 미적분(다항함수의 미분)과 스칼라, 벡터, 행렬에 대한 개념은 알고있다고 가정하고 내용을 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 저희가 고등학교때 배웠던 미적분의 개념을 떠올려보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\frac{dy}{dx} = \lim_{h\rightarrow 0} \frac{f(x+h)-f(x)}{h}=f^\prime(x)$$&lt;/p&gt;
&lt;p&gt;이것은 &lt;strong&gt;스칼라 정의역의 원소&lt;/strong&gt;를 스칼라 &lt;strong&gt;공역에 있는 원소&lt;/strong&gt;에 대응시키는 함수 $f$에 대한 미분으로 볼 수 있습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이제 &lt;strong&gt;입력이 스칼라&lt;/strong&gt;이고 &lt;strong&gt;출력이 스칼라&lt;/strong&gt;인 함수로 바꿀 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 함수는 당연하겠지만 한 스칼라에는 하나의 스칼라만 미분 가능하기때문에 원하는 변수만 취급하고 다른 변수는 상수로 취급하는 편미분을 이용해야합니다.&lt;/p&gt;
&lt;p&gt;여기서 추가로 저희는 표기법을 알아야합니다.&lt;/p&gt;
&lt;p&gt;행렬 미분에서 표기법은 2가지가 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;분자 표기법 : 미분 대상(분자)의 차원을 행으로, 미분 변수(분모)의 차원을 열로 배치합니다.&lt;/li&gt;
&lt;li&gt;분모 표기법 : 미분 변수(분모)의 차원을 행으로, 미분 대상(분자)의 차원을 열로 배치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 표기법은 전치 관계에 있어서 주의해서 봐야합니다.&lt;/p&gt;
&lt;p&gt;벡터-스칼라 미분으로 돌아가서 이걸 이제 분모기준 표기법으로 표기하면 각 $y$에 대해 벡터 $\bold{x}$의 원소로 편미분 한 것과 같으니
다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\bold{x}= \begin{bmatrix} x_1 \ x_2 \ ...\ x_n \end{bmatrix}, f(\bold{x})= y, f^\prime(\bold{x})=\begin{bmatrix} \frac{\partial y}{\partial x_1}\ \frac{\partial y}{\partial x_2} \...\\frac{\partial y}{\partial x_n} \end{bmatrix} $$&lt;/p&gt;
&lt;p&gt;벡터-스칼라 미분도 알아봤으니 저희는 이제 스칼라-벡터 함수도 미분을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 함수의 정의와 미분 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\bold f(x) = \begin{bmatrix}f_1(x)\f_2(x)\...\f_m(x)\end{bmatrix}, \frac{\partial \bold y}{\partial x}=\bold f^\prime(x)=\begin{bmatrix}\frac{\partial y_1}{\partial x}\ \frac{\partial y_2}{\partial x} \ ... \ \frac{\partial y_m}{\partial x} \end{bmatrix}$$&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이 생각을 더 확장해서 이제 저희는 &lt;strong&gt;입력도 벡터&lt;/strong&gt;이고 &lt;strong&gt;출력도 벡터&lt;/strong&gt;인 함수를 생각할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그러면 그 결과는 행렬이 나오고 그 행렬을 &lt;em&gt;자코비안 행렬&lt;/em&gt;이라 부르며 보통 벡터를 다른 벡터로 변환시키므로 선형변환입니다.&lt;/p&gt;
&lt;p&gt;$$\bold x = \begin{bmatrix} x_1 \ x_2 \ ...\ x_n \end{bmatrix},  \bold y = \begin{bmatrix} y_1 \ y_2 \ ...\ y_n \end{bmatrix}, \frac{\partial \bold y}{\partial \bold x} = \begin{bmatrix} \frac{\partial y_1}{\partial x_1} &amp;amp; \frac{\partial y_1}{\partial x_2} &amp;amp; \cdots &amp;amp; \frac{\partial y_1}{\partial x_n} \ \frac{\partial y_2}{\partial x_1} &amp;amp; \frac{\partial y_2}{\partial x_2} &amp;amp; \cdots &amp;amp; \frac{\partial y_2}{\partial x_n} \ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \ \frac{\partial y_m}{\partial x_1} &amp;amp; \frac{\partial y_m}{\partial x_2} &amp;amp; \cdots &amp;amp; \frac{\partial y_m}{\partial x_n} \end{bmatrix}$$&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;마지막으로 행렬 함수의 미분까지 저희는 정의할 수 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;입력이 행렬&lt;/strong&gt;이고 &lt;strong&gt;출력이 스칼라&lt;/strong&gt;인 함수의 미분&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;$$\frac{\partial y}{\partial \bold X} = \begin{bmatrix} \frac{\partial y}{\partial x_{11}} &amp;amp; \frac{\partial y}{\partial x_{12}} &amp;amp; \cdots &amp;amp; \frac{\partial y}{\partial x_{1n}} \ \frac{\partial y}{\partial x_{21}} &amp;amp; \frac{\partial y}{\partial x_{22}} &amp;amp; \cdots &amp;amp; \frac{\partial y}{\partial x_{2n}} \ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \ \frac{\partial y}{\partial x_{m1}} &amp;amp; \frac{\partial y}{\partial x_{m2}} &amp;amp; \cdots &amp;amp; \frac{\partial y}{\partial x_{mn}} \end{bmatrix}$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;입력이 스칼라&lt;/strong&gt;이고 &lt;strong&gt;출력이 행렬&lt;/strong&gt;인 함수의 미분&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;$$\frac{\partial \bold Y}{\partial x} = \begin{bmatrix} \frac{\partial y_{11}}{\partial x} &amp;amp; \frac{\partial y_{21}}{\partial x} &amp;amp; \cdots &amp;amp; \frac{\partial y_{n1}}{\partial x} \ \frac{\partial y_{12}}{\partial x} &amp;amp; \frac{\partial y_{22}}{\partial x} &amp;amp; \cdots &amp;amp; \frac{\partial y_{n2}}{\partial x} \ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \ \frac{\partial y_{1m}}{\partial x} &amp;amp; \frac{\partial y_{2m}}{\partial x} &amp;amp; \cdots &amp;amp; \frac{\partial y_{nm}}{\partial x} \end{bmatrix}$$&lt;/p&gt;
&lt;p&gt;이제 모든 준비를 마쳤고 본격적으로 신경망을 들여다 볼 차례입니다.&lt;/p&gt;
&lt;p&gt;기본적인 신경망에서 선형 레이어는 다음과 같은 수식으로 이루어집니다.&lt;/p&gt;
&lt;p&gt;$$\bold x^T \bold \it W + \bold b = \bold y$$&lt;/p&gt;
&lt;p&gt;이 때, $\bold x$는 입력 벡터, $\bold W$는 가중치 행렬, $\bold b$는 편향 벡터, $\bold y$는 출력 벡터입니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;신경망은 오차함수(Loss Function)을 미분의 연쇄 법칙(Chain rule)에 따라 각 가중치와 입력에 대해 미분해 가면서 기울기를
거슬러 올라가며 계산합니다.&lt;/p&gt;
&lt;p&gt;이 때 범위를 좁혀 오차함수의 값을 $E_{total}$이라고 하고 레이어가 한 층이라 할 때 역전파를 보면 가중치 $W$에 대해 미분을 하면
다음과 같이 식이 구성됩니다. (활성화 함수는 계산이 복잡해지니 일단 제외)&lt;/p&gt;
&lt;p&gt;$$\frac{\partial E_{total}}{\partial \bold \it W}=\frac{\partial E_{total}}{\partial \bold y}\frac{\partial \bold y}{\partial \bold \it W}$$&lt;/p&gt;
&lt;p&gt;이 때 $\frac{\partial \bold y}{\partial \bold W}$는 3차원 텐서라 컴퓨터에 적합한 자료구조가 아닙니다.&lt;/p&gt;
&lt;p&gt;따라서 이 식을 수정할 수 있는지 보기위해 전개해보겠습니다.&lt;/p&gt;
&lt;p&gt;예를 들어 $\bold y = \bold \it W\bold x(\bold \it W \in \R^{m\times n},\bold x\in \R^n)$일 때
각 원소는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\frac{\partial y_i}{\partial \bold \it W_{jk}} = \begin{cases}x_k &amp;amp;\text{if i = j} \ 0 &amp;amp;\text{otherwise}\end{cases}$$&lt;/p&gt;
&lt;p&gt;즉 이 연산은 i와 j가 같을 때만 원소가 존재합니다.&lt;/p&gt;
&lt;p&gt;이것과 같은 것을 생각해보면 나타나는 것이 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\frac{\partial E_{total}}{\partial \bold y}\bold x^T$$&lt;/p&gt;
&lt;p&gt;이것은  &lt;strong&gt;다음 층에서 건너들어온 기울기&lt;/strong&gt;와 &lt;strong&gt;입력&lt;/strong&gt;을 &lt;strong&gt;외적&lt;/strong&gt;(outer product)한것과 같습니다.&lt;/p&gt;
&lt;p&gt;이 결과는 3차원 텐서의 축소된 표현으로 가능한 이유는 연산의 구조상 특정 차원이 불필요하게 중복되기 때문입니다.&lt;/p&gt;
&lt;p&gt;따라서 외적을 이용해서 컴퓨터에서 기울기 계산을 &lt;em&gt;빠르게 처리&lt;/em&gt;합니다.&lt;/p&gt;
&lt;p&gt;이러한 생각을 계속 해보면 &lt;em&gt;앞쪽으로 전달할 입력&lt;/em&gt;에 대한 손실함수의 미분도 금방 생각해낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;$$\frac{\partial E_{total}}{\partial \bold x} = \frac{\partial E_{total}}{\partial \bold y}\frac{\partial \bold y}{\partial \bold x}=\frac{\partial E_{total}}{\partial \bold y}\bold \it W$$&lt;/p&gt;
&lt;p&gt;즉, 오차함수에 대한 입력의 미분은 &lt;strong&gt;가중치 행렬&lt;/strong&gt;($\bold \it W$)이 된다는 걸 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 블로그 포스트에서는 계산의 간단함을 위해 한 층의 선형 레이어 모델로 가정하고 활성화 함수도 제외했지만 실제 상황에서는
일반적으로 층이 여러개이고 활성화 함수도 존재해 체인 룰을 이용해 미분을 계속해 나가야 합니다.&lt;/p&gt;
&lt;h2&gt;회고 및 앞으로 할 일&lt;/h2&gt;
&lt;p&gt;역전파 알고리즘에 대해 수학적으로 엄밀히 정의하고 컴퓨터 알고리즘 적으로도 어떻게 짜야할지 엄밀히 정의했는데
이걸 알기위해 다른 기초적인 개념들, 자코비안 행렬부터 외적, 편미분 등등을 배우느라 엄청 힘들었습니다.
아직 선형대수학을 완전히 알지 못해 이 개념들을 다 이해는 못하지만 코드로 구현할 정도는 이해했으니 지금은 이정도로 알고
추후에 더 찾아봐야겠습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>백준 11003번 최솟값 찾기 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/baekjoon-11003/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-11003/</guid><description>자료 구조 / 우선 순위 큐 / 덱 / 덱을 이용한 구간 최댓값 트릭</description><pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;p&gt;N개의 수 $A_1, A_2, ..., A_N$과 $L$이 주어진다.&lt;/p&gt;
&lt;p&gt;$D_i$ = $A_{i-L+1}$ ~ $A_i$ 중의 최솟값이라고 할 때, D에 저장된 수를 출력하는 프로그램을 작성하시오. 이때, i ≤ 0 인 Ai는 무시하고
D를 구해야 한다.&lt;/p&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 N과 L이 주어진다. (1 ≤ L ≤ N ≤ 5,000,000)&lt;/p&gt;
&lt;p&gt;둘째 줄에는 N개의 수 $A_i$가 주어진다. (-109 ≤ Ai ≤ 109)&lt;/p&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 $D_i$를 공백으로 구분하여 순서대로 출력한다.&lt;/p&gt;
&lt;h2&gt;풀이&lt;/h2&gt;
&lt;p&gt;이 문제는 덱과 슬라이딩 윈도우 개념을 이용하면 쉽게 풀리는 문제입니다.&lt;/p&gt;
&lt;p&gt;덱은 간단히 말해 앞과 뒤로 모두 나올 수 있는 큐라고 생각하시면 됩니다.&lt;/p&gt;
&lt;p&gt;슬라이딩 윈도우는 아래 그림처럼 고정된 길이를 탐색해 나가며 목표를 달성하는 기법입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%881.webp&quot; alt=&quot;슬라이딩 윈도우 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%882.webp&quot; alt=&quot;슬라이딩 윈도우 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%883.webp&quot; alt=&quot;슬라이딩 윈도우 3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이 문제에서는 이 덱에 앞쪽에 최솟값을 저장하고 뒤쪽에는 최솟값 후보를 저장합니다.&lt;/p&gt;
&lt;p&gt;또한 값을 저장할때 원본 배열의 인덱스도 같이 저장해 순서 데이터도 보존해줍니다.&lt;/p&gt;
&lt;p&gt;그렇게 생각했을 때 플로우를 보면 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;덱이 비어있으면 새 값과 인덱스를 넣음.&lt;/li&gt;
&lt;li&gt;새 값보다 덱의 뒤 값이 크거나 같으면 뒤에서부터 모두 제거.&lt;/li&gt;
&lt;li&gt;덱의 앞 값이 윈도우 범위를 벗어나면 앞에서 제거.&lt;/li&gt;
&lt;li&gt;덱에 새 값과 인덱스를 삽입.&lt;/li&gt;
&lt;li&gt;덱의 앞 값이 현재 구간의 최소값이므로 출력.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;백준 예시 입력 앞부분을 그림으로 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%884.webp&quot; alt=&quot;풀이 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%885.webp&quot; alt=&quot;풀이 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%886.webp&quot; alt=&quot;풀이 3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%887.webp&quot; alt=&quot;풀이 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B0%B1%EC%A4%8011003%EB%B2%888.webp&quot; alt=&quot;풀이 5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;deque&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

int N, L;
int arr[5000000];
deque&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; dq;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cin.tie(nullptr);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; L;

    for (int i = 0; i &amp;lt; N; ++i) cin &amp;gt;&amp;gt; arr[i];

    for (int i = 0; i &amp;lt; N; ++i)
    {
        if (!dq.empty())
        {
            if (dq.front().second &amp;lt; i - L + 1) dq.pop_front();
        }
        while (!dq.empty() &amp;amp;&amp;amp; dq.back().first &amp;gt; arr[i]) dq.pop_back();
        dq.push_back(make_pair(arr[i], i));
        cout &amp;lt;&amp;lt; dq.front().first &amp;lt;&amp;lt; &quot; &quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>테트리스 깨는 AI 제작 - 3</title><link>https://blog.ushiohayase.com/posts/tetris-ai-3/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-3/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;오늘 한 것&lt;/h2&gt;
&lt;p&gt;레이어 클래스 구현&lt;/p&gt;
&lt;p&gt;README 생성&lt;/p&gt;
&lt;p&gt;네임스페이스 재정의&lt;/p&gt;
&lt;h2&gt;회고 및 앞으로 할 일&lt;/h2&gt;
&lt;p&gt;엊그제 텐서 클래스를 완성하고 나서 텐서를 이용하여 만들 수 있는 레이어 클래스, 즉 Convolution 레이어, Linear 레이어같은 실제로
모델을 구성하는 레이어의 부모를 선언하고 정의하였습니다.&lt;/p&gt;
&lt;p&gt;cuDNN을 이용한 코드로 기존 텐서 클래스의 코드도 갈아엎고 이번 레이어 클래스도 제작했습니다.&lt;/p&gt;
&lt;p&gt;각 레이어는 가중치와 편향을 가지고, 입력과 출력을 &lt;code&gt;shared_ptr&lt;/code&gt;로 공유해서 가지며 텐서 자체는
&lt;code&gt;cudnn_frontend::graph::Tensor_attributes&lt;/code&gt;를 사용하여 저장을 하고 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 이 클래스를 상속해서 Tensorflow에서 Dense Layer라고 불리고 Pytorch에서 Linear 레이어라고 불리는 선형 레이어를
제작했습니다.&lt;/p&gt;
&lt;p&gt;순전파는 평범하게 $XW+B=Y$ 라는 행렬 곱 연산을 이용해서 구현을 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;역전파는 좀 이해하기 어려운 점도 있었는데 일단 편미분까지는 기존에 알고있던 지식이 있어 이해했지만 편미분을 행렬형태로 확장시키는 것이 힘들었습니다.&lt;/s&gt;
&lt;s&gt;어찌저찌해서 가중치 W에 대한 미분이&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;$$\frac{\partial E_{total}}{\partial W} = X^T \cdot \frac{\partial L}{\partial Y}$$&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;라는 것과 입력 X에 대한 미분이&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;$$\frac{\partial E_{total}}{\partial X} = \frac{\partial L}{\partial Y}\cdot W^T$$&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;라는 것을 이해하고 코드로 구현했습니다.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;이제 모델을 쉽게 생성할 수 있도록 모델 클래스를 잘 설계하고 만들어야겠습니다.&lt;/s&gt;&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>테트리스 깨는 AI 제작 - 2</title><link>https://blog.ushiohayase.com/posts/tetris-ai-2/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-2/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Sat, 10 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;오늘 한 것&lt;/h2&gt;
&lt;p&gt;텐서 클래스 구현 마무리&lt;/p&gt;
&lt;h2&gt;회고 및 앞으로 할 일&lt;/h2&gt;
&lt;p&gt;텐서 클래스를 제작하면서 템플릿을 이용했는데 이 템플릿과 CUDA를 이용하면서 모든 자료형에 대해 잘 인스턴스화 되도록 설계를 함과 동시에
선언과 구현을 분리하고 싶어 방법을 찾아 많이 헤맸습니다.
헤더 파일 끝에 포함해보기도 하고 CMake 설정을 바꿔보기도 하였지만 결과는 많은 오류만 맞이하였습니다.
그래도 계속 해결책을 찾다가 결국에는 어차피 AI에서 텐서는 문자 자료형이나 사용자 자료형은 안받으니 기본 숫자 자료형에 대해서만
인스턴스화시켜 구현하기로 하였습니다.&lt;/p&gt;
&lt;p&gt;이 과정에서 CUDA파일과 cpp파일이 섞이고 컴파일러가 구분하지 못해 어려움을 겪었지만 #ifdef도 해보면서 어떤 원리로 구분하는지 알게 되었습니다.
고치는 과정에서 테스트라는걸 처음 도입해서 시도해봤는데 매우 편리해서 앞으로도 자주 사용할 것 같습니다.&lt;/p&gt;
&lt;p&gt;테스트를 여러번 시도해보며 행렬 합과 스칼라 배를 GPU로 매우 큰 크기의 3차원 배열로 까지 구현하는데 성공했습니다.&lt;/p&gt;
&lt;p&gt;추가로 과제 시간 제약때문에 cudnn이라는 라이브러리 정도는 사용하려하는데 이 라이브러리에 맞춰서 개발해야겠습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>백준 1647번 &quot;도시 분할 계획&quot; C++ 풀이</title><link>https://blog.ushiohayase.com/posts/baekjoon-1647/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-1647/</guid><description>그래프 이론 / 최소 스패닝 트리</description><pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다. 그러다가 평화로운 마을에 가게 되었는데, 그곳에서는 알 수 없는 일이 벌어지고 있었다.&lt;/p&gt;
&lt;p&gt;마을은 N개의 집과 그 집들을 연결하는 M개의 길로 이루어져 있다. 길은 어느 방향으로든지 다닐 수 있는 편리한 길이다. 그리고 각 길마다 길을
유지하는데 드는 유지비가 있다. 임의의 두 집 사이에 경로가 항상 존재한다.&lt;/p&gt;
&lt;p&gt;마을의 이장은 마을을 두 개의 분리된 마을로 분할할 계획을 가지고 있다. 마을이 너무 커서 혼자서는 관리할 수 없기 때문이다. 마을을 분할할 때는
각 분리된 마을 안에 집들이 서로 연결되도록 분할해야 한다. 각 분리된 마을 안에 있는 임의의 두 집 사이에 경로가 항상 존재해야 한다는 뜻이다.
마을에는 집이 하나 이상 있어야 한다.&lt;/p&gt;
&lt;p&gt;그렇게 마을의 이장은 계획을 세우다가 마을 안에 길이 너무 많다는 생각을 하게 되었다. 일단 분리된 두 마을 사이에 있는 길들은 필요가 없으므로
없앨 수 있다. 그리고 각 분리된 마을 안에서도 임의의 두 집 사이에 경로가 항상 존재하게 하면서 길을 더 없앨 수 있다. 마을의 이장은 위
조건을 만족하도록 길들을 모두 없애고 나머지 길의 유지비의 합을 최소로 하고 싶다. 이것을 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 집의 개수 N, 길의 개수 M이 주어진다. N은 2이상 100,000이하인 정수이고, M은 1이상 1,000,000이하인 정수이다.
그 다음 줄부터 M줄에 걸쳐 길의 정보가 A B C 세 개의 정수로 주어지는데 A번 집과 B번 집을 연결하는 길의 유지비가
C (1 ≤ C ≤ 1,000)라는 뜻이다.&lt;/p&gt;
&lt;p&gt;임의의 두 집 사이에 경로가 항상 존재하는 입력만 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 없애고 남은 길 유지비의 합의 최솟값을 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;풀이&lt;/h2&gt;
&lt;p&gt;이 문제는 문제를 쭉 읽다보면 아는 사람은 보이겠지만 최소 스패닝 트리, 최소 신장 트리에 관한 걸 길게 설명한 문제이다.&lt;/p&gt;
&lt;p&gt;문제에서 요구하는건 최소 신장 트리를 반으로 잘라 가장 짧게 만들라는 것인데 그것은 최소 신장 트리에서 가장 긴 간선을 빼면 된다.&lt;/p&gt;
&lt;p&gt;그래서 바로 이 방식으로 크루스칼 알고리즘을 이용해서 구현해 문제를 풀었다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; edge;
int parent[100&apos;001];

int Find(int v)
{
    if (parent[v] == v || parent[v] == 0) return v;
    return Find(parent[v]);
}

void Union(int a, int b)
{
    int p1 = Find(a);
    int p2 = Find(b);

    if (p1 &amp;lt; p2)
        parent[p2] = p1;
    else
        parent[p1] = p2;
}

int main()
{
    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;

    for (int i = 0; i &amp;lt; M; ++i)
    {
        int a, b, c;
        cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b &amp;gt;&amp;gt; c;
        edge.push_back({c, a, b});
    }

    int result = 0;
    int max_edge = 0;

    sort(edge.begin(), edge.end());

    for (auto&amp;amp; i : edge)
    {
        if (Find(i[1]) != Find(i[2]))
        {
            Union(i[1], i[2]);
            result += i[0];
            max_edge = max(max_edge, i[0]);
        }
    }

    cout &amp;lt;&amp;lt; result - max_edge;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>백준 1766번 문제집 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/baekjoon-1766/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-1766/</guid><description>자료 구조/그래프 이론/우선순위 큐/방향성 비순환 그래프/위상 정렬</description><pubDate>Thu, 08 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;민오는 1번부터 N번까지 총 N개의 문제로 되어 있는 문제집을 풀려고 한다. 문제는 난이도 순서로 출제되어 있다. 즉 1번 문제가 가장 쉬운
문제이고 N번 문제가 가장 어려운 문제가 된다.&lt;/p&gt;
&lt;p&gt;어떤 문제부터 풀까 고민하면서 문제를 훑어보던 민오는, 몇몇 문제들 사이에는 &apos;먼저 푸는 것이 좋은 문제&apos;가 있다는 것을 알게 되었다. 예를
들어 1번 문제를 풀고 나면 4번 문제가 쉽게 풀린다거나 하는 식이다. 민오는 다음의 세 가지 조건에 따라 문제를 풀 순서를 정하기로 하였다.&lt;/p&gt;
&lt;p&gt;N개의 문제는 모두 풀어야 한다.
먼저 푸는 것이 좋은 문제가 있는 문제는, 먼저 푸는 것이 좋은 문제를 반드시 먼저 풀어야 한다.
가능하면 쉬운 문제부터 풀어야 한다.
예를 들어서 네 개의 문제가 있다고 하자. 4번 문제는 2번 문제보다 먼저 푸는 것이 좋고, 3번 문제는 1번 문제보다 먼저 푸는 것이 좋다고
하자. 만일 4-3-2-1의 순서로 문제를 풀게 되면 조건 1과 조건 2를 만족한다. 하지만 조건 3을 만족하지 않는다. 4보다 3을 충분히
먼저 풀 수 있기 때문이다. 따라서 조건 3을 만족하는 문제를 풀 순서는 3-1-4-2가 된다.&lt;/p&gt;
&lt;p&gt;문제의 개수와 먼저 푸는 것이 좋은 문제에 대한 정보가 주어졌을 때, 주어진 조건을 만족하면서 민오가 풀 문제의 순서를 결정해 주는 프로그램을
작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 문제의 수 N(1 ≤ N ≤ 32,000)과 먼저 푸는 것이 좋은 문제에 대한 정보의 개수 M(1 ≤ M ≤ 100,000)이
주어진다. 둘째 줄부터 M개의 줄에 걸쳐 두 정수의 순서쌍 A,B가 빈칸을 사이에 두고 주어진다. 이는 A번 문제는 B번 문제보다 먼저 푸는
것이 좋다는 의미이다.&lt;/p&gt;
&lt;p&gt;항상 문제를 모두 풀 수 있는 경우만 입력으로 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 문제 번호를 나타내는 1 이상 N 이하의 정수들을 민오가 풀어야 하는 순서대로 빈칸을 사이에 두고 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;풀이&lt;/h2&gt;
&lt;p&gt;딱 문제를 읽고 원래 알고있던 Kahn 알고리즘을 써야겠다는 생각이 떠올랐고 알고리즘 그대로 구현한뒤 가능한 쉬운 문제부터 풀라는 조건이 있으니
사용되는 자료구조만 큐에서 우선순위 큐로 바꿨습니다.&lt;/p&gt;
&lt;p&gt;아직 많이 안풀어봐서 일지도 모르겠지만 위상 정렬 문제는 골드에서는 정해진 템플릿에서 나와 어렵지 않은것 같습니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;

int entry[32001];
vector&amp;lt;int&amp;gt; quest[32001];

int main()
{
    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;

    for (int i = 0; i &amp;lt; M; ++i)
    {
        int x, y;

        cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;

        entry[y]++;
        quest[x].push_back(y);
    }

    priority_queue&amp;lt;int, vector&amp;lt;int&amp;gt;, greater&amp;lt;int&amp;gt;&amp;gt; pq;

    for (int i = 1; i &amp;lt;= N; ++i)
        if (entry[i] == 0) pq.push(i);

    while (!pq.empty())
    {
        const int node = pq.top();
        cout &amp;lt;&amp;lt; node &amp;lt;&amp;lt; &quot; &quot;;
        pq.pop();

        for (auto i : quest[node])
        {
            entry[i]--;
            if (entry[i] == 0) pq.push(i);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>테트리스 깨는 AI 제작 - 1</title><link>https://blog.ushiohayase.com/posts/tetris-ai-1/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/tetris-ai-1/</guid><description>테트리스를 스스로 깨는 강화학습 에이전트 CUDA부터 개발하기</description><pubDate>Thu, 08 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;AI에 대해서는 예전부터 관심이 있었고 C++과 rust같은 로우레벨 프로그래밍 언어도 개인적으로 좋아합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 프로젝트는 C++와 CUDA를 활용해 외부 라이브러리 없이 테트리스 게임을 직접 구현하고,
이 게임을 스스로 플레이하며 최고 점수를 노리는 강화학습 에이전트를 처음부터 만들어보는 과정을 기록하는 것을 목표로 할 것입니다.&lt;/p&gt;
&lt;p&gt;로우레벨 언어와 인공지능, 그리고 GPU 프로그래밍에 관심이 많은 학부생으로서, 이미 잘 만들어진 라이브러리나 프레임워크에 의존하지 않고
처음부터 모든 것을 직접 설계하고 구현해보는 경험을 통해, 진짜로 시스템이 어떻게 돌아가는지 깊이 이해하고 싶습니다.&lt;/p&gt;
&lt;p&gt;또한, GPU의 병렬 연산 능력을 실제로 활용해보며, 이론으로만 배웠던 개념들이 실제 코드와 하드웨어에서 어떻게 동작하는지 체험하고자 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 통해 배우고자 하는 가장 큰 목표는, 강화학습의 핵심 원리와 GPU 프로그래밍의 실전 기술을 내 손으로 직접 구현하며 익히는 것입니다.&lt;/p&gt;
&lt;p&gt;테트리스라는 익숙한 게임을 스스로 만들고, 그 위에서 동작하는 에이전트를 설계하면서, 상태 공간과 행동 집합, 보상 함수 설계의 중요성을 느낄 것입니다.&lt;/p&gt;
&lt;p&gt;또한, CUDA를 활용해 대량의 시뮬레이션을 병렬로 처리하는 과정에서, GPU 메모리 관리나 커널 최적화와 같은 실전적인 문제들을 직접
해결해보고자 합니다.&lt;/p&gt;
&lt;p&gt;라이브러리 없이 신경망이나 알고리즘을 처음부터 구현하는 과정에서, 평소에는 잘 느끼지 못했던 컴퓨팅 자원의 한계나, 병렬 연산의 어려움도
경험할 수 있을 것이라 기대하고 있습니다.&lt;/p&gt;
&lt;p&gt;어려움도 많겠지만 이런 난관들을 직접 부딪히고 해결해가는 과정에서, 단순히 결과만 얻는 것이 아니라, 문제를 분석하고 해결책을 찾아가는 과정
자체가 큰 성장의 기회가 될 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;설계 구상&lt;/h2&gt;
&lt;p&gt;아직 전체적으로 명확하게 생각하진 않았지만 일단 프로그램은 크게 테트리스 부분과 에이전트 부분으로 나누고 에이전트 부분은 DQN 방법을 이용하는
것으로 생각해뒀습니다.&lt;/p&gt;
&lt;p&gt;에이전트 부분의 객체 요소들은 텐서, 파라미터, 레이어, 모델, 옵티마이저정도를 정의하고 사용하려 생각하고있습니다.&lt;/p&gt;
&lt;p&gt;추가적인 디테일은 프로젝트를 진행하며 배우고 추가하려하고 있습니다.&lt;/p&gt;
&lt;h2&gt;오늘한 것&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;쿠다 설치 및 세팅&lt;/li&gt;
&lt;li&gt;CMake 세팅&lt;/li&gt;
&lt;li&gt;텐서 클래스 정의&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;회고 및 할 일&lt;/h2&gt;
&lt;p&gt;CUDA를 설치하고 빌드 설정은 끝냈는데 너무 기대됩니다.&lt;/p&gt;
&lt;p&gt;다음에는 텐서 클래스 정의 마무리와 파라미터, 레이어, 모델을 정의해야겠습니다.&lt;/p&gt;
&lt;h2&gt;프로젝트 깃헙&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/Ushionn&quot;}&lt;/p&gt;
</content:encoded></item><item><title>백준 11049번 행렬 곱셈 순서 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/baekjoon-11049/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-11049/</guid><description>다이나믹 프로그래밍 사용</description><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;크기가 N×M인 행렬 A와 M×K인 B를 곱할 때 필요한 곱셈 연산의 수는 총 N×M×K번이다. 행렬 N개를 곱하는데 필요한 곱셈 연산의 수는
행렬을 곱하는 순서에 따라 달라지게 된다.&lt;/p&gt;
&lt;p&gt;예를 들어, A의 크기가 5×3이고, B의 크기가 3×2, C의 크기가 2×6인 경우에 행렬의 곱 ABC를 구하는 경우를 생각해보자.&lt;/p&gt;
&lt;p&gt;AB를 먼저 곱하고 C를 곱하는 경우 (AB)C에 필요한 곱셈 연산의 수는 5×3×2 + 5×2×6 = 30 + 60 = 90번이다.
BC를 먼저 곱하고 A를 곱하는 경우 A(BC)에 필요한 곱셈 연산의 수는 3×2×6 + 5×3×6 = 36 + 90 = 126번이다.
같은 곱셈이지만, 곱셈을 하는 순서에 따라서 곱셈 연산의 수가 달라진다.&lt;/p&gt;
&lt;p&gt;행렬 N개의 크기가 주어졌을 때, 모든 행렬을 곱하는데 필요한 곱셈 연산 횟수의 최솟값을 구하는 프로그램을 작성하시오. 입력으로 주어진 행렬의
순서를 바꾸면 안 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 행렬의 개수 N(1 ≤ N ≤ 500)이 주어진다.&lt;/p&gt;
&lt;p&gt;둘째 줄부터 N개 줄에는 행렬의 크기 r과 c가 주어진다. (1 ≤ r, c ≤ 500)&lt;/p&gt;
&lt;p&gt;항상 순서대로 곱셈을 할 수 있는 크기만 입력으로 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 입력으로 주어진 행렬을 곱하는데 필요한 곱셈 연산의 최솟값을 출력한다. 정답은 $2^{31}-1$ 보다 작거나 같은 자연수이다.
또한, 최악의 순서로 연산해도 연산 횟수가 $2^{31}-1$보다 작거나 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;풀이 과정&lt;/h2&gt;
&lt;p&gt;일단 이 문제의 문제 조건을 보았을 때 N은 500까지이고 시간 제한은 1초이니 시간복잡도는 약 $O(N^3)$까지 허용되는 것으로 파악했습니다.&lt;/p&gt;
&lt;p&gt;그러고 나서는 백준 10844번 문제 [^1]를 풀 때처럼 기본적으로 dp를 이용하면서 각 행렬의 행과 열을 dp 배열의 행 차원의 요소로 넣은
뒤에 (std::pair 이용) 각 위치에서 양 사이드로 퍼져나가면서 필요한 곱셈 연산 수의 최솟값을 구하는 알고리즘을 구상했습니다.&lt;/p&gt;
&lt;p&gt;자료구조로는 &lt;code&gt;pair&amp;lt;int, pair&amp;lt;int, int&amp;gt;&amp;gt; dp[]&lt;/code&gt;를 구상했습니다.&lt;/p&gt;
&lt;p&gt;i번째 행과 j번째 열에서 점화식은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$dp[i][j]=min(dp[i - 1][j].first + dp[i - 1][j].second.first * dp[i - 1][j].second.second * dp[i - 1][j + 1].second.second,dp[i - 1][j + 1].first + dp[i - 1][j].second.first * dp[i - 1][j].second.second * dp[i - 1][j + 1].second.second)$$&lt;/p&gt;
&lt;p&gt;이 문제의 예제 입력에 대해서는 잘 나왔지만 다른 예제에 대해선 반례가 많이 나와 이 알고리즘은 포기했습니다.&lt;/p&gt;
&lt;p&gt;이 알고리즘을 포기하고 나서는 내가 알고있는 지식의 바깥 범위의 문제인것 같아 챗봇에게 문제를 다 알려주지 말고 아주 약간의 힌트만 달라고 하였습니다.&lt;/p&gt;
&lt;p&gt;그 결과 이 문제에서는 분할 정복을 써야 한다고 나왔고 저는 분할 정복에 대해 익숙하지 않고 잘 몰랐기에 분할 정복에 대해 코드를 보고
이해하기로 하였습니다.&lt;/p&gt;
&lt;p&gt;그 후 다시 dp의 배열을 처음에 잠깐 상상만 했다가 구현이 힘들 것 같아 포기했던 i행 j열이 i번째부터 j번째까지를 의미하는 배열로 자료구조를
바꿔서 제작하였습니다.&lt;/p&gt;
&lt;p&gt;중간에 원래라면 재귀와 메모이제이션을 이용하여 풀면 이렇게까지 복잡하게 안가도 됐겠지만 탑-다운 방식의 dp를 안쓴지 오래되어 생각해내지 못했습니다.&lt;/p&gt;
&lt;p&gt;확실히 그래프 탐색이나 다른 유형에 비해 dp가 약한게 느껴지고 더 연습해야겠습니다.&lt;/p&gt;
&lt;p&gt;[^1]: &lt;a href=&quot;https://www.acmicpc.net/problem/10844&quot;&gt;백준 10844번 문제 링크&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# include &amp;lt;iostream&amp;gt;
# include &amp;lt;numeric&amp;gt;

using namespace std;

int N;
pair&amp;lt;int, int&amp;gt; matrix[500];
int dp[500][500];

int main()
{
    cin &amp;gt;&amp;gt; N;

    fill(*dp, *dp + 500 * 500, numeric_limits&amp;lt;int&amp;gt;::max());

    for (int i = 0; i &amp;lt; N; ++i)
    {
        cin &amp;gt;&amp;gt; matrix[i].first &amp;gt;&amp;gt; matrix[i].second;
        dp[i][i] = 0;
    }

    for (int i = 1; i &amp;lt; N; ++i)
        for (int j = 0; j &amp;lt; N - i; ++j)
        {
            int tmp = i + j;
            for (int k = j; k &amp;lt; tmp; ++k)
            {
                const int cost = dp[j][k] + dp[k + 1][tmp] + matrix[j].first * matrix[k].second * matrix[tmp].second;
                dp[j][tmp] = min(dp[j][tmp], cost);
            }
        }

    cout &amp;lt;&amp;lt; dp[0][N - 1];
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>브렌트 알고리즘(사이클 탐지 알고리즘)</title><link>https://blog.ushiohayase.com/posts/brents-algorithm/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/brents-algorithm/</guid><description>사이클 탐지 알고리즘 중 브렌트 알고리즘 설명</description><pubDate>Thu, 24 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;브렌트 알고리즘은 Richard P. Brent가 토끼와 거북이 알고리즘같은 사이클 탐지 알고리즘을 개선하기 위해 고안한 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;브렌트 알고리즘은 기존 플로이드의 토끼와 거북이 알고리즘과 비교해 실행시간이 24%~36%나 더 빠른 성질을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;이 알고리즘 또한 토끼와 거북이 알고리즘처럼 두 개의 포인터를 사용하고 사이클의 시작점과 길이를 모두 찾을 수 있습니다.&lt;/p&gt;
&lt;p&gt;토끼와 거북이 알고리즘과 다르게 브렌트 알고리즘은 탐색범위를 늘려 평균적으로 더 빠르게 탐색을 실시합니다.&lt;/p&gt;
&lt;h2&gt;동작 원리&lt;/h2&gt;
&lt;p&gt;브렌트 알고리즘은 토끼와 거북이라는 포인터와 power(탐색 범위)와 len이라는 현재 단계의 이동 횟수 변수를 사용합니다.&lt;/p&gt;
&lt;p&gt;토끼는 시작점에서 한 칸 건너간 곳에서 출발하고 거북이는 시작점에서 출발, power와 len은 1의 값을 가지고 시작합니다.&lt;/p&gt;
&lt;p&gt;이후로는 아래의 과정을 충돌이 발생하거나 토끼가 리스트의 끝에 도달할 때까지 반복합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;토끼가 거북이가 충돌하는가?&lt;/li&gt;
&lt;li&gt;len이 power와 같은가?
&lt;ol&gt;
&lt;li&gt;같다면
&lt;ol&gt;
&lt;li&gt;거북이를 현재 토끼 위치로 재설정&lt;/li&gt;
&lt;li&gt;power를 2배 증가&lt;/li&gt;
&lt;li&gt;len을 0으로 리셋&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;토끼 한 칸 증가&lt;/li&gt;
&lt;li&gt;길이 1 증가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;만약 리스트의 끝에 도달하면 사이클이 존재하지 않는 것입니다.&lt;/p&gt;
&lt;p&gt;충돌이 발생할 경우 len 값이 사이클의 길이입니다.&lt;/p&gt;
&lt;p&gt;예시로는 아래 그림과 같은 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%981.webp&quot; alt=&quot;1&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%982.webp&quot; alt=&quot;2&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%983.webp&quot; alt=&quot;3&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%984.webp&quot; alt=&quot;4&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%985.webp&quot; alt=&quot;5&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%986.webp&quot; alt=&quot;6&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%987.webp&quot; alt=&quot;7&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%988.webp&quot; alt=&quot;8&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%989.webp&quot; alt=&quot;9&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9810.webp&quot; alt=&quot;10&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9811.webp&quot; alt=&quot;11&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9812.webp&quot; alt=&quot;12&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9813.webp&quot; alt=&quot;13&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9814.webp&quot; alt=&quot;14&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9815.webp&quot; alt=&quot;15&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9816.webp&quot; alt=&quot;16&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9817.webp&quot; alt=&quot;17&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9818.webp&quot; alt=&quot;18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이렇게 사이클의 존재를 찾은 뒤에는 시작점도 탐색할 수 있습니다.&lt;/p&gt;
&lt;p&gt;시작점을 탐색할 땐 거북이를 다시 출발점으로 보내고 사이클 길이만큼 토끼를 앞으로 이동시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9819.webp&quot; alt=&quot;19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;그 후 거북이와 토끼를 한 칸씩 이동시키며 만나는 지점을 찾고 이 때의 이동횟수가 출발점부터 사이클 시작점까지의 거리입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9820.webp&quot; alt=&quot;20&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9821.webp&quot; alt=&quot;21&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9822.webp&quot; alt=&quot;22&quot; /&gt;
&lt;img src=&quot;./%EB%B8%8C%EB%A0%8C%ED%8A%B8_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9823.webp&quot; alt=&quot;23&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/brents-cycle-detection-algorithm/&quot;&gt;brents-cycle-detection-algorithm GeekforGeeks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cycle_detection&quot;&gt;brents-cycle-detection-algorithm Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>백준 10844번 쉬운 계단 수 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/%EB%B0%B1%EC%A4%80-10844%EB%B2%88-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98-c-%ED%92%80%EC%9D%B4/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/%EB%B0%B1%EC%A4%80-10844%EB%B2%88-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98-c-%ED%92%80%EC%9D%B4/</guid><description>백준 10844번 쉬운 계단 수 C++ 풀이</description><pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;45656이란 수를 보자.&lt;/p&gt;
&lt;p&gt;이 수는 인접한 모든 자리의 차이가 1이다. 이런 수를 계단 수라고 한다.&lt;/p&gt;
&lt;p&gt;N이 주어질 때, 길이가 N인 계단 수가 총 몇 개 있는지 구해보자. 0으로 시작하는 수는 계단수가 아니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 100보다 작거나 같은 자연수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;풀이&lt;/h2&gt;
&lt;p&gt;이 문제를 봤을 때 계단 수를 찾는 과정 자체가 탐색을 해야하고 그 과정이 메모이제이션을 활용할 것 같아 DP를 활용해야겠다고 생각했습니다.&lt;/p&gt;
&lt;p&gt;dp 배열은 2차원 배열로 정의했고 행은 자리수, 열은 0-9의 숫자로 정의했습니다.&lt;/p&gt;
&lt;p&gt;그에 따른 점화식은 i행 j열일때 i-1행 j-1,j+1열의 값을 가져와 더하는 것으로 정의했습니다.&lt;/p&gt;
&lt;p&gt;dp의 관련된 요소는 여기까지고 이 문제의 본질은 계산 과정에서 숫자가 매우 커진다는 것인데 따라서 출력도 1&apos;000&apos;000&apos;000으로 나눈
값으로 출력하라고 나와있습니다.&lt;/p&gt;
&lt;p&gt;이 문제를 위해서는 계산 과정 중간중간의 모듈러 연산을 활용해야했습니다.&lt;/p&gt;
&lt;p&gt;모듈러 연산 중 덧셈에 관한 것을 활용했는데 그 내용은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$(a + b) \mod n = {(a \mod n )+ (b \mod n)} \mod n$$&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

using namespace std;

int N;

unsigned long long dp[100][10];

int main()
{
    cin &amp;gt;&amp;gt; N;

    for (int i = 1; i &amp;lt; 10; ++i)
    {
        dp[0][i] = 1;
    }

    for (int i = 1; i &amp;lt; N; ++i)
    {
        for (int j = 0; j &amp;lt; 10; ++j)
        {
            if (j != 0 &amp;amp;&amp;amp; j != 9)
                dp[i][j] = (dp[i - 1][j - 1] % 1&apos;000&apos;000&apos;000 +
                            dp[i - 1][j + 1] % 1&apos;000&apos;000&apos;000) %
                           1&apos;000&apos;000&apos;000;
            else if (j == 0)
                dp[i][j] = dp[i - 1][j + 1];
            else if (j == 9)
                dp[i][j] = dp[i - 1][j - 1];
        }
    }

    unsigned long long result = 0;

    for (int i = 0; i &amp;lt; 10; ++i)
    {
        result = (result + dp[N - 1][i] % 1&apos;000&apos;000&apos;000) % 1&apos;000&apos;000&apos;000;
    }

    cout &amp;lt;&amp;lt; result;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Baekjoon-9465</title><link>https://blog.ushiohayase.com/posts/baekjoon-9465/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-9465/</guid><description>백준 9465번 스티커 C++ 풀이</description><pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;p&gt;문제
상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해
책상을 꾸미려고 한다.&lt;/p&gt;
&lt;p&gt;상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다.
즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다.&lt;/p&gt;
&lt;p&gt;모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점수를 매기고, 점수의 합이 최대가 되게 스티커를 떼어내려고 한다. 먼저, 그림 (b)와 같이
각 스티커에 점수를 매겼다. 상냥이가 뗄 수 있는 스티커의 점수의 최댓값을 구하는 프로그램을 작성하시오. 즉, 2n개의 스티커 중에서 점수의 합이
최대가 되면서 서로 변을 공유 하지 않는 스티커 집합을 구해야 한다.&lt;/p&gt;
&lt;p&gt;위의 그림의 경우에 점수가 50, 50, 100, 60인 스티커를 고르면, 점수는 260이 되고 이 것이 최대 점수이다. 가장 높은 점수를 가지는
두 스티커 (100과 70)은 변을 공유하기 때문에, 동시에 뗄 수 없다.&lt;/p&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는
n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 점수이다. 연속하는 두 정수 사이에는 빈 칸이 하나 있다. 점수는 0보다 크거나
같고, 100보다 작거나 같은 정수이다.&lt;/p&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;p&gt;각 테스트 케이스 마다, 2n개의 스티커 중에서 두 변을 공유하지 않는 스티커 점수의 최댓값을 출력한다.&lt;/p&gt;
&lt;h2&gt;풀이 과정&lt;/h2&gt;
&lt;p&gt;처음에 문제를 봤을때는 상하좌우의 스티커를 못쓴다고 하길래 백트래킹 알고리즘인가 싶었지만 2행 n열인것과 입력 제한을 봤을때 $O(N)$까지만
허용되어 보여 동적계획법을 사용해야할 것 같아 보였습니다.&lt;/p&gt;
&lt;p&gt;처음에 점화식은 아래와 같이 구성했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i번쨰에 위쪽을 선택한 경우: 선택지 중 최대 + 위쪽 선택 값
&lt;ul&gt;
&lt;li&gt;i-1 번째에 아래와 i-2번째의 위쪽 최선의 해를 선택한 경우&lt;/li&gt;
&lt;li&gt;i-2 번째의 아래 쪽 최선의 해를 선택한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;i번째에 아래쪽을 선택한 경우: 선택지 중 최대 + 아래쪽 선택 값
&lt;ul&gt;
&lt;li&gt;i-1 번째에 위쪽과 i-2번째의 아래쪽 최선의 해를 선택한 경우&lt;/li&gt;
&lt;li&gt;i-2 번째의 위 쪽 최선의 해를 선택한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 4가지 경우의 수로 풀려했지만 계속 틀렸습니다.&lt;/p&gt;
&lt;p&gt;한 1시간 고민한 결과 도무지 답이 나오지 않아 챗봇에게 점화식 힌트를 주라고 물어봤더니 선택하지 않는 경우의 수 상태도 포함하라고 조언을 받아
코드를 다시 작성했습니다.&lt;/p&gt;
&lt;p&gt;새로운 점화식은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i번째에 선택하지 않는 경우: 선택지 중 최대
&lt;ul&gt;
&lt;li&gt;i-1번째 선택하지않는 최선의 해 경우&lt;/li&gt;
&lt;li&gt;i-1번째 위쪽 선택한 최선의 해 경우&lt;/li&gt;
&lt;li&gt;i-1번째 아래쪽 선택한 최선의 해 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;i번째에 위쪽 선택한 경우: 선택지 중 최대 + 위쪽 선택 값
&lt;ul&gt;
&lt;li&gt;i-1번째 선택하지않는 최선의 해 경우&lt;/li&gt;
&lt;li&gt;i-1번째 아래쪽 선택한 최선의 해 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;i번째에 아래쪽 선택한 경우: 선택지 중 최대 + 아래쪽 선택 값
&lt;ul&gt;
&lt;li&gt;i-1번째 선택하지않는 최선의 해 경우&lt;/li&gt;
&lt;li&gt;i-1번째 위쪽 선택한 최선의 해 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 코드를 다시 작성한 결과 맞았습니다.&lt;/p&gt;
&lt;p&gt;추가적으로 여기서 느낀 점은 내가 문제의 상태를 정의하고 점화식으로 바꾸는 것에 약하다고 느꼈고 DP문제를 더 많이 풀어봐야겠습니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

using namespace std;

int N, T;

int upper[100&apos;000];
int downer[100&apos;000];

int dp[100&apos;000][3];

int main()
{
    cin &amp;gt;&amp;gt; T;
    for (int t = 0; t &amp;lt; T; ++t)
    {
        cin &amp;gt;&amp;gt; N;
        for (int i = 0; i &amp;lt; N; ++i) cin &amp;gt;&amp;gt; upper[i];
        for (int i = 0; i &amp;lt; N; ++i) cin &amp;gt;&amp;gt; downer[i];

        if (N == 1)
        {
            cout &amp;lt;&amp;lt; max(upper[0], downer[0]) &amp;lt;&amp;lt; &quot;\n&quot;;
            continue;
        }
        dp[0][1] = upper[0];
        dp[0][2] = downer[0];
        for (int i = 1; i &amp;lt; N; ++i)
        {
            dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][2]));
            dp[i][1] = upper[i] + max(dp[i - 1][0], dp[i - 1][2]);
            dp[i][2] = downer[i] + max(dp[i - 1][0], dp[i - 1][1]);
        }
        cout &amp;lt;&amp;lt; max(max(dp[N - 1][0], dp[N - 1][1]), dp[N - 1][2]) &amp;lt;&amp;lt; &quot;\n&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>플로이드의 토끼와 거북이 알고리즘(사이클 탐지 알고리즘)</title><link>https://blog.ushiohayase.com/posts/floyds-tortoise-and-hare-algorithm/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/floyds-tortoise-and-hare-algorithm/</guid><description>토끼와 거북이 알고리즘 설명</description><pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;토끼와 거북이 알고리즘, Floyd&apos;s Tortoise and Hare Algorithm은 연결 리스트에서 사이클의 존재 여부를
찾기 위해 고안된 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;이 알고리즘은 1967년 Robert W. Floyd의 논문에 나타났으며, &quot;Tortoise and Hare&quot;라는 이름은 이솝 우화에서 유래했습니다.&lt;/p&gt;
&lt;p&gt;또한 이 알고리즘을 활용하면 사이클의 시작점과 길이를 찾을 수 있습니다.&lt;/p&gt;
&lt;p&gt;판별하는데 필요한 시간복잡도는 $O(\mu + \lambda)$ (시작점부터 사이클 시작점까지 거리: $\mu$, 사이클 길이: $\lambda$)
이며 필요한 공간복잡도는 $O(1)$입니다.&lt;/p&gt;
&lt;h2&gt;동작 원리&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B41.webp&quot; alt=&quot;시작&quot; /&gt;&lt;/p&gt;
&lt;p&gt;먼저 토끼라고 이름을 붙인 포인터와 거북이라고 이름을 붙인 포인터를 시작점에 둡니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B42.webp&quot; alt=&quot;과정1&quot; /&gt;
&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B43.webp&quot; alt=&quot;과정2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;토끼 포인터는 한 번 움직일 때 2칸씩 이동하고 거북이 포인터는 한 번 움직일 때 1칸씩 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B44.webp&quot; alt=&quot;과정3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;두 포인터를 동시에 이동시키면서 만약 두 포인터가 만나면 사이클이 존재하는 것이고 토끼 포인터가 리스트의 끝에 도달한다면
연결 리스트에 사이클이 없는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B46.webp&quot; alt=&quot;리제로&quot; /&gt;&lt;/p&gt;
&lt;p&gt;사이클의 시작점을 찾으려면 만난 후에 거북이를 시작점으로 다시 옮깁니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B47.webp&quot; alt=&quot;다시 과정&quot; /&gt;&lt;/p&gt;
&lt;p&gt;그 후 이번엔 두 포인터를 한 칸 씩 이동시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%86%A0%EB%81%BC%EC%99%80%EA%B1%B0%EB%B6%81%EC%9D%B48.webp&quot; alt=&quot;시작점&quot; /&gt;&lt;/p&gt;
&lt;p&gt;그러면 두 포인터가 만나는 지점이 사이클의 시작점입니다.&lt;/p&gt;
&lt;h3&gt;동작 원리와 수학적 이해&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;시작점부터 사이클 시작점까지 거리: $\mu$&lt;/li&gt;
&lt;li&gt;사이클 내 거북이의 첫번째 만남까지 이동거리: $m$&lt;/li&gt;
&lt;li&gt;사이클 길이: $\lambda$&lt;/li&gt;
&lt;li&gt;토끼가 사이클을 회전한 횟수: $k$&lt;/li&gt;
&lt;li&gt;거북이 총 이동거리: $i$&lt;/li&gt;
&lt;li&gt;토끼 총 이동거리: $2i$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;첫 만남까지 거북이가 이동한 거리는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$\mu + m$$&lt;/p&gt;
&lt;p&gt;토끼가 이동한 거리는 거북이보다 2배 속도가 빠르므로 다음과 같이 됩니다.&lt;/p&gt;
&lt;p&gt;$$2i=2(\mu+m)$$&lt;/p&gt;
&lt;p&gt;다시 토끼가 이동한 거리를 써보면 토끼는 시작점 -&amp;gt; 사이클 회전 -&amp;gt; m만큼 추가 이동이므로 아래와 같이 됩니다.&lt;/p&gt;
&lt;p&gt;$$2i = \mu + k\lambda + m$$&lt;/p&gt;
&lt;p&gt;즉 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;$$2(\mu + m)=\mu+k\lambda+m$$&lt;/p&gt;
&lt;p&gt;$$\mu+m=k\lambda$$&lt;/p&gt;
&lt;p&gt;그러면 처음 만나는 위치는 사이클의 시작지점에서 $m$만큼 떨어진 지점에서 만납니다.&lt;/p&gt;
&lt;p&gt;사이클의 시작지점은 거북이의 경우 $\mu$칸, 토끼의 경우 $k\lambda$칸 이동하면 찾을 수 있습니다.&lt;/p&gt;
&lt;p&gt;추가적으로 $m$은 사이클의 시작지점에서 거리이므로 아래의 식이 성립합니다.&lt;/p&gt;
&lt;p&gt;$$(k\lambda - \mu) \mod \lambda$$&lt;/p&gt;
&lt;p&gt;따라서 사이클 시작점에서 처음 만나는 지점까지의 거리의 식이 유도됩니다.&lt;/p&gt;
&lt;p&gt;$$\lambda - (\mu \mod \lambda)$$&lt;/p&gt;
&lt;h2&gt;참고자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cycle_detection&quot;&gt;Floyd&apos;s Algorithm Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Baekjoon-1644</title><link>https://blog.ushiohayase.com/posts/baekjoon-1644/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-1644/</guid><description>백준 1644번 C++ 풀이</description><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;p&gt;하나 이상의 연속된 소수의 합으로 나타낼 수 있는 자연수들이 있다. 몇 가지 자연수의 예를 들어 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;3 : 3 (한 가지)
41 : 2+3+5+7+11+13 = 11+13+17 = 41 (세 가지)
53 : 5+7+11+13+17 = 53 (두 가지)
하지만 연속된 소수의 합으로 나타낼 수 없는 자연수들도 있는데, 20이 그 예이다. 7+13을 계산하면 20이 되기는 하나 7과 13이 연속이 아니기에 적합한 표현이 아니다. 또한 한 소수는 반드시 한 번만 덧셈에 사용될 수 있기 때문에, 3+5+5+7과 같은 표현도 적합하지 않다.&lt;/p&gt;
&lt;p&gt;자연수가 주어졌을 때, 이 자연수를 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 4,000,000)&lt;/p&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 자연수 N을 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 출력한다.&lt;/p&gt;
&lt;h2&gt;풀이과정&lt;/h2&gt;
&lt;p&gt;이 문제는 연속된 합이라는 단어를 통해 부분합과 투 포인터를 이용하면 $O(N)$ 의 시간으로 합 구하는 건 쉽게 할 수 있겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;하지만 정작 헷갈렸던건 소수를 구하는 부분이였습니다.&lt;/p&gt;
&lt;p&gt;이 문제는 N의 범위가 4,000,000까지 가능해 에라토스테네스의 체를 이용한 알고리즘을 제대로 구현하여 시간을 최대한 아끼는게 힘들었습니다.&lt;/p&gt;
&lt;p&gt;처음에는 2부터 N까지 모두 나눠보며 에라토스테네스의 체를 구현했지만 당연히 실패하였습니다.&lt;/p&gt;
&lt;p&gt;이 후 검사는 $\sqrt{N}$까지만 해도 된다는 걸 상기해냈고 검사할 때 i번째 일 때 $i * i$ 미만의 수들은 이미 검사가 되있을 것이라는 것을 깨달아 시간을 더 줄이는 코드를 작성했습니다.&lt;/p&gt;
&lt;p&gt;그리하여 최종 코드는 다음과 같습니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N;

int main()
{
    cin &amp;gt;&amp;gt; N;

    vector&amp;lt;int&amp;gt; isPrime(N + 1, true);
    vector&amp;lt;int&amp;gt; primeNumbers(1);
    vector&amp;lt;int&amp;gt; partialSum(2);
    primeNumbers[0] = 2;
    partialSum[0] = 0;
    partialSum[1] = 2;

    if (N == 1)
    {
        cout &amp;lt;&amp;lt; 0;
        return 0;
    }
    else if (N == 2)
    {
        cout &amp;lt;&amp;lt; 1;
        return 0;
    }

    for (int i = 2; i &amp;lt;= sqrt(N) + 1; ++i)
    {
        if (isPrime[i])
        {
            for (int j = i * i; j &amp;lt;= N; j += i)
            {
                isPrime[j] = false;
            }
        }
    }

    for (int i = 3; i &amp;lt;= N; ++i)
    {
        if (isPrime[i]) partialSum.emplace_back(partialSum.back() + i);
    }

    const int len = partialSum.size();
    int left = 0, right = 1;

    int result = 0;

    while (right &amp;lt; len)
    {
        const int sum = partialSum[right] - partialSum[left];
        if (sum == N)
        {
            result++;
            left++;
        }
        else if (sum &amp;gt; N)
            left++;
        else if (sum &amp;lt; N)
            right++;
    }

    cout &amp;lt;&amp;lt; result;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SocketChatServerProject-1회차</title><link>https://blog.ushiohayase.com/posts/socketchatserverproject-1%ED%9A%8C%EC%B0%A8/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/socketchatserverproject-1%ED%9A%8C%EC%B0%A8/</guid><description>C++과 Socket을 이용한 멀티스레딩 서버와 클라이언트 제작기</description><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;이 프로젝트는 기존에 개념으로만 접했던 멀티 스레딩과 소켓을 직접 제작하며 경험해 봄으로서
실제 사용할 때 어떤 어려운 점이 있는지 어떤 점을 주의해야하는지 등을 배우기 위해서 진행하는 프로젝트입니다.&lt;/p&gt;
&lt;p&gt;목표는 디스코드나 카카오톡과 같은 채팅이 가능하고 기록이 저장되며 실시간으로 채팅이 동기화되는 그런 프로그램을 만드는 것입니다.&lt;/p&gt;
&lt;h2&gt;오늘 한 것&lt;/h2&gt;
&lt;p&gt;오늘은 서버와 클라이언트의 헤더 파일에서 클래스를 정의하고 그 아래의 메서드와 멤버 변수를 선언 및 구현하였습니다.&lt;/p&gt;
&lt;p&gt;구현한 기능은 기초적인 것으로 클라이언트는 서버와 소켓을 연결하고 데이터를 주고받는 코드를 제작하였습니다.&lt;/p&gt;
&lt;p&gt;서버쪽에서는 서버가 실행이 되면 요청을 받는 소켓을 하나 열고 만약 요청이 들어오면 스레드 배열에 스레드를 추가하고 연결된 소켓도
소켓 배열에 추가하는 기능과 스레드에서 메시지를 송수신하는 기능을 기초를 구현하였습니다.&lt;/p&gt;
&lt;h2&gt;어려웠던 점&lt;/h2&gt;
&lt;p&gt;스레드를 스레드 배열에 생성하는 기능을 구현하는 것이 힘들었습니다.&lt;/p&gt;
&lt;p&gt;처음에는 스레드를 힙에 생성하고 스레드 포인터 배열에 추가하는 방식으로 구현하려 했었습니다.&lt;/p&gt;
&lt;p&gt;하지만 이 방식은 객체를 힙에 생성해서 소멸시킬 위치와 타이밍을 정하기 어려웠습니다.&lt;/p&gt;
&lt;p&gt;고민한 결과 혹시해서 멤버 함수를 이용하는 스레드 객체도 emplace_back 메서드를 사용할 수 있는지 찾아보니
멤버 함수 포인터를 이용하면 가능하다 하여 해당 방식으로 스레드 포인터 배열이 아닌 스레드 배열을 이용하여 구현헸습니다.&lt;/p&gt;
&lt;h2&gt;앞으로 개선할 점&lt;/h2&gt;
&lt;p&gt;클라이언트와 서버를 실행한 결과, 클라이언트와 서버를 하나씩 실행하고 메시지를 하나 보냈을 때 두 프로그램이 멈추는 현상이 있어 해당 현상부터 고치고
다른 기능을 구현할 예정입니다.&lt;/p&gt;
</content:encoded></item><item><title>Baekjoon-12100</title><link>https://blog.ushiohayase.com/posts/baekjoon-12100/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-12100/</guid><description>백준 12100번 문제 C++ 풀이과정</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/12100&quot;&gt;링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;문제가 길어 링크를 첨부했습니다.&lt;/p&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.&lt;/p&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;p&gt;최대 5번 이동시켜서 얻을 수 있는 가장 큰 블록을 출력한다.&lt;/p&gt;
&lt;h2&gt;풀이&lt;/h2&gt;
&lt;p&gt;먼저 이 문제는 블럭들을 4방향으로 움직이고 합치는 것이 중점입니다.&lt;/p&gt;
&lt;p&gt;이 것을 위해 매 단계마다 보드 전체를 탐색하며 블럭이 있으면 해당하는 방향으로 움직이고 합치기로 생각했습니다.&lt;/p&gt;
&lt;p&gt;보드 크기는 최대 20이고 5번까지 움직일 수 있다 조건이 주어져 있어 최악의 경우 반복횟수는 $20&lt;em&gt;20&lt;/em&gt;4^5 = 409,600$ 으로
제가 생각하는 최대 반복횟수인 10억번에 미치지 않아 충분히 제한시간 안에 풀릴 것이라 생각했습니다.&lt;/p&gt;
&lt;p&gt;이 문제의 충돌은 블록이 있는 칸에서 가장 마지막으로 탐색한 칸을 저장한 다음 해당하는 칸의 숫자가 현위치 블럭의 숫자와
같으면 충돌하게 구현하였습니다.&lt;/p&gt;
&lt;p&gt;조건에서 충돌은 블럭 당 1번이라 그랬는데 이 충돌을 매 행이나 열마다 충돌 여부 배열을 만들어 이번 회차에 충돌했는지
여부를 기록하고 사용해 이 조건을 구현하였습니다.&lt;/p&gt;
&lt;p&gt;충돌이 아닌 이동의 경우는 아까 사용했던 블럭이 있는 마지막 탐색 칸의 한 칸 앞으로 바로 이동시키는 것으로 구현했습니다.&lt;/p&gt;
&lt;p&gt;이렇게 이동 및 충돌 구현은 쉬웠지만 정작 헤맸던 부분은 객체 수명 관리였습니다.&lt;/p&gt;
&lt;p&gt;처음 시도하고 계속 오류가 났는데 시도하고 코드를 탐색하다 보니 아래와 같이 4방향으로 탐색할 때 같은 행렬을
사용하는 실수를 저질렀습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; arr{q.front().second};
        q.pop();

        for (int i = 0; i &amp;lt; 4; ++i)
        {
            moveArr(arr, dx[i], dy[i]);
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 것을 해결하기 위해 지역변수로 선언하고 참조로 넘겼더니 객체가 사라지는 문제가 있었고 결국엔 객체를 깊은 복사 생성자로
매 분기마다 복사하는 것으로 최종적으로 구현했습니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;

using namespace std;

int N;

int dx[4]{1, -1, 0, 0};
int dy[4]{0, 0, 1, -1};

void moveArr(vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; arr, int deltaX, int deltaY)
{
    if (deltaX &amp;gt; 0)
    {
        for (int j = 0; j &amp;lt; N; ++j)
        {
            int blockCol = N;  // 블록이 있는 칸 중 열이 가장 작은 수
            vector&amp;lt;bool&amp;gt; notMerge(N);
            for (int i = N - 1; i &amp;gt;= 0; --i)
            {
                if (arr[j][i] == 0) continue;

                // 현위치 블록과 블록이 도착할 칸 다음 칸 블록이 같은 수라면
                if (blockCol &amp;lt; N &amp;amp;&amp;amp; arr[j][i] == arr[j][blockCol] &amp;amp;&amp;amp;
                    !notMerge[blockCol])
                {
                    arr[j][blockCol] *= 2;
                    arr[j][i] = 0;
                    notMerge[blockCol] = true;
                }
                else
                {
                    arr[j][blockCol - 1] = arr[j][i];
                    if (blockCol - 1 != i) arr[j][i] = 0;
                    blockCol -= 1;  // 블록이 있는 칸 갱신
                }
            }
        }
    }
    else if (deltaX &amp;lt; 0)
    {
        for (int j = 0; j &amp;lt; N; ++j)
        {
            int blockCol = -1;  // 블록이 있는 칸 중 열이 가장 큰 수
            vector&amp;lt;bool&amp;gt; notMerge(N);
            for (int i = 0; i &amp;lt; N; ++i)
            {
                if (arr[j][i] == 0) continue;

                // 현위치 블록과 블록이 도착할 칸 다음 칸 블록이 같은 수라면
                if (blockCol &amp;gt; -1 &amp;amp;&amp;amp; arr[j][i] == arr[j][blockCol] &amp;amp;&amp;amp;
                    !notMerge[blockCol])
                {
                    arr[j][blockCol] *= 2;
                    arr[j][i] = 0;
                    notMerge[blockCol] = true;
                }
                else
                {
                    arr[j][blockCol + 1] = arr[j][i];
                    if (blockCol + 1 != i) arr[j][i] = 0;
                    blockCol += 1;  // 블록이 있는 칸 갱신
                }
            }
        }
    }
    else if (deltaY &amp;gt; 0)
    {
        for (int j = 0; j &amp;lt; N; ++j)
        {
            int blockRow = N;  // 블록이 있는 칸 중 행이 가장 작은 수
            vector&amp;lt;bool&amp;gt; notMerge(N);
            for (int i = N - 1; i &amp;gt;= 0; --i)
            {
                if (arr[i][j] == 0) continue;

                // 현위치 블록과 블록이 도착할 칸 다음 칸 블록이 같은 수라면
                if (blockRow &amp;lt; N &amp;amp;&amp;amp; arr[i][j] == arr[blockRow][j] &amp;amp;&amp;amp;
                    !notMerge[blockRow])
                {
                    arr[blockRow][j] *= 2;
                    arr[i][j] = 0;
                    notMerge[blockRow] = true;
                }
                else
                {
                    arr[blockRow - 1][j] = arr[i][j];
                    if (blockRow - 1 != i) arr[i][j] = 0;
                    blockRow -= 1;  // 블록이 있는 칸 갱신
                }
            }
        }
    }
    else if (deltaY &amp;lt; 0)
    {
        for (int j = 0; j &amp;lt; N; ++j)
        {
            int blockRow = -1;
            vector&amp;lt;bool&amp;gt; notMerge(N);
            for (int i = 0; i &amp;lt; N; ++i)
            {
                if (arr[i][j] == 0) continue;
                if (blockRow &amp;gt; -1 &amp;amp;&amp;amp; arr[i][j] == arr[blockRow][j] &amp;amp;&amp;amp;
                    !notMerge[blockRow])
                {
                    arr[blockRow][j] *= 2;
                    arr[i][j] = 0;
                    notMerge[blockRow] = true;
                }
                else
                {
                    arr[blockRow + 1][j] = arr[i][j];
                    if (blockRow + 1 != i) arr[i][j] = 0;
                    blockRow += 1;  // 블록이 있는 칸 갱신
                }
            }
        }
    }
}

int bfs(const vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; init)
{
    queue&amp;lt;pair&amp;lt;int, vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;gt;&amp;gt; q;

    q.push({0, init});

    int result = 0;

    while (!q.empty())
    {
        const int level = q.front().first;

        for (int i = 0; i &amp;lt; 4; ++i)
        {
            vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; arr(q.front().second);
            moveArr(arr, dx[i], dy[i]);
            if (level == 4)
            {
                int tmp = 0;
                for (int j = 0; j &amp;lt; N; ++j)
                    for (int k = 0; k &amp;lt; N; ++k)
                        result = arr[j][k] &amp;gt; result ? arr[j][k] : result;
            }
            else
            {
                q.push({level + 1, arr});
            }
        }
        q.pop();
    }

    return result;
}

int main()
{
    vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; arr;
    cin &amp;gt;&amp;gt; N;

    arr.resize(N);

    for (int i = 0; i &amp;lt; N; ++i)
    {
        arr[i].resize(N);
        for (int j = 0; j &amp;lt; N; ++j)
        {
            cin &amp;gt;&amp;gt; arr[i][j];
        }
    }

    cout &amp;lt;&amp;lt; bfs(arr);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Baekjoon-2467</title><link>https://blog.ushiohayase.com/posts/baekjoon-2467/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/baekjoon-2467/</guid><description>백준 2467번 C++ 풀이</description><pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;p&gt;KOI 부설 과학연구소에서는 많은 종류의 산성 용액과 알칼리성 용액을 보유하고 있다. 각 용액에는 그 용액의 특성을 나타내는 하나의 정수가 주어져있다. 산성 용액의 특성값은 1부터 1,000,000,000까지의 양의 정수로 나타내고, 알칼리성 용액의 특성값은 -1부터 -1,000,000,000까지의 음의 정수로 나타낸다.&lt;/p&gt;
&lt;p&gt;같은 양의 두 용액을 혼합한 용액의 특성값은 혼합에 사용된 각 용액의 특성값의 합으로 정의한다. 이 연구소에서는 같은 양의 두 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들려고 한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 주어진 용액들의 특성값이 [-99, -2, -1, 4, 98]인 경우에는 특성값이 -99인 용액과 특성값이 98인 용액을 혼합하면 특성값이 -1인 용액을 만들 수 있고, 이 용액의 특성값이 0에 가장 가까운 용액이다. 참고로, 두 종류의 알칼리성 용액만으로나 혹은 두 종류의 산성 용액만으로 특성값이 0에 가장 가까운 혼합 용액을 만드는 경우도 존재할 수 있다.&lt;/p&gt;
&lt;p&gt;산성 용액과 알칼리성 용액의 특성값이 정렬된 순서로 주어졌을 때, 이 중 두 개의 서로 다른 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액을 찾는 프로그램을 작성하시오.&lt;/p&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;p&gt;첫째 줄에는 전체 용액의 수 N이 입력된다. N은 2 이상 100,000 이하의 정수이다. 둘째 줄에는 용액의 특성값을 나타내는 N개의 정수가 빈칸을 사이에 두고 오름차순으로 입력되며, 이 수들은 모두 -1,000,000,000 이상 1,000,000,000 이하이다. N개의 용액들의 특성값은 모두 서로 다르고, 산성 용액만으로나 알칼리성 용액만으로 입력이 주어지는 경우도 있을 수 있다.&lt;/p&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;p&gt;첫째 줄에 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액의 특성값을 출력한다. 출력해야 하는 두 용액은 특성값의 오름차순으로 출력한다. 특성값이 0에 가장 가까운 용액을 만들어내는 경우가 두 개 이상일 경우에는 그 중 아무것이나 하나를 출력한다.&lt;/p&gt;
&lt;h2&gt;풀이과정&lt;/h2&gt;
&lt;p&gt;먼저 이 문제를 보았을 때 실수로 알고리즘 분류 태그를 누르는 바람에 이분 탐색과 투 포인터라는 키워드를 보아
어떻게 풀어야하는지 절반은 알고 시작했습니다.&lt;/p&gt;
&lt;p&gt;투 포인터를 이용하면 이미 정렬된 입력을 받기에 양 끝에서 가운데로 오며 두 포인터가 가르키는 숫자의 합의 절댓값의 최소를
찾으면 되는 문제라고 생각했습니다.&lt;/p&gt;
&lt;p&gt;먼저 최솟값을 찾을 변수를 최대값으로 설정하고 두 포인터가 가르키는 수의 차이를 빼
만약 더 작다면 갱신하는 방식으로 코드를 작성하였습니다.&lt;/p&gt;
&lt;p&gt;하지만 이 코드는 두 수가 모두 음수일 때를 생각하지 못해 오류가 났고 최대값을 1e+9 + 1로
했는데 만약 두 가르킨 수가 1e+9 + 1e+9 - 1이면 저 최대값을 넘어버리는 문제가 있었습니다.&lt;/p&gt;
&lt;p&gt;그리하여 이러한 문제를 해결하고 문제를 맞췄습니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

constexpr int INF = 1e+9 * 2;

using namespace std;

int N;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N;

    vector&amp;lt;int&amp;gt; arr;

    arr.resize(N);

    for (int i = 0; i &amp;lt; N; ++i)
        cin &amp;gt;&amp;gt; arr[i];

    int left = 0;
    int right = N - 1;
    int result = INF;

    pair&amp;lt;int, int&amp;gt; answer;

    while (right - left &amp;gt;= 1)
    {
        int tmp = arr[left] + arr[right];
        if (abs(tmp) &amp;lt; abs(result))
        {
            answer.first = arr[left];
            answer.second = arr[right];
            result = tmp;
        }

        if (tmp &amp;gt; 0)
            right--;
        else
            left++;
    }

    cout &amp;lt;&amp;lt; answer.first &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; answer.second;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>삼항연산자와 if-else문</title><link>https://blog.ushiohayase.com/posts/controlstatementandtriadoperator/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/controlstatementandtriadoperator/</guid><description>삼항연산자와 제어문의 최적화별 속도 비교 및 이유 분석</description><pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;이 블로그에선 삼항 연산자와 제어문으로 변수 대입하는 것을 100억번 반복시키고 그 결과를 분석할 것입니다.&lt;/p&gt;
&lt;h2&gt;테스트 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;chrono&amp;gt;
#include &amp;lt;iostream&amp;gt;

volatile int x = 0; // 최적화 방지

int main()
{
    const long long N = 1e10;
    int a2 = 0, b2 = 1, c2 = 2;
    int a1 = 0, b1 = 1, c1 = 2;

    // if-else 테스트
    auto start2 = std::chrono::high_resolution_clock::now();
    for (long long i = 0; i &amp;lt; N; ++i)
    {
        if (i % 2)
        {
            a1 = b1;
        }
        else
        {
            a1 = c2;
        }
        x = a1;
    }
    auto end2 = std::chrono::high_resolution_clock::now();

    // 삼항 연산자 테스트
    auto start1 = std::chrono::high_resolution_clock::now();
    for (long long i = 0; i &amp;lt; N; ++i)
    {
        a2 = (i % 2) ? b2 : c2;
        x = a2;
    }
    auto end1 = std::chrono::high_resolution_clock::now();

    // 결과 출력
    auto duration1 = std::chrono::duration_cast&amp;lt;std::chrono::microseconds&amp;gt;(end1 - start1);
    auto duration2 = std::chrono::duration_cast&amp;lt;std::chrono::microseconds&amp;gt;(end2 - start2);
    std::cout &amp;lt;&amp;lt; &quot;Ternary: &quot; &amp;lt;&amp;lt; duration1.count() &amp;lt;&amp;lt; &quot;μs\n&quot;;
    std::cout &amp;lt;&amp;lt; &quot;If-Else: &quot; &amp;lt;&amp;lt; duration2.count() &amp;lt;&amp;lt; &quot;μs\n&quot;;

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 1번 실행할때 마다 a 변수에 각각 다른 변수(b,c)를 대입하는 코드이고 최적화를 하지 못하게 하기 위하여
강제로 메모리에 쓰게 만들기 위해 &lt;code&gt;volatile&lt;/code&gt; 키워드를 삽입한 변수를 사용한 코드를 삽입했습니다..&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;컴파일 옵션&lt;/th&gt;
&lt;th&gt;삼항연산자&lt;/th&gt;
&lt;th&gt;if-else문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최적화없음(/Od)&lt;/td&gt;
&lt;td&gt;5,545,151μs&lt;/td&gt;
&lt;td&gt;4,712,050μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;많은최적화(/O2)&lt;/td&gt;
&lt;td&gt;5,527,017μs&lt;/td&gt;
&lt;td&gt;4,716,471μs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;실험 결과 삼항 연산자가 제어문보다 속도가 더 느리게 나왔습니다.&lt;/p&gt;
&lt;h2&gt;어셈블리 코드&lt;/h2&gt;
&lt;p&gt;위 코드의 삼항연산자와 제어문의 디어셈블리 코드는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;제어문 :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mov rax, qword ptr [rsp+58h]
cqo
and rax,1
xor rax,rdx
sub rax,rdx
test rax,rax

je main+0ADh
mov eax,dword ptr [b1]
mov dword ptr [a1], eax

jmp main+0B5h
mov eax,dword ptr [c2]
mov dword ptr [a1],eax

mov eax,dword ptr [a1]
mov dword ptr [x], eax
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;삼항 연산자 :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mov rax,qword ptr[rsp+98h]
cqo
and rax,1
xor rax,rdx
sub rax,rdx
test rax,rax

je main+134h
mov eax,dword ptr [b2]
mov dword ptr [rsp+108h],eax

jmp main+13Fh
mov eax,dword ptr [c2]
mov dword ptr [rsp+108h],eax

mov eax,dword ptr [rsp+108h]
mov dword ptr [a2], eax

mov eax,dword ptr [a2]
mov dword ptr [x],eax
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;이유 분석&lt;/h2&gt;
&lt;p&gt;일단 위의 코드는 조건이 0과 1을 규칙적으로 반복하는 매우 예측 가능한 패턴입니다.&lt;/p&gt;
&lt;p&gt;따라서 CPU의 분기 예측은 100% 정확도로 예측합니다.&lt;/p&gt;
&lt;p&gt;예측을 성공하면 파이프라인이 완전히 활용되며 분기 예측의 jmp 명령어가 실제로 실행되지 않고 추측 실행만으로 처리됩니다.&lt;/p&gt;
&lt;p&gt;또 어셈블리 코드를 보았을때 제어문은 메모리에 있는 각각의 변수들을 레지스터 1번만 거치고 a변수에 저장하지만
삼항연산자는 메모리에서 레지스터를 거친후 다시 메모리에 저장하고 그걸 다시 레지스터로 불러들였다가 메모리에 저장하는
코드를 가지고 있어 속도가 훨씬 느려지게 됩니다.&lt;/p&gt;
&lt;p&gt;추가적으로 이 어셈블리 코드에서는 나오지 않았지만 삼항연산자는 데이터 의존성이 생길 수 있습니다.&lt;/p&gt;
&lt;p&gt;삼항연산자는 조건 평가, 값 선택(cmov 명령어), 결과 저장 단계로 이루어지는데 값 선택 단계는 조건 평가 단계 이후에만 진행될 수 있기에
삼항연산자의 결과를 사용하는 다음 명령어는 연산이 끝난뒤에만 진행될 수 있게 됩니다.&lt;/p&gt;
&lt;h2&gt;O2옵션이 더 느린 이유&lt;/h2&gt;
&lt;p&gt;위 실험에서는 최적화를 하지 않는 /Od옵션이 /O2옵션보다 더 빠르게 실행되었습니다.&lt;/p&gt;
&lt;p&gt;이 이유에는 여러 복합적인 것이 섞여있을것이기에 확신하기는 어렵지만 아마 최적화를 하며 분기 예측 패턴을 파괴같은 이유로 거나
컴퓨터의 다른 프로세스가 파이프라인에 섞여들어갔기 때문으로 추측됩니다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;삼항 연산자는 분기 미스 예측을 제거하지만, 모든 실행에서 조건 평가 완료를 강제하여 오히려 성능 저하를 초래할 수 있습니다.&lt;/p&gt;
&lt;p&gt;최적화 시에는 분기 패턴의 예측 가능성과 데이터 의존성 비용을 종합적으로 평가해야합니다.&lt;/p&gt;
</content:encoded></item><item><title>백준 2252번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj2252/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj2252/</guid><description>백준 2252번 C++ 풀이</description><pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;N명의 학생들을 키 순서대로 줄을 세우려고 한다. 각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하기로 하였다. 그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.&lt;/p&gt;
&lt;p&gt;일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N(1 ≤ N ≤ 32,000), M(1 ≤ M ≤ 100,000)이 주어진다. M은 키를 비교한 횟수이다. 다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다.&lt;/p&gt;
&lt;p&gt;학생들의 번호는 1번부터 N번이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 학생들을 앞에서부터 줄을 세운 결과를 출력한다. 답이 여러 가지인 경우에는 아무거나 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;p&gt;이 문제가 속한 Solved.ac의 클래스가 5이고 거기에 있는 알고리즘 목록을 생각해봤을때 이 문제는
위상 정렬을 사용하는 문제같았다.&lt;/p&gt;
&lt;p&gt;위상 정렬은 비순환 지향 그래프(DAG; Directed Acyclic Graph)에서 방향을 거꾸로 하지 않고 순서대로
배열을 하는 알고리즘을 말합니다.&lt;/p&gt;
&lt;p&gt;위상 정렬에는 똑같이 시간 복잡도가 $O(V + E)$인 Kahn 알고리즘과 DFS를 이용한 알고리즘이 있습니다.&lt;/p&gt;
&lt;p&gt;저는 그 중 Kahn 알고리즘을 사용하였습니다.&lt;/p&gt;
&lt;p&gt;Kahn 알고리즘은 진입차수(노드로 연결된 엣지 개수)가 0인 노드를 전부 큐에 집어넣습니다.&lt;/p&gt;
&lt;p&gt;그 후 큐에서 노드를 하나 뽑아 그 노드가 가르키는 노드의 진입차수를 모두 1씩 줄입니다.&lt;/p&gt;
&lt;p&gt;큐가 빌 때까지 반복합니다.&lt;/p&gt;
&lt;p&gt;그렇게 했을때 큐에서 빠져나간 순서가 위상 정렬된 순서가 됩니다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int N, M;

vector&amp;lt;int&amp;gt; adj[32001];
int inDegree[32001];
vector&amp;lt;int&amp;gt; result;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;

    for (int i = 0; i &amp;lt; M; ++i)
    {
        int x, y;
        cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;

        adj[x].push_back(y);
        inDegree[y]++;
    }

    queue&amp;lt;int&amp;gt; q;

    for (int i = 1; i &amp;lt;= N; ++i)
    {
        if (inDegree[i] == 0)
            q.push(i);
    }

    while (!q.empty())
    {
        int u = q.front();
        q.pop();

        result.push_back(u);

        for (int v : adj[u])
        {
            inDegree[v]--;
            if (inDegree[v] == 0)
                q.push(v);
        }
    }

    for (int x : result)
        cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot; &quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
</content:encoded></item><item><title>Direct 3D 11의 VertexShader</title><link>https://blog.ushiohayase.com/posts/direct3d11vertexshader/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/direct3d11vertexshader/</guid><description>D3D11의 Vertex Shader</description><pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;Vertex Shader는 하는 일이 매우 심플합니다.&lt;/p&gt;
&lt;p&gt;각자 자신만의 좌표계에 있는 정점 데이터들을 한 좌표계로 모으고
그걸 카메라의 좌표계로 변환하고 카메라 시야 안에 있는 것들만 사용하며
원근법을 적용합니다.&lt;/p&gt;
&lt;p&gt;각각의 변환은 이미 행렬로서 구현이 되어있어 &lt;code&gt;CreateVertexShader&lt;/code&gt; 함수를 이용해 생성해주기만 하면 됩니다.&lt;/p&gt;
&lt;p&gt;하지만 테셀레이션을 사용할 것이라면 이 셰이더 단계에서 World-View Projection을 수행하면 안됩니다.&lt;/p&gt;
&lt;p&gt;왜냐하면 테셀레이션 작업시에 다른 많은 Vertex를 새롭게 생성시키기 때문입니다.&lt;/p&gt;
</content:encoded></item><item><title>백준 1806번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj1806/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj1806/</guid><description>백준 1806번 C++ 풀이</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;10,000 이하의 자연수로 이루어진 길이 N짜리 수열이 주어진다. 이 수열에서 연속된 수들의 부분합 중에 그 합이 S 이상이 되는 것 중, 가장 짧은 것의 길이를 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N (10 ≤ N &amp;lt; 100,000)과 S (0 &amp;lt; S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 구하고자 하는 최소의 길이를 출력한다. 만일 그러한 합을 만드는 것이 불가능하다면 0을 출력하면 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;p&gt;합을 이용한 문제이니 부분합을 이용하고 구간만 필요하니 투 포인터를 이용해서 부분합 배열을 검사해 내가며
길이가 가장 짧은 것을 찾는걸 생각했다.&lt;/p&gt;
&lt;p&gt;이 경우 부분합 배열을 모두 탐색해야하므로 $O(N)$의 시간 복잡도를 가져 제한시간과 숫자 범위를 고려할 때
성공할 만하다고 결론지었다.&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numeric&amp;gt;
using namespace std;

constexpr int INF = numeric_limits&amp;lt;int&amp;gt;::max();

int N, S;

int arr[100001];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; S;

    for (int i = 1; i &amp;lt;= N; ++i)
    {
        cin &amp;gt;&amp;gt; arr[i];
        arr[i] += arr[i - 1];
    }

    int len = INF;

    int first = 1;
    int second = 1;

    while (second != N + 1)
    {
        int sum = arr[second] - arr[first - 1];
        if (sum &amp;gt;= S)
        {
            len = second - first + 1 &amp;lt; len ? second - first + 1 : len;
            first++;
        }
        else
            second++;
    }

    cout &amp;lt;&amp;lt; ((len != INF) ? len : 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다
:::&lt;/p&gt;
&lt;p&gt;깃헙링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Gold/1806.%E2%80%85%EB%B6%80%EB%B6%84%ED%95%A9&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>DirectX의 InputAssembler</title><link>https://blog.ushiohayase.com/posts/directx3drenderingpipelineia/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/directx3drenderingpipelineia/</guid><description>다이렉트 3D의 그래픽 파이프라인 단계 중 하나인 IA(Input-Assembler)에 대해 알아봅니다</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;Input-Assembler는 그래픽 파이프라인에서 첫 번째 단계를 담당하고 있는 단계입니다.&lt;/p&gt;
&lt;p&gt;IA(Input-Assembler)는 primitive data(점, 선, 삼각형)를 유저가 채운 버퍼에서 읽고
다른 파이프라인 단계에서 쓸 수 있도록 모읍니다.&lt;/p&gt;
&lt;p&gt;이 모은 데이터는 다음 그래픽 파이프라인 단계인 GS(Geometry Shader)에 사용될 수 있도록
인접한 점들끼리 모읍니다.&lt;/p&gt;
&lt;p&gt;데이터의 Adjacency는 GS에만 적용되는데 이 것을 적용하면 각 삼각형마다 3개의 Adjancency 데이터가
추가로 데이터에 포함됩니다.&lt;/p&gt;
&lt;p&gt;만약 면의 끝 쪽이라거나의 이유로 정점이 없더라도 데이터를 넣을 땐 더미 정점이라도 넣어야 합니다.&lt;/p&gt;
&lt;p&gt;이 IA 단계는 Input 버퍼를 만들고 Input-Layout 객체를 만들고 IA 단계에 묶는 방식으로 진행됩니다.&lt;/p&gt;
&lt;h2&gt;Input Buffers&lt;/h2&gt;
&lt;p&gt;이 인풋 버퍼에는 정점 버퍼와 인덱스(색인) 버퍼, 2가지 종류가 있습니다.&lt;/p&gt;
&lt;p&gt;정점 버퍼는 IA 단계에 정점 데이터를 공급해주는 버퍼로 필수적인 버퍼입니다.&lt;/p&gt;
&lt;p&gt;인덱스 버퍼는 정점 버퍼로부터의 색인을 정점에 공급해주는 버퍼입니다.&lt;/p&gt;
&lt;p&gt;이 정점 버퍼들은 여러개 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;정점 버퍼는 프로그래머가 구조체를 정의하고 그 구조체로 만든 배열과 구조체에 대한 정보가 든
Direct 3D가 지원하는 구조체(D3D11_BUFFER_DESC &lt;a href=&quot;%5BD3D11_BUFFER_DESC_MS_Learn%5D(https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc)&quot;&gt;^1&lt;/a&gt;)를 이용해 생성합니다.&lt;/p&gt;
&lt;p&gt;생성할 때는 디바이스 객체의 CreateBuffer 메서드를 사용해서 생성합니다.&lt;/p&gt;
&lt;h2&gt;Input-Layout Object&lt;/h2&gt;
&lt;p&gt;여기서는 앞서 생성한 버퍼에 대한 정보를 추가적으로 Direct 3D에 제공합니다.&lt;/p&gt;
&lt;p&gt;D3D11_INPUT_ELEMENT_DESC &lt;a href=&quot;%5BD3D11_INPUT_ELEMENT_DESC%5D(https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/ns-d3d11-d3d11_input_element_desc)&quot;&gt;^2&lt;/a&gt; 배열로 생성해서 디바이스 객체의 CreateInputLayout 메서드로 Direct 3D에 등록합니다.&lt;/p&gt;
&lt;p&gt;이 구조체의 각 요소는 각 정점의 데이터를 묘사합니다.&lt;/p&gt;
&lt;p&gt;이 인풋 레이아웃 객체는 셰이더의 인풋 시그니처에 맞기만하면 여러번 사용해도 됩니다.&lt;/p&gt;
&lt;h2&gt;단계에 바인딩&lt;/h2&gt;
&lt;p&gt;생성한 오브젝트들은 각각 디바이스 컨텍스트 객체의 IASetVertexBuffers, IASetIndexBuffer, IASetInputLayout 메서드를
통해 단계에 바인딩합니다.&lt;/p&gt;
</content:encoded></item><item><title>DirectX 3D&apos;s Primitive Topologies</title><link>https://blog.ushiohayase.com/posts/directx3dprimitivetopologies/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/directx3dprimitivetopologies/</guid><description>DirectX 3D가 지원하는 Primitive Type(Topology)에 대해 알아봅니다</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;DirectX 3D에는 Primitive Type이란 것과 Primitive Adjacency가 있습니다.&lt;/p&gt;
&lt;p&gt;Primitive Type은 각 정점(Vertex)들을 파이프라인에서 어떻게 해석하고 렌더링할지 정합니다.&lt;/p&gt;
&lt;p&gt;Primitive Adjacency는 둘러싸고 있는 정점들을 데이터에 포함할 지 여부입니다.&lt;/p&gt;
&lt;p&gt;각 Primitive Type마다 Primitive Adjacency에 따라 2종류의 타입이 존재합니다.&lt;/p&gt;
&lt;p&gt;이 것은 각 Primitive Type의 이름 뒤에 &lt;code&gt;_ADJ&lt;/code&gt;가 붙는지 여부에 따라 구별됩니다.&lt;/p&gt;
&lt;h2&gt;Primitive Types&lt;/h2&gt;
&lt;p&gt;기본적인 Primitive Types는 Point List, Line List, Line Strip, Triangle List, Triangle Strip이 존재합니다.&lt;/p&gt;
&lt;p&gt;각각의 타입들은 다음 그림과 같이 해석됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./image.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;여기서 Strip은 다른 데이터끼리 같은 정점을 공유하고 List는 그렇지 않습니다.&lt;/p&gt;
&lt;p&gt;그리하여 기하 구조에 따라 다르겠지만 각각의 Strip은
일반적으로는 메모리 접근에 대한 시간을 줄여줍니다.&lt;/p&gt;
</content:encoded></item><item><title>DirectX 렌더링 파이프라인</title><link>https://blog.ushiohayase.com/posts/directx_rendering_pipline/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/directx_rendering_pipline/</guid><description>다이렉트X 11의 렌더링 파이프라인을 알아봅니다</description><pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;DirectX 11의 그래픽 파이프라인은 DirectX 10의 그래픽 파이프라인과 같은 단계를 지원하고
거기에 추가적인 단계가 추가되어져 있습니다.&lt;/p&gt;
&lt;p&gt;파이프라인은 아래 그림과 같이 이루어져 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./pipeline.png&quot; alt=&quot;pipeline&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이 파이프라인을 사용하여 DirectX에서 3D 그래픽을 모니터로 출력할 수 있습니다.&lt;/p&gt;
&lt;p&gt;각각의 파이프라인 단계들은 미리 정의하여 런타임에 실행됩니다.&lt;/p&gt;
&lt;p&gt;이 단계들을 중간에 수정할 수도 있겠지만 그럴 시 CPU와 GPU간의 통신이 필요해
속도가 느려지게됩니다.&lt;/p&gt;
&lt;p&gt;각 단계들은 HLSL(High-Level Shader Language)라는 언어를 사용해 구성됩니다.&lt;/p&gt;
</content:encoded></item><item><title>백준 11660번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-11660/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-11660/</guid><description>백준 11660번 C++ 풀이</description><pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;N×N개의 수가 N×N 크기의 표에 채워져 있다. (x1, y1)부터 (x2, y2)까지 합을 구하는 프로그램을 작성하시오. (x, y)는 x행 y열을 의미한다.&lt;/p&gt;
&lt;p&gt;예를 들어, N = 4이고, 표가 아래와 같이 채워져 있는 경우를 살펴보자.&lt;/p&gt;
&lt;p&gt;1	2	3	4
2	3	4	5
3	4	5	6
4	5	6	7
여기서 (2, 2)부터 (3, 4)까지 합을 구하면 3+4+5+4+5+6 = 27이고, (4, 4)부터 (4, 4)까지 합을 구하면 7이다.&lt;/p&gt;
&lt;p&gt;표에 채워져 있는 수와 합을 구하는 연산이 주어졌을 때, 이를 처리하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 표의 크기 N과 합을 구해야 하는 횟수 M이 주어진다. (1 ≤ N ≤ 1024, 1 ≤ M ≤ 100,000) 둘째 줄부터 N개의 줄에는 표에 채워져 있는 수가 1행부터 차례대로 주어진다. 다음 M개의 줄에는 네 개의 정수 x1, y1, x2, y2 가 주어지며, (x1, y1)부터 (x2, y2)의 합을 구해 출력해야 한다. 표에 채워져 있는 수는 1,000보다 작거나 같은 자연수이다. (x1 ≤ x2, y1 ≤ y2)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;총 M줄에 걸쳐 (x1, y1)부터 (x2, y2)까지 합을 구해 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;p&gt;전형적인 부분합 알고리즘을 이용한 문제입니다.&lt;/p&gt;
&lt;p&gt;부분합 알고리즘은 요소들의 배열말고 다른 곳에 배열 일부 구간에 대한 합을 저장해두는 알고리즘으로
N개의 요소가 주어졌을 때 원래대로 배열의 합을 구하려면 $O(N)$이 필요하지만 부분합에서는 $O(1)$로 가능합니다.&lt;/p&gt;
&lt;p&gt;이 문제에서 sum이 부분합 배열, arr이 요소 배열일때 부분합 배열의 점화식은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sum[i][j] = arr[i-1][j-1] + sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;또 arr 배열의 i1행 j1열부터 i2행 j2열까지의 합은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sum[i2+1][j2+1] - sum[i1][j2+1] - sum[i2+1][j1] + sum[i1][j1]&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numeric&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;
int *arrData;
int **arr;

int *sumData;
int **sum;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;

    arrData = new int[N * N];
    arr = new int *[N];

    sumData = new int[(N + 1) * (N + 1)];
    sum = new int *[N + 1];

    for (int i = 0; i &amp;lt;= N; ++i)
    {
        arr[i] = arrData + i * N;
        sum[i] = sumData + i * (N + 1);
        for (int j = 0; j &amp;lt;= N; ++j)
        {
            if (i != N &amp;amp;&amp;amp; j != N)
                cin &amp;gt;&amp;gt; arr[i][j];
            if (i != 0 &amp;amp;&amp;amp; j != 0)
                sum[i][j] = arr[i - 1][j - 1] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
            else
                sum[i][j] = 0;
        }
    }
    sum[N] = sumData + N * (N + 1);

    for (int i = 0; i &amp;lt; M; ++i)
    {
        int x1, x2, y1, y2;
        cin &amp;gt;&amp;gt; x1 &amp;gt;&amp;gt; y1 &amp;gt;&amp;gt; x2 &amp;gt;&amp;gt; y2;

        cout &amp;lt;&amp;lt; sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1] &amp;lt;&amp;lt; &quot;\n&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/11660.%E2%80%85%EA%B5%AC%EA%B0%84%E2%80%85%ED%95%A9%E2%80%85%EA%B5%AC%ED%95%98%EA%B8%B0%E2%80%855&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>DirectX3D 초기화</title><link>https://blog.ushiohayase.com/posts/directx_2/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/directx_2/</guid><description>DirectX3D를 초기화하고 화면에 출력할 때까지의 필요한 요소를 찾고 탐구해봅니다.</description><pubDate>Sat, 29 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;DirectX3D는 프로그래머와 그래픽 하드웨어 사이에서 추상화를 담당하는 중재자라고 할 수 있습니다.
프로그래머는 추상화된 계층이 있어 하드웨어의 명령어를 알 필요까지 없게 하고 하드웨어 설계자도 DirectX의 요구 사항에 맞춰
설계하여 사용하기 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;여기서는 DirectX3D의 초기화 과정을 순차적으로 따라가며 각각의 요소가 무엇을 의미하는지 차근차근 알아가 볼 것입니다.&lt;/p&gt;
&lt;p&gt;DirectX3D의 화면에 초기화할 때까지 과정을 일반적으로 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;디바이스 인터페이스와 디바이스 컨텍스트 인터페이스 생성&lt;/li&gt;
&lt;li&gt;스왑체인 생성&lt;/li&gt;
&lt;li&gt;렌더 타겟 뷰 생성&lt;/li&gt;
&lt;li&gt;Depth/Stencil 뷰 생성&lt;/li&gt;
&lt;li&gt;뷰들을 출력 병합기 (Output Merger)에 묶기&lt;/li&gt;
&lt;li&gt;뷰포트 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;DirectX의 텍스처&lt;/h2&gt;
&lt;p&gt;DirectX에서 1차원 텍스처는 1차원 배열과 비슷하고, 2차원 텍스처는 2차원 배열과 비슷하고,
3차원 텍스처는 3차원 배열과 비슷합니다.
이 텍스처의 원소들의 형식(Format)은 DXGI_FORMAT 열거형의 한 멤버에 반드시 해당해야 합니다.&lt;/p&gt;
&lt;p&gt;일반적으로 텍스처는 이미지 자료를 담지만, 깊이(Depth) 정보와 같은 다른 자료를 담는 것도 가능합니다.&lt;/p&gt;
&lt;p&gt;GPU는 텍스처에 대해 필터링이라든가 다중 표본 추출과 같은 연산을 수행할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Device Interface &amp;amp;&amp;amp; Device Context Interface&lt;/h2&gt;
&lt;p&gt;이 ID3D11Device(Device Interface)와 ID3D11DeviceContext(Device Context Interface)는 각각
기능 지원 점검과 자원 할당 / 렌더 대상 설정 및 그래픽 파이프라인에 묶기, GPU가 수행할 렌더링 명령 지시와 같은 역할을 수행하는 객체입니다.&lt;/p&gt;
&lt;p&gt;ID3D11Device와 ID3D11DeviceContext는 D3D11CreateDevice &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice)&quot;&gt;^1&lt;/a&gt;함수를 통해 생성합니다.&lt;/p&gt;
&lt;h2&gt;스왑체인(SwapChain;교환 사슬)&lt;/h2&gt;
&lt;p&gt;DirectX는 화면을 2개의 버퍼를 이용하여 그립니다.&lt;/p&gt;
&lt;p&gt;정확히는 후면 버퍼와 전면 버퍼가 있고 한 프레임의 장면 전체를 후면 버퍼에 그린 후 후면 버퍼와 전면 버퍼의
역할을 맞바꾸고 맞바뀐뒤 전면 버퍼를 화면에 표시하며(Presenting; 제시) 바꾸기 전 전면버퍼는 후면 버퍼가 되어 장면을 그리는 걸 반복합니다.&lt;/p&gt;
&lt;p&gt;이 때 전면 버퍼와 후면 버퍼의 맞바꾸는 것은 포인터만 맞바꾸는 것이기에 효율적인 연산입니다.&lt;/p&gt;
&lt;p&gt;이 후면 버퍼의 개수에 따라 1개일땐 이중 버퍼링, 2개일 땐 삼중 버퍼링이라고 부릅니다.&lt;/p&gt;
&lt;p&gt;이 버퍼는 크기 변경을 위한 메서드(IDXGISwapChain::ResizeBuffers)와 버퍼의 제시를 위한 메서드(IDXGISwapChain::Present)를 제공합니다. &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nn-dxgi-idxgiswapchain)&quot;&gt;^2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;스왑체인은 IDXGIFactory::CreateSwapChain &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory-createswapchain)&quot;&gt;^3&lt;/a&gt;메서드를 사용해서 생성합니다.&lt;/p&gt;
&lt;h2&gt;렌더 대상 뷰(Render Target View;렌더 타겟 뷰)&lt;/h2&gt;
&lt;p&gt;DirectX에서는 자원이 있을 때 텍스처 자원이 파이프라인의 단계에 직접 묶이진 않습니다.&lt;/p&gt;
&lt;p&gt;대신에 그 텍스처의 자원 뷰(Resource View)를 생성해서 사용합니다.&lt;/p&gt;
&lt;p&gt;그 자원 뷰가 있으므로 인해서 런타입과 드라이버가 유효성 점검과 매핑을 뷰 생성 시점에서 수행할 수 있기 때문에 묶는 시점에서
형식 점검이 최소화됩니다.&lt;/p&gt;
&lt;p&gt;자원 뷰를 사용할 때는 Direct3D에게 자원의 사용 방식(파이프라인의 어느 단계에 묶을건지)을 알려줘야하고, 생성 시점에서 무형식을 지정한 자원 형식의 구체적인 형식을 결정해야합니다.&lt;/p&gt;
&lt;p&gt;무형식 자원은 텍스처 원소를 한 파이프라인 단계에서는 부동소수점 값으로서 사용하고 다른 단계에서는 정수로서 사용가능함을
의미합니다.&lt;/p&gt;
&lt;h2&gt;깊이-스텐실(Depth-Stencil) 버퍼와 뷰 생성&lt;/h2&gt;
&lt;p&gt;깊이 버퍼는 각 픽셀의 깊이 정보를 담습니다.&lt;/p&gt;
&lt;p&gt;Direct3D는 이 깊이 버퍼를 사용해서 어떤 물체가 다른 물체 앞에 있는지 판별하기 위해 필요합니다.&lt;/p&gt;
&lt;p&gt;깊이 버퍼링 알고리즘은 렌더링되는 각 픽셀의 깊이 값을 계산해서 깊이 판정을 수행합니다.&lt;/p&gt;
&lt;p&gt;이 수행된 픽셀은 관찰자에게 가장 가까운 픽셀이 승자가 되어 후면 버퍼에 기록됩니다.&lt;/p&gt;
&lt;p&gt;깊이 버퍼같은 2차원 텍스처를 생성하기 위해선 D3D11_TEXTURE_DESC &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_texture2d_desc)&quot;&gt;^4&lt;/a&gt;구조체를 채우고 ID3D11Device::CreateTexture2D &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-createtexture2d)&quot;&gt;^5&lt;/a&gt;메서드를 호출해야합니다.&lt;/p&gt;
&lt;h2&gt;뷰들을 출력 병합기(Output Merger)에 묶고 뷰포트 설정&lt;/h2&gt;
&lt;p&gt;이제 뷰들을 다 생성했으니 자원들이 파이프라인의 렌더 대상과 깊이-스텐실 버퍼로 작용하도록 출력 병합기에 묶어야 합니다.&lt;/p&gt;
&lt;p&gt;이 작업은 디바이스 컨텍스트의 OMSetRenderTargets &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets)&quot;&gt;^6&lt;/a&gt;메서드를 사용해서 묶습니다.&lt;/p&gt;
&lt;p&gt;마지막으로 장면의 일부인 뷰포트를 디바이스 컨텍스트의 RSSetViewports &lt;a href=&quot;%5BMS_Learn_Docs%5D(https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-rssetviewports)&quot;&gt;^7&lt;/a&gt;메서드를 사용해서 Direct3D에게 알려줍니다.&lt;/p&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;DirectX 11을 이용한 3D 게임 프로그래밍 입문&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>DirectX의 XMVECTOR</title><link>https://blog.ushiohayase.com/posts/directx_1/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/directx_1/</guid><description>DirectX에서의 FXMVECTOR와 CXMVECTOR의 차이와 때에 따라 사용하는 법</description><pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;기본적으로 DirectX에서는 벡터를 다룰때 XMVECTOR라는 구조체를 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;개념 : XMVECTOR는 DirectXMath의 핵심 요소 중 하나로 SIMD(Single Instruction, Multi Data)를 위한 벡터 타입입니다.
XMVECTOR는 주로 4개의 32비트 부동소수점 값(float) 또는 32비트 정수 (int)를 저장하기 위해 설계되었습니다.&lt;/li&gt;
&lt;li&gt;성능 : XMVECTOR는 SIMD 명령어(x86기준 SSE/SSE2/AVX...etc, ARM기준 NEON)을 활용하여 벡터 및 행렬 연산을 한 명령어로 여러 데이터를 동시에 처리해 높은 성능을 얻을 수 있습니다.&lt;/li&gt;
&lt;li&gt;요구조건 : XMVECTOR는 16바이트 경계에 정렬(16-bytes aligned; 메모리 주소가 16의 배수)되어야 최적의 성능을 발휘합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 XMVECTOR가 각 아키텍처마다 어떻게 변환되는지 알아보고 각 대상의 차이점 및 공통점을 알아볼 예정입니다.&lt;/p&gt;
&lt;h2&gt;XMVECTOR&lt;/h2&gt;
&lt;p&gt;XMVECTOR는 SIMD 연산을 위한 기본적인 타입입니다.
이것 자체는 플랫폼 별로 있는 기본 SIMD 벡터 타입에 대한 &lt;code&gt;typedef&lt;/code&gt; 또는 &lt;code&gt;using&lt;/code&gt;을 이용한 별칭입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;XMVECTOR 자체의 정의&lt;/th&gt;
&lt;th&gt;기본 SIMD 벡터 타입&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;x86(32bit,SSE/SSE2 지원)&lt;/td&gt;
&lt;td&gt;__m128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x64&lt;/td&gt;
&lt;td&gt;__m128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM(32bit, NEON 지원)&lt;/td&gt;
&lt;td&gt;float32x4_t&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM(64bit)&lt;/td&gt;
&lt;td&gt;float32x4_t&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;XMVECTOR는 크게 FXMVECTOR &amp;amp; GXMVECTOR &amp;amp; HXMVECTOR와 CXMVECTOR로 플랫폼 별로 최적의 호출 규약에 맞추어 추상화될 수 있습니다.&lt;/p&gt;
&lt;h2&gt;FXMVECTOR &amp;amp; GXMVECTOR &amp;amp; HXMVECTOR와 CXMVECTOR&lt;/h2&gt;
&lt;p&gt;FXMVECTOR &amp;amp; GXMVECTOR &amp;amp; HXMVECTOR와 CXMVECTOR는 모두 XMVECTOR를 효율적으로 사용하기 위한 타입 별칭입니다.&lt;/p&gt;
&lt;p&gt;아래 표는 각각의 형태가 어떤 타입의 별칭인지가 나와있는 표입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;아키텍처&lt;/th&gt;
&lt;th&gt;FXMVECTOR 정의&lt;/th&gt;
&lt;th&gt;GXMVECTOR 정의&lt;/th&gt;
&lt;th&gt;HXMVECTOR 정의&lt;/th&gt;
&lt;th&gt;CXMVECTOR 정의&lt;/th&gt;
&lt;th&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;x86&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레지스터로 전달이 비효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x64&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;__vectorcall 사용시 HXMVECTOR로 전달하는 인자의 개수 확장 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NEON 레지스터 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM64&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;const XMVECTOR&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NEON 레지스터 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 표에서 &lt;code&gt;const XMVECTOR&lt;/code&gt;는 규약마다 다르지만 일반적으로는 레지스터로 로드하여 접근해 메모리 접근보다 빠르게 실행할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;x64 아키텍처에서 전달 방식&lt;/h3&gt;
&lt;p&gt;일반적으로 벡터를 이용하는 함수는 처음에 오는 3개의 인자를 FXMVECTOR의 형식으로 전달하라고 합니다.
정확히는 처음에 오는 XMVECTOR 형식의 인자 3개까지를 각각 FXMVECTOR -&amp;gt; GXMVECTOR -&amp;gt; HXMVECTOR 형식으로 전달해야 효율적입니다.
이것은 Microsoft DirectXMath 라이브러리의 권장사항인데 주로 Microsoft의 x64 호출 규약과 관련이 깊습니다.&lt;/p&gt;
&lt;p&gt;이 x64 호출 규약에는 &lt;code&gt;__fastcall&lt;/code&gt;과 &lt;code&gt;__vectorcall&lt;/code&gt;이 포함되는데 일반적으로는 &lt;code&gt;__fastcall&lt;/code&gt; 규약이 사용됩니다.
이 &lt;code&gt;__fastcall&lt;/code&gt; 규약은 첫 인자 3개까지를 FXMVECTOR 형태로 넘기라고하는데 그것은 XMM0-XMM3까지의 레지스터에 저장됩니다.&lt;/p&gt;
&lt;p&gt;하지만 프로그래머가 &lt;code&gt;__vectorcall&lt;/code&gt; 규약을 함수 이름 앞에 명시해서 바꿀 경우 벡터 인자를 최대 6개까지 레지스터로 전달할 수 있습니다. (XMM0-XMM5)&lt;/p&gt;
&lt;p&gt;이 규약들을 정할 땐 XMVECTOR 뿐만 아니라 다른 인자들도 레지스터를 차지한다는 점을 생각하며 잘 선택해야 합니다.&lt;/p&gt;
&lt;h2&gt;더 병렬 처리 성능을 높이는 법&lt;/h2&gt;
&lt;p&gt;여기서 더 성능을 내기위해 AVX 명령어 셋을 이용할 수 있습니다.
사용하는 방법은 컴파일러가 AVX 명령어를 사용하도록 인자를 줘서 컴파일 하면 됩니다. (MSVC 경우 /arch:AVX, /arch:AVX2, /arch:AVX512)&lt;/p&gt;
&lt;p&gt;이렇게 하면 컴파일러가 더 효율적일 수도 있는 명령어로 바꿔주어 더 성능이 향상될 수도 있습니다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;이렇게 XMVECTOR가 어떻게 나뉘는지 컴파일시 어떤 명령어와 규약이 어떤 형태로 작동하는지 살펴봤습니다.&lt;/p&gt;
&lt;p&gt;이러한 규약과 명령어 셋, 타입들을 프로그래머들은 잘 선택해 성능이 잘 나오도록 프로그래밍 해야할 것입니다.&lt;/p&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/dxmath/pg-xnamath-internals#parameter-passing&quot;&gt;MS_Learn_DirectXMath&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/cpp/cpp/vectorcall&quot;&gt;__vectorcall&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention&quot;&gt;x64_호출규약&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>DFS와 BFS</title><link>https://blog.ushiohayase.com/posts/bfs_dfs/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bfs_dfs/</guid><description>DFS와 BFS에 대해 알아보기</description><pubDate>Sun, 23 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;BFS&lt;/h1&gt;
&lt;h2&gt;BFS란?&lt;/h2&gt;
&lt;p&gt;BFS는 Breadth Fisrt Search의 약어로 단어 그대로 넓게 펴져서 탐색하는 것을 말합니다.
좀 더 자세하게 말하면 어떤 탐색해야 할 것을 그래프 자료구조 형태로 나타내었을 때 주변 노드부터
탐색하는 것을 BFS라 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./bfs.png&quot; alt=&quot;BFS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이 알고리즘은 한 노드에 연결되는 모든 노드를 순차적으로 탐색하기 때문에 가중치가 없는 그래프에서는
시작 노드로부터 끝 노드까지 최단 거리를 알아낼 수 있습니다.
추가적으로 DFS와는 다르게 순차적으로 탐색하기에 그래프의 길이가 무한해도 답을 찾을 수 있습니다.&lt;/p&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;p&gt;BFS는 배열을 사용할 수도 있고 큐를 사용할 수도 있지만 일반적으로 큐를 이용하여 구현하는 편입니다.&lt;/p&gt;
&lt;p&gt;아래는 일반적으로 큐를 사용하는 BFS 코드 템플릿입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;queue&amp;gt;

using namespace std;

void bfs(int startNode)
{
    queue&amp;lt;int&amp;gt; q;

    q.push(startNode);

    while(!q.empty())
    {
        const int node = q.front();
        q.pop();

        // 조건에 맞으면 리턴하는 코드

        /*
        * 조건에 따라
        * 큐에 노드를 추가하는 코드
        */
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;시간 복잡도와 공간 복잡도&lt;/h2&gt;
&lt;p&gt;BFS의 시간 복잡도는 최악의 경우, 모든 노드를 한번씩 탐색해야하기 때문에 $O(N)$ 입니다.&lt;/p&gt;
&lt;p&gt;공간복잡도의 경우 최악의 경우, 모든 노드를 큐에 저장해야 할 수 있기 때문에 $O(N)$ 의
공간 복잡도를 가집니다.&lt;/p&gt;
&lt;h1&gt;DFS&lt;/h1&gt;
&lt;h2&gt;DFS란?&lt;/h2&gt;
&lt;p&gt;DFS는 Depth First Search의 약자로서 단어 그대로 깊은 곳 먼저 탐색하는 알고리즘입니다.
DFS는 노드의 갈림길이 나왔을 때 한 노드를 쭉 따라가 그 경로에 가장 깊은 곳까지 들어갔다가 나오면서
탐색하는 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./dfs.png&quot; alt=&quot;DFS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;DFS는 현 경로상의 노드들만 기억하면 되므로 비교적 공간 복잡도가 작습니다.&lt;/p&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;p&gt;DFS는 스택과 재귀 함수로 구현할 수 있지만 일반적으로는 재귀 함수를 이용해 구현합니다.
구조상 스택 오버플로우가 쉽게 발생할 수 있기에 주의해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int visited[MAX];
vector&amp;lt;int&amp;gt; graph[]

void dfs(int current) {
    visited[current] = true;

    for(int next: graph[current]) {
        if(!visited[next]) {
            dfs(next);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;시간 복잡도와 공간 복잡도&lt;/h2&gt;
&lt;p&gt;DFS는 최악의 경우, 모든 노드를 한번씩 탐색해야 하기에 시간 복잡도는 $O(N)$ 입니다.&lt;/p&gt;
&lt;p&gt;공간 복잡도는 최악의 경우, 가장 깊은 곳까지의 길이기에 그래프의 길이를 d라 하면 $O(d)$ 입니다.&lt;/p&gt;
</content:encoded></item><item><title>백준 7576번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-7576/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-7576/</guid><description>백준 7576번 C++ 풀이</description><pubDate>Sat, 22 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 7576번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다.&lt;/p&gt;
&lt;p&gt;창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.&lt;/p&gt;
&lt;p&gt;토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.&lt;/p&gt;
&lt;p&gt;토마토가 하나 이상 있는 경우만 입력으로 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;여러분은 토마토가 모두 익을 때까지의 최소 날짜를 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;각 시작 지점에서 토마토가 안익은 곳까지의 거리를 구하는 문제로 환원&lt;/li&gt;
&lt;li&gt;숫자를 입력받고 2차원 배열에 입력받은 숫자가 1이면 0, 0이면 INTMAX, -1이면 -1을 저장한다.&lt;/li&gt;
&lt;li&gt;BFS를 돌린다. 이 때 큐를 2개를 두어 하나는 찾을 위치 큐, 하나는 현재 단계 큐를 두어 탐색한다.&lt;/li&gt;
&lt;li&gt;이제 2차원 배열에는 최소 거리만 저장되었기에 그 배열에서 최댓값을 찾고 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numeric&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;
int levels[1000][1000];
vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; startPoints;

int moveX[4]{1, -1, 0, 0};
int moveY[4]{0, 0, 1, -1};

void bfs(int startX, int startY)
{
    queue&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; q;
    queue&amp;lt;int&amp;gt; floor;
    int currentFloor = 0;

    q.emplace(startX, startY);
    floor.push(0);

    while (!q.empty())
    {
        const int x = q.front().first;
        const int y = q.front().second;
        currentFloor = floor.front();
        q.pop();
        floor.pop();

        for (int i = 0; i &amp;lt; 4; ++i)
        {
            const int newX = x + moveX[i];
            const int newY = y + moveY[i];

            if (newX &amp;gt;= 0 &amp;amp;&amp;amp; newX &amp;lt; M &amp;amp;&amp;amp; newY &amp;gt;= 0 &amp;amp;&amp;amp; newY &amp;lt; N &amp;amp;&amp;amp; levels[newY][newX] &amp;gt; 0 &amp;amp;&amp;amp;
                currentFloor + 1 &amp;lt; levels[newY][newX])
            {
                q.emplace(newX, newY);
                floor.emplace(currentFloor + 1);
                levels[newY][newX] = currentFloor + 1 &amp;lt; levels[newY][newX] ? currentFloor + 1 : levels[newY][newX];
            }
        }
    }
}

int main()
{
    cin &amp;gt;&amp;gt; M &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        for (int j = 0; j &amp;lt; M; ++j)
        {
            int x;
            cin &amp;gt;&amp;gt; x;

            if (x == 1)
            {
                startPoints.emplace_back(j, i);
                levels[i][j] = 0;
            }
            else if (x == 0)
                levels[i][j] = numeric_limits&amp;lt;int&amp;gt;::max();
            else if (x == -1)
            {
                levels[i][j] = -1;
            }
        }
    }

    int result = 0;
    for (const auto&amp;amp; [fst, snd] : startPoints)
    {
        bfs(fst, snd);
    }

    for (int i = 0; i &amp;lt; N; ++i)
        for (int j = 0; j &amp;lt; M; ++j)
        {
            int tmp = levels[i][j];
            result = tmp &amp;gt; result ? tmp : result;
            if (tmp == numeric_limits&amp;lt;int&amp;gt;::max())
            {
                cout &amp;lt;&amp;lt; -1;
                return 0;
            }
        }

    cout &amp;lt;&amp;lt; result;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution[결과]
시간 초과
:::&lt;/p&gt;
&lt;h2&gt;1차 시도 실패 원인 분석&lt;/h2&gt;
&lt;p&gt;bfs 함수 호출을 여러번하여 불필요한 탐색 횟수를 늘린 것 같았다.&lt;/p&gt;
&lt;h2&gt;2차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numeric&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;
int levels[1000][1000];
vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; startPoints;

int moveX[4]{1, -1, 0, 0};
int moveY[4]{0, 0, 1, -1};

void bfs()
{
    queue&amp;lt;pair&amp;lt;int, pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt; q;

    for (const auto&amp;amp; [startX, startY] : startPoints)
    {
        q.push({0, {startX, startY}});
    }

    while (!q.empty())
    {
        const int x = q.front().second.first;
        const int y = q.front().second.second;
        const int currentFloor = q.front().first;
        q.pop();

        for (int i = 0; i &amp;lt; 4; ++i)
        {
            const int newX = x + moveX[i];
            const int newY = y + moveY[i];

            if (newX &amp;gt;= 0 &amp;amp;&amp;amp; newX &amp;lt; M &amp;amp;&amp;amp; newY &amp;gt;= 0 &amp;amp;&amp;amp; newY &amp;lt; N &amp;amp;&amp;amp; levels[newY][newX] &amp;gt; 0 &amp;amp;&amp;amp;
                currentFloor + 1 &amp;lt; levels[newY][newX])
            {
                q.push({currentFloor + 1, {newX, newY}});

                levels[newY][newX] = currentFloor + 1;
            }
        }
    }
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    cin &amp;gt;&amp;gt; M &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        for (int j = 0; j &amp;lt; M; ++j)
        {
            int x;
            cin &amp;gt;&amp;gt; x;

            if (x == 1)
            {
                startPoints.emplace_back(j, i);
                levels[i][j] = 0;
            }
            else if (x == 0)
                levels[i][j] = numeric_limits&amp;lt;int&amp;gt;::max();
            else
            {
                levels[i][j] = -1;
            }
        }
    }

    int result = 0;

    bfs();

    for (int i = 0; i &amp;lt; N; ++i)
        for (int j = 0; j &amp;lt; M; ++j)
        {
            int tmp = levels[i][j];
            result = tmp &amp;gt; result ? tmp : result;
            if (tmp == numeric_limits&amp;lt;int&amp;gt;::max())
            {
                cout &amp;lt;&amp;lt; -1;
                return 0;
            }
        }

    cout &amp;lt;&amp;lt; result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;:::note[결과]
맞았습니다!!
:::&lt;/h2&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Gold/7576.%E2%80%85%ED%86%A0%EB%A7%88%ED%86%A0&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>프로시저와 시스템 콜</title><link>https://blog.ushiohayase.com/posts/exceptional_control_flow/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/exceptional_control_flow/</guid><description>ECF(Exceptional Control Flow; 예외적인 제어흐름)에 대해 알아보기</description><pubDate>Sat, 22 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;ECF&lt;/h1&gt;
&lt;h2&gt;ECF(Exceptional Control Flow;예외적인 처리 흐름)이란?&lt;/h2&gt;
&lt;p&gt;프로세서가 전원을 공급받아 작동을 시작하는 순간부터 종료될 때까지, 프로그램 카운터는 일련의 주소 값을 순차적으로 가리킵니다.
이러한 주소 값의 흐름, 즉 제어 흐름은 프로그램 내에서 명령어가 순차적으로 실행되는 기본적인 방식입니다.
점프, 호출, 반환과 같은 프로그래밍 명령어들은 프로그램 내부 상태 변화에 따라 제어 흐름을 변경하는 데 필수적인 메커니즘입니다.
이러한 명령어들은 프로그램 변수에 의해 표현되는 내부 프로그램 상태 변화에 반응하여 제어 흐름에 갑작스러운 변화를 일으킵니다.&lt;/p&gt;
&lt;p&gt;그러나 실제 컴퓨터 시스템은 프로그램 내부 변수에 의해 포착되지 않거나 프로그램 실행과 직접적인 관련이 없는 시스템 상태 변화에도 대응할 수 있어야 합니다.
예를 들어, 하드웨어 타이머는 주기적으로 작동하며 처리되어야 하고, 네트워크 어댑터로 패킷이 도착하면 메모리에 저장되어야 합니다.
프로그램은 디스크에서 데이터를 요청한 후 데이터 준비 완료 알림을 받을 때까지 대기해야 하며, 자식 프로세스를 생성한 부모 프로세스는 자식 프로세스가 종료될 때 알림을 받아야 합니다.
현대 시스템은 이러한 상황에 대응하기 위해 제어 흐름에 갑작스러운 변화를 일으키며, 이러한 변화를 일반적으로 예외적인 제어 흐름(Exceptional Control Flow, ECF)이라고 합니다.
ECF는 컴퓨터 시스템의 모든 수준에서 발생합니다.&lt;/p&gt;
&lt;p&gt;응용 프로그램 수준에서 프로세스는 다른 프로세스에 시그널을 보내 수신자의 시그널 처리기로 제어를 갑자기 전달할 수 있습니다.
ECF를 이해하는 것은 응용 프로그램이 운영 체제와 상호 작용하는 방식을 이해하는 데 도움이 됩니다.
응용 프로그램은 트랩 또는 시스템 호출이라는 형태의 ECF를 사용하여 운영 체제에 서비스를 요청합니다.
컴퓨터 시스템의 모든 수준에서 존재하는 다양한 형태의 ECF를 설명하며, 하드웨어와 운영 체제의 교차점에 있는 예외부터 시작하여 응용 프로그램에 운영 체제로의 진입점을 제공하는 예외인 시스템 호출까지 논의합니다.&lt;/p&gt;
&lt;h2&gt;예외 (Exceptions)&lt;/h2&gt;
&lt;p&gt;예외는 프로세서 상태의 변화(이벤트)에 대한 응답으로 응용 프로그램에서 예외 처리기로 갑작스럽게 제어가 전달되는 것을 의미합니다.
처리기가 처리를 완료한 후에는 중단된 프로그램으로 제어를 반환하거나 프로그램을 중단시킵니다.
시스템에서 발생할 수 있는 각 예외 유형에는 고유한 음이 아닌 정수 예외 번호가 할당됩니다.
이러한 번호 중 일부는 프로세서 설계자가 할당하고, 다른 번호는 운영 체제 커널(운영 체제의 메모리 상주 부분) 설계자가 할당합니다.
런타임 시(시스템이 일부 프로그램을 실행 중일 때), 프로세서는 이벤트가 발생했음을 감지하고 해당 예외 번호 k를 결정합니다.&lt;/p&gt;
&lt;p&gt;예외는 명령어 실행의 직접적인 결과인 동기적 예외와 현재 명령어 실행과 관련이 없는 비동기적 예외로 나눌 수 있습니다.
동기적 예외의 예로는 0으로 나누기, 유효하지 않은 메모리 접근, 산술 오버플로, 정의되지 않은 명령어, 시스템 호출 등이 있습니다.
비동기적 예외(인터럽트)의 예로는 하드웨어 타이머 만료, 네트워크 패킷 도착, 디스크 I/O 완료, 키보드 입력 등이 있습니다.&lt;/p&gt;
&lt;h3&gt;예외의 유형&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;인터럽트 (Interrupts):&lt;/strong&gt; 인터럽트는 프로세서 외부에서 발생하는 예상치 못한 이벤트입니다.
하드웨어 인터럽트는 키보드, 마우스, 디스크, 네트워크와 같은 주변 장치가 CPU의 주의를 요청하기 위해 생성합니다.
소프트웨어 인터럽트는 소프트웨어에 의해 발생하며, 종종 시스템 호출이나 트랩과 같이 의도적으로 발생합니다.
인터럽트가 발생하면 프로세서는 현재 상태를 저장하고 커널 모드로 전환하여 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)을 실행한 후 저장된 상태를 복원하여 중단된 프로그램을 다시 시작합니다.
인터럽트는 우선순위에 따라 마스크(블록) 가능하거나 불가능할 수 있습니다.
인터럽트는 운영 체제가 하드웨어와 상호 작용하고 비동기적 이벤트를 관리하는 주요 방법입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;트랩 (Traps):&lt;/strong&gt; 트랩은 명령어 실행의 결과로 발생하는 의도적인 예외입니다.
트랩의 가장 중요한 용도는 사용자 프로그램과 커널 사이에 시스템 호출이라고 하는 프로시저와 유사한 인터페이스를 제공하는 것입니다.
syscall이나 int와 같은 특수 명령어를 실행하면 커널의 예외 처리기로 트랩이 발생합니다.
트랩 처리기는 트랩 명령어 바로 다음 명령어로 제어를 반환합니다.
트랩은 권한 있는 작업을 위해 사용자 모드에서 커널 모드로 전환하는 것을 용이하게 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;폴트 (Faults):&lt;/strong&gt; 폴트는 처리기가 수정할 수 있는 오류로 인해 발생하는 예외로, 프로그램을 다시 시작할 수 있게 합니다.
가장 일반적인 폴트 유형 중 하나는 페이지 폴트입니다.
페이지 폴트는 프로그램이 현재 물리적 메모리(RAM)에 없는 가상 메모리 페이지에 접근하려고 할 때 발생합니다.
메모리 관리 장치(Memory Management Unit, MMU)가 폴트를 감지하면 0, 운영 체제는 보조 저장 장치에서 해당 페이지를 찾아 RAM에 로드하고 페이지 테이블을 업데이트하여 처리합니다.
페이지 폴트는 메모리에 있지만 MMU에 로드된 것으로 표시되지 않은 마이너 폴트(소프트 폴트)와 메모리에 없는 메이저 폴트(하드 폴트)로 분류할 수 있습니다.
유효하지 않은 페이지 폴트는 가상 주소 공간 외부의 주소에 접근하려고 할 때 발생합니다.
다른 폴트의 예로는 세그먼트 없음, 유효하지 않은 TSS(Task State Segment) 등이 있습니다.
폴트 처리기는 일반적으로 폴트를 발생시킨 명령어로 제어를 반환합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;어보트 (Aborts):&lt;/strong&gt; 어보트는 복구할 수 없는 심각한 오류로 인해 발생하는 예외입니다.
하드웨어 오류, 메모리 보호 위반, 치명적인 시스템 오류 등이 그 예입니다.
어보트는 일반적으로 프로세스의 종료를 초래합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예외 처리 메커니즘&lt;/h3&gt;
&lt;p&gt;운영 체제가 시작될 때 예외 테이블이라는 점프 테이블이 할당되고 초기화됩니다.
시스템의 각 가능한 예외 유형에는 고유한 음이 아닌 정수 예외 번호가 할당됩니다.
예외 번호는 예외 테이블의 인덱스로 사용됩니다.
예외 테이블의 시작 주소는 예외 테이블 베이스 레지스터라는 특수 CPU 레지스터에 저장됩니다. 예외가 발생하면 프로세서는 예외 번호를 사용하여 테이블에서 해당 예외 처리기의 주소를 찾습니다.&lt;/p&gt;
&lt;p&gt;예외 처리기는 예외가 발생했을 때 실행되는 커널 수준의 루틴입니다.
처리기는 예외의 원인을 파악하고 5, 페이지 폴트 처리, 시그널 전달, 프로세스 종료 등 예외 유형에 따라 적절한 조치를 취합니다.
폴트의 경우 처리기는 오류를 수정하고 실행을 재개하려고 시도할 수 있습니다.
다른 예외의 경우 처리기는 프로그램을 종료하거나 다른 시스템 수준의 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;예외가 발생하면 프로세서는 중단된 프로그램의 현재 상태(CPU 레지스터, 프로그램 카운터 등)를 저장합니다.
컨텍스트는 일반적으로 커널 스택에 저장됩니다.
예외 처리기가 완료되면 저장된 컨텍스트가 복원되어 중단된 프로그램이 실행을 재개할 수 있습니다(적절한 경우).
예외 유형에 따라 반환 주소는 현재 명령어나 다음 명령어를 가리킬 수 있습니다.&lt;/p&gt;
&lt;h2&gt;프로세스 (Processes)&lt;/h2&gt;
&lt;p&gt;프로세스는 실행 중인 프로그램의 인스턴스입니다.
각 프로세스는 고유한 주소 공간(가상 메모리), 코드, 데이터, 스택 및 힙을 가진 활성 엔티티이며, 운영 체제에 의해 관리됩니다.&lt;/p&gt;
&lt;p&gt;프로세스는 생성, 준비, 실행, 대기, 종료의 생명 주기를 거칩니다.
새로운 프로세스는 생성되어 보조 기억 장치에 로드된 후, 준비 상태로 전환되어 주 메모리에 로드되고 CPU 할당을 기다립니다.
실행 상태에서 프로세스는 CPU에 의해 현재 실행 중입니다.
단일 CPU 코어에서는 한 번에 하나의 프로세스만 실행 상태에 있을 수 있습니다.
대기/차단 상태에서 프로세스는 I/O 완료 또는 리소스 가용성과 같은 이벤트가 발생하기를 기다립니다.
프로세스 실행이 완료되면 종료 상태로 전환됩니다.
메모리가 부족할 때 프로세스가 보조 기억 장치로 스왑 아웃되는 경우 일시 중단 준비 및 일시 중단 대기와 같은 선택적 상태가 일부 시스템에 포함될 수 있습니다.&lt;/p&gt;
&lt;p&gt;운영 체제는 프로세스 제어 블록(Process Control Block, PCB)이라는 데이터 구조에 각 프로세스에 대한 정보를 유지합니다.
PCB에는 프로세스 ID(PID), 현재 프로세스 상태, 다음에 실행할 명령어의 주소를 나타내는 프로그램 카운터(PC), CPU 레지스터 내용, 메모리 관리 정보(예: 페이지 테이블 베이스 주소), 회계 정보(예: 사용된 CPU 시간), I/O 상태 정보(예: 열린 파일)와 같은 필수 정보가 포함되어 있습니다.
PCB를 통해 OS는 스케줄링, 컨텍스트 스위칭 및 리소스 할당을 포함하여 프로세스를 관리하고 제어할 수 있습니다.&lt;/p&gt;
&lt;p&gt;컨텍스트 스위칭은 실행 중인 한 프로세스의 컨텍스트(상태)를 저장하고 다른 프로세스의 컨텍스트를 복원하여 새 프로세스가 CPU에서 실행될 수 있도록 하는 프로세스입니다.
이는 멀티태스킹에 필수적이며, 여러 프로세스가 단일 CPU를 공유할 수 있도록 합니다.
컨텍스트 스위칭은 시간 공유 시스템에서 시간 할당량 만료, I/O 요청으로 인한 프로세스 차단 또는 리소스 대기, 인터럽트 처리(하드웨어 또는 소프트웨어), 우선순위가 높은 프로세스가 실행 준비 상태가 됨(선점), 시스템 호출(사용자 모드에서 커널 모드로 전환), 프로세스 종료 등의 원인으로 발생합니다.
컨텍스트 스위칭 단계에는 현재 프로세스의 상태를 PCB에 저장하고(CPU 레지스터, PC, 메모리 맵, 스택 포인터), 다음에 실행할 프로세스를 선택하고(스케줄링 알고리즘 사용), 새 프로세스의 저장된 상태를 PCB에서 복원하는 작업이 포함됩니다.
컨텍스트 스위칭은 CPU가 실제 응용 프로그램 코드를 실행하지 않고 상태를 저장하고 복원하는 작업을 수행하므로 오버헤드를 발생시킵니다.
잦은 컨텍스트 스위칭은 시스템 성능 저하로 이어질 수 있으며, 과도한 컨텍스트 스위칭은 시스템이 유용한 작업 실행보다 스위칭에 더 많은 시간을 소비하는 스래싱을 초래할 수 있습니다.
컨텍스트 스위치 시간은 하드웨어 지원(예: 여러 레지스터 세트)에 따라 크게 달라집니다.&lt;/p&gt;
&lt;h2&gt;시그널 (Signals)&lt;/h2&gt;
&lt;p&gt;시그널은 실행 중인 프로그램에 특정 동작(종료 또는 오류 처리 등)을 유발하기 위해 전송되는 표준화된 메시지입니다.
이는 제한적인 형태의 프로세스 간 통신(Inter-Process Communication, IPC)입니다.
시그널은 특정 이벤트 또는 조건을 나타내기 위해 운영 체제, 다른 프로세스 또는 프로세스 자체에서 생성될 수 있는 비동기적 알림입니다.
시그널의 일반적인 용도는 프로세스 중단, 일시 중단, 종료 또는 강제 종료입니다.&lt;/p&gt;
&lt;p&gt;각 시그널에는 번호(일반적으로 1에서 1 사이)와 기호 이름(예: SIGINT, SIGTERM, SIGSEGV)이 할당됩니다.
몇 가지 일반적인 시그널과 그 의미는 다음과 같습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;시그널&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;기본 동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SIGHUP&lt;/td&gt;
&lt;td&gt;끊김 (제어 터미널 닫힘)&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGINT&lt;/td&gt;
&lt;td&gt;인터럽트 (Ctrl+C)&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGQUIT&lt;/td&gt;
&lt;td&gt;종료 (Ctrl+\)&lt;/td&gt;
&lt;td&gt;종료 (코어 덤프)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGILL&lt;/td&gt;
&lt;td&gt;불법 명령어&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGSEGV&lt;/td&gt;
&lt;td&gt;세그멘테이션 오류 (유효하지 않은 메모리 접근)&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGTERM&lt;/td&gt;
&lt;td&gt;종료 (소프트웨어 종료 시그널)&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGCHLD&lt;/td&gt;
&lt;td&gt;자식 프로세스 상태 변경&lt;/td&gt;
&lt;td&gt;무시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGALRM&lt;/td&gt;
&lt;td&gt;alarm()으로부터의 알람&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGKILL&lt;/td&gt;
&lt;td&gt;강제 종료 (잡거나 무시할 수 없음)&lt;/td&gt;
&lt;td&gt;종료 (즉시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGSTOP&lt;/td&gt;
&lt;td&gt;정지 (잡거나 무시할 수 없음)&lt;/td&gt;
&lt;td&gt;정지 (즉시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGUSR1&lt;/td&gt;
&lt;td&gt;사용자 정의 시그널 1&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGUSR2&lt;/td&gt;
&lt;td&gt;사용자 정의 시그널 2&lt;/td&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;프로세스는 특정 시그널을 수신했을 때 실행할 시그널 처리기 함수를 등록할 수 있습니다.
시그널 처리기는 signal() 또는 sigaction() 시스템 호출을 사용하여 설치할 수 있습니다.
sigaction()은 더 고급 제어를 위해 일반적으로 선호됩니다.
시그널이 전달되면 OS는 프로세스의 일반적인 실행을 중단하고 등록된 처리기를 호출합니다.
처리기가 등록되지 않은 경우 시그널의 기본 동작이 수행됩니다.
프로세스는 또한 SIG_IGN을 사용하여 시그널을 무시하거나 SIG_DFL을 사용하여 기본 동작으로 되돌릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;시그널 처리기를 구현할 때 몇 가지 중요한 고려 사항이 있습니다.
시그널 처리기는 언제든지 프로세스를 중단할 수 있으므로, 재진입 가능한 함수(함수 호출이 완료되기 전에 다시 호출되어도 안전한 함수)만 호출하여 경합 조건 및 정의되지 않은 동작을 피해야 합니다.
특정 함수 집합만 시그널 처리기 내에서 안전하게 호출할 수 있도록 보장됩니다(비동기 시그널 안전 함수).
이러한 함수는 일반적으로 간단하며 중단된 코드에서 수정 중일 수 있는 전역 상태에 의존하지 않습니다.printf() 및 malloc()과 같은 함수는 일반적으로 비동기 시그널 안전 함수가 아닙니다.
시그널 처리기 내에서 전역 변수에 대한 접근은 신중하게 관리해야 합니다.
처리기와 메인 프로그램 간에 공유되는 변수에는 volatile sig_atomic_t를 사용하여 원자성을 보장하고 컴파일러 최적화로 인한 문제를 방지하는 것이 좋습니다.
시그널 처리기가 실행되는 동안 해당 시그널을 트리거한 시그널은 일반적으로 재귀 호출을 방지하기 위해 차단됩니다.
적절한 처리를 위해 다른 시그널도 차단해야 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;각 프로세스에는 현재 전달이 차단된 시그널을 결정하는 시그널 마스크가 있습니다.
시그널은 생성될 수 있지만(프로세스에 전송됨) 현재 시그널 마스크에 의해 차단된 경우 보류 상태로 유지됩니다.
커널은 프로세스가 사용자 모드로 복귀하려고 할 때 보류 중인 시그널을 확인합니다.
차단되지 않은 보류 중인 시그널이 있으면 이 시점에서 전달됩니다.
사용자 모드 처리기가 있는 프로세스에 시그널이 전달되면 해당 유형의 시그널은 처리기가 실행되는 동안 자동으로 차단됩니다.
시스템 호출은 시그널에 의해 중단될 수 있습니다.
일부 시스템 호출은 시그널 처리기가 반환된 후 자동으로 다시 시작되는 반면(sigaction에서 SA_RESTART 플래그가 설정된 경우), 다른 시스템 호출은 오류(예: EINTR)를 반환합니다.&lt;/p&gt;
&lt;h2&gt;비지역 점프 (Non-local Jumps): setjmp와 longjmp&lt;/h2&gt;
&lt;p&gt;비지역 점프는 일반적인 함수 호출 및 반환 메커니즘을 우회하여 두 개의 분리된 함수 간의 코드 실행 점프입니다.
이는 setjmp() 및 longjmp() 함수를 사용하여 수행됩니다.&lt;/p&gt;
&lt;p&gt;비지역 점프의 일반적인 사용 사례는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;깊이 중첩된 함수에서 전체 호출 스택을 언와인딩하지 않고 오류 처리 루틴으로 직접 점프하여 오류를 처리하는 경우.&lt;/li&gt;
&lt;li&gt;이는 일반적인 오류 반환 값이 충분하지 않거나 전파하기 번거로운 상황에서 유용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;C에는 기본 제공 예외 처리가 없으므로 C에서 try/catch와 유사한 구조를 만드는 경우.&lt;/li&gt;
&lt;li&gt;단일 프로세스 내에서 다른 실행 컨텍스트 간을 전환하여 코루틴을 구현하는 경우(그러나 이는 종종 스택 조작을 위한 플랫폼별 트릭이 필요함).&lt;/li&gt;
&lt;li&gt;시그널 처리기에서 프로그램 실행의 특정 지점으로 반환하는 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;setjmp(jmp_buf env) 함수는 현재 실행 컨텍스트(프로그램 카운터, 스택 포인터 및 레지스터 값 포함)를 env가 가리키는 jmp_buf 구조체에 저장합니다.
setjmp()가 처음 호출되면 컨텍스트가 저장되었음을 나타내는 0을 반환합니다.
jmp_buf는 저장된 상태를 보유하는 불투명 데이터 유형(일반적으로 배열 또는 구조체)입니다.
정의되지 않은 동작을 피하기 위해 setjmp()는 특정 컨텍스트(예: if 또는 while 문의 전체 제어 표현식 또는 독립 실행형 표현식)에서 호출해야 합니다.&lt;/p&gt;
&lt;p&gt;longjmp(jmp_buf env, int val) 함수는 setjmp() 호출에 의해 이전에 env에 저장된 실행 컨텍스트를 복원합니다.
실행은 해당 setjmp() 호출이 val에 지정된 값으로 반환된 것처럼 재개됩니다.&lt;/p&gt;
&lt;p&gt;setjmp()와 longjmp()를 사용할 때는 몇 가지 주의 사항과 잠재적인 문제점이 있습니다.
setjmp() 호출과 longjmp() 호출 사이에 획득한 리소스(malloc으로 할당된 메모리, 열린 파일 등)는 C에 스택 언와인딩 중에 호출되는 소멸자 개념이 없기 때문에 자동으로 해제되지 않습니다.
따라서 수동 리소스 관리가 중요합니다.
setjmp()를 호출한 함수에서 자동(지역, 비정적, 비외부) 변수의 값은 longjmp() 후 예상대로 보존되지 않을 수 있습니다.
값을 복원하려면 setjmp()와 longjmp() 호출 사이에 수정된 경우 이러한 변수를 volatile로 선언해야 합니다.
setjmp()와 longjmp() 사이의 호출 스택에 가변 길이 배열(Variable Length Array, VLA)을 사용하면 메모리 누수 및 정의되지 않은 동작이 발생할 수 있습니다.
이러한 컨텍스트에서는 VLA를 피해야 합니다.
시그널 마스크에 대한 setjmp()와 longjmp()의 동작은 기본 표준에 엄격하게 정의되어 있지 않습니다.&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Randel E Bryant, David R O&apos;Hallaron의 컴퓨터 시스템&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>링커의 작동구조</title><link>https://blog.ushiohayase.com/posts/linker_process/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/linker_process/</guid><description>링커의 작동구조와 관련 내용 알아보기</description><pubDate>Mon, 17 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;링킹이란&lt;/h1&gt;
&lt;p&gt;C++에서 실행 가능한 프로그램을 만들기 위해서는 일반적으로 다음과 같은 과정을 거친다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;전처리기 - 전처리 구문을 처리한다.&lt;/li&gt;
&lt;li&gt;컴파일러 - 전처리된 소스 코드를 어셈블리어로 변환한다.&lt;/li&gt;
&lt;li&gt;어셈블러 - 어셈블리어를 기계어로 바꾸어 재배치 가능한 바이너리 목적파일로 변환한다.&lt;/li&gt;
&lt;li&gt;링커 - 필요한 시스템 목적파일들과 함께 실행 가능한 목적파일을 생성한다. (Windows에서는 exe)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이와 같은 과정에서 링커들은 재배치 가능한 목적 파일을 입력으로 받아
실행 가능한 목적 파일을 생성하는데 이것을 링킹이라 합니다.&lt;br /&gt;
입력인 재배치 가능한 목적파일들은 여러 섹션으로 나위어 있습니다.
링커는 실행 가능한 목적 파일, 실행 파일을 만들기 위해 두 가지 주요한 작업을 수행해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;심볼 해석(symbol resolution) - 목적 파일들이 정의하고 참조한 심볼을 정확하게 하나의 심볼로 연결합니다.&lt;/li&gt;
&lt;li&gt;재배치(resolution) - 하나의 심볼로 정의되면 그 심볼로 가는 모든 참조를 수정해서 한 메모리 위치로 특정짓습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 역할을 할 수 있는 목적파일(오브젝트 파일)은 재배치 가능하고(Relocatable), 내부에 코드(.text),
데이터(.rdata, .data) 섹션과 함께 심볼 테이블과 재배치 엔트리를 포함합니다.&lt;/p&gt;
&lt;p&gt;목적파일은 형식에 따라 분류되며 OS마다 각자가 선택한 형식을 사용합니다.
현대의 x86-64 리눅스와 유닉스 시스템은 ELF(Executable and Linkable Format), Mac OS-X는 Mach-O 포맷,
윈도우는 PE(Portable Executable) 포맷을 사용합니다.&lt;/p&gt;
&lt;h1&gt;윈도우 PE&lt;/h1&gt;
&lt;h2&gt;링킹 과정&lt;/h2&gt;
&lt;p&gt;윈도우 PE에서 링킹은 여러 오브젝트 파일과 정적 라이브러리(.lib 확장자 파일)를 하나의 실행 파일로 결합합니다.
링커는 각 모듈의 &lt;em&gt;심볼 테이블&lt;/em&gt;을 참고해, 함수와 전역 변수 등의 참조를 올바른 정의와 연결합니다.
이 과정에서 재배치 정보가 사용되어 코드 내 주소들은 최종 메모리 배치에 맞게 수정됩니다.
마지막으로 이 링킹을 다 하고나면 남는 결과물은 DOS 헤더, PE 헤더, 섹션 테이블 및 각 섹션이 포함된 Windows PE 형식의 실행파일(.exe 확장자 파일)입니다.&lt;/p&gt;
&lt;h2&gt;심볼 테이블&lt;/h2&gt;
&lt;p&gt;먼저 심볼이란 간단히 말해 함수나 변수, 객체 등을 식별할 수 있는 식별자입니다.
각 심볼은 해당 코드, 데이터의 주소(또는 오프셋), 크기, 그리고 기타 속성 등의 정보를 포함합니다.&lt;/p&gt;
&lt;p&gt;심볼 테이블은 오브젝트 파일 내부에 존재하며, 모듈 내의 정의된 모든 심볼과 다른 모듈에 정의된 것을 참조하는
외부 심볼 정보를 포함합니다.&lt;/p&gt;
&lt;h3&gt;심볼 해석 과정&lt;/h3&gt;
&lt;p&gt;링킹이 시작되면 링커는 각 오브젝트 파일의 심볼 테이블을 읽습니다.
링커는 모듈 간의 &lt;em&gt;참조된 심볼&lt;/em&gt;에 대해 동일한 이름의 &lt;em&gt;정의된 심볼&lt;/em&gt;을 찾습니다.
만약 어떤 심볼이 한 모듈에서 정의되고, 다른 모듈에서는 단순 참조만 있다면, 이 참조는 정의된 심볼의
주소로 해결됩니다.&lt;/p&gt;
&lt;p&gt;이 때 한 모듈에서 강한 정의(함수들과 초기화된 전역변수)를 제공하고 다른 모듈에서 약한 정의(비초기화된 전역변수)를 제공한 경우 강한 정의가 우선됩니다.
만약 서로 다른 두 모듈에서 강한 전역 심볼을 정의한 경우 링커는 일반적으로 충돌 오류를 발생시키지만 인라인 함수나, 템플릿같은
특정 상황에서는 링커가 하나의 정의를 선택하도록 규칙(우선 순위, COMDAT 섹션 [^1] 사용등)을 적용합니다.&lt;/p&gt;
&lt;p&gt;[^1]: COMDAT 섹션은 섹션 헤더 특성 필드에 플래그가 설정되어있고 해당 플래그에 따라 어떻게 선택할지 판단합니다.&lt;/p&gt;
&lt;h2&gt;정적 라이브러리 링킹 및 참조 해석&lt;/h2&gt;
&lt;p&gt;정적 라이브러리란 여러 오브젝트 파일을 아카이브한 파일 형식으로 링커는 실행 파일 내에 필요한 심볼이
참조될 때, 해당 심볼을 포함하는 오브젝트 파일만을 정적 라이브러리에서 추출해 삽입합니다.
오브젝트 파일 내 참조(외부 심볼)는 링커가 정적 라이브러리에서 해당 심볼을 가진 오브젝트 파일을 찾아 연결합니다.
이 때, 재배치 정보가 함께 적용되어, 라이브러리에서 가져온 코드의 주소가 실행 파일의 메모리 배치에 맞게 수정됩니다.&lt;/p&gt;
&lt;p&gt;링커는 Optional Header에 지정된 Preferred Image Base(예: EXE는 보통 0x400000)를 기준으로, 각 섹션의 &lt;strong&gt;최종 가상 주소(Virtual Address, VA)&lt;/strong&gt; 를 계산합니다.&lt;/p&gt;
&lt;p&gt;예를 들어, .text 섹션은 Image Base + 섹션 정렬에 따른 오프셋에 배치되고,
.data, .rdata 등 다른 섹션도 각각의 오프셋이 계산되어 배정됩니다.
이 때, 오브젝트 파일 내 상대 주소에 해당 섹션의 가상 주소를 더하여 절대 주소로 변환합니다.&lt;/p&gt;
&lt;h2&gt;재배치(Relocation)&lt;/h2&gt;
&lt;p&gt;오브젝트 파일은 일반적으로 메모리 주소가 절대 주소 [^2]가 아닌 상대 주소 [^3]로 작성됩니다.
재배치 엔트리(Relocation entries)는 코드 내 주소가 수정되어야 할 위치와 수정 방법을 명시합니다.&lt;/p&gt;
&lt;p&gt;링커는 모든 오브젝트 파일의 재배치 엔트리를 참고해, 각 심볼의 &lt;em&gt;가상 주소&lt;/em&gt;를 계산합니다.
계산된 주소로 각 참조 위치를 수정하고, 필요시 PE파일의 베이스 리로케이션(Base Relocation) 섹션에 해당 정보를 기록합니다.&lt;/p&gt;
&lt;p&gt;만약 실행 파일이 기본 주소대신 다른 주소에 로드된다면, OS 로더가 재배치 정보를 참고하여 매모리에 매핑된 주소들을
다시 조정합니다.&lt;/p&gt;
&lt;p&gt;[^2]: 메모리에서 특정 위치를 직접적으로 가르키는 고유 주소로 물리적 메모리의 실제 위치를 나타냅니다.
[^3]: 기준점을 기준으로 계산한 상대적인 주소로, 특정 기준점에서 얼마나 떨어져 있는지를 나타냅니다.&lt;/p&gt;
&lt;h2&gt;PE 파일 구조&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;DOS 헤더 및 DOS Stub:
&lt;ul&gt;
&lt;li&gt;초기 DOS 실행 코드와 “This program cannot be run in DOS mode.” 메시지를 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PE Signature:
&lt;ul&gt;
&lt;li&gt;&quot;PE\0\0&quot;라는 매직 넘버로 PE 파일임을 표시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;COFF File Header:
&lt;ul&gt;
&lt;li&gt;머신 유형, 오브젝트 파일 수, 타임스탬프, 심볼 테이블 정보 등 기본 정보를 담습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optional Header:
&lt;ul&gt;
&lt;li&gt;실행에 필요한 중요한 정보(예: 진입점 주소, 이미지 베이스, 섹션 정렬, 데이터 디렉토리 정보)를 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;섹션 테이블(Section Table):
&lt;ul&gt;
&lt;li&gt;각 섹션(.text, .data, .rdata, .rsrc 등)의 가상 주소, 파일 내 오프셋, 크기 등을 기록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;섹션 데이터:
&lt;ul&gt;
&lt;li&gt;실제 코드, 데이터, 리소스 등이 담긴 섹션들이 위치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 디렉토리:
&lt;ul&gt;
&lt;li&gt;Import Table, Export Table, 리로케이션 정보, 디버그 정보 등 추가 데이터에 대한 포인터들을 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;PE 파일 로딩 과정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;PE 헤더 파싱 및 메모리 매핑:
&lt;ul&gt;
&lt;li&gt;OS 로더는 PE 헤더를 읽어, 실행 파일의 섹션들을 적절한 메모리 영역(가상 메모리)에 매핑합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;재배치 적용:
&lt;ul&gt;
&lt;li&gt;지정된 베이스 주소로 매핑되지만, 만약 충돌이 발생하면 재배치 정보를 사용하여 코드와 데이터의 주소들을 수정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Import Table 처리:
&lt;ul&gt;
&lt;li&gt;실행 파일이 참조하는 DLL 목록과 각 DLL에서 필요한 함수의 주소 정보를 확인하여, 각 DLL을 메모리에 로드합니다.
Import Address Table(IAT)에 각 DLL의 함수 주소를 채워 넣습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실행 준비:
&lt;ul&gt;
&lt;li&gt;초기화 루틴 수행 및 진입점(Entry Point)으로 제어가 전달되어 실행이 시작됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;동적 링킹과 위치 독립 코드(PIC; Position Independent Code)&lt;/h1&gt;
&lt;p&gt;동적 링킹에 필요한 윈도우의 .dll 파일 역시 Windows PE 파일 형식이며 DLL 내부 Export Table에 내보낼 함수와 변수 목록이
기재되어 있습니다.&lt;/p&gt;
&lt;h2&gt;동적 링킹 과정&lt;/h2&gt;
&lt;p&gt;실행 파일은 내부 Import Table에 DLL의 함수와 데이터에 대한 참조를 기록합니다.
OS 로더는 실행시 Import Table 기반으로 DLL을 로드한 후, 각 함수에 실제 메모리 주소를
IAT(Import Address Table) [^4]에 채웁니다.&lt;/p&gt;
&lt;p&gt;응용 프로그램은 &lt;code&gt;LoadLibrary&lt;/code&gt; 함수를 통해 DLL을 로드하고, &lt;code&gt;GetProcAddress&lt;/code&gt;로 특정 심볼의 주소를 가져와
호출할 수 있습니다.&lt;/p&gt;
&lt;p&gt;[^4]: IAT는 실행 파일들이 사용하는 외부 함수의 주소를 저장하는 것입니다. PE 파일 형식의 일부로 디스크에선 의미가 없고
메모리에 로드되었을 때 실제 함수 주소가 채워지며 의미가 있게 됩니다.&lt;/p&gt;
&lt;h2&gt;위치 독립 코드&lt;/h2&gt;
&lt;p&gt;PIC는 코드가 고정된 주소에 의존하지 않고, 실행시 실제 메모리 배치에 맞게 상대 주소로 참조를 이루어지게 도와주는 것입니다.&lt;/p&gt;
&lt;p&gt;DLL은 기본적으로 PIC 형태로 작성되어, 만약 기본 주소 충돌이 발생하면 재배치를 최소화하거나, 재배치 엔트리의 수정 없이도 올바르게 동작할 수 있도록 설계됩니다.
보통, PIC를 위해 레지스터 기반의 상대 주소 계산 또는 글로벌 오프셋 테이블(GOT) 방식을 사용합니다.&lt;/p&gt;
&lt;p&gt;Windows에서는 DLL이 미리 지정된 Preferred Base Address를 가지지만, 충돌 시 OS 로더가 재배치를 수행합니다.
최신 컴파일러는 PIC를 최대한 활용하여 재배치 비용을 낮추도록 최적화합니다.&lt;/p&gt;
&lt;h3&gt;GOT&lt;/h3&gt;
&lt;p&gt;GOT는 PLT(Procedure Linkage Table) [^5]가 참조하는 테이블로 외부 프로시저들의 실제 메모리 주소를 저장합니다.&lt;br /&gt;
동적 링킹을 통해 프로그램 시작 시 주소가 결정됩니다.&lt;/p&gt;
&lt;p&gt;GOT를 이용하면 외부 라이브러리 함수 호출 시 PLT가 GOT를 참조하여 해당 함수의 실제 주소로 점프할 수 있습니다.&lt;/p&gt;
&lt;p&gt;프로그램을 처음 실행하면 GOT 엔트리가 초기화되어 있지 않고 함수가 처음 호출될 때 동적 링커가 실제 주소를 GOT에 기록합니다.&lt;/p&gt;
&lt;p&gt;[^5]: PLT는 외부 프로시저를 연결해주는 테이블로 다른 라이브러리에 있는 함수를 호출하게 해줍니다.
이것은 프로그램 텍스트 세션에 위치합니다.&lt;/p&gt;
&lt;h1&gt;어셈블리 단에서의 흐름&lt;/h1&gt;
&lt;p&gt;개발자는 어셈블리 언어(또는 고급 언어에서 생성된 어셈블리 코드)를 통해 함수, 변수 등을 정의하며, 각 항목에 대해 레이블을 사용합니다.
예를 들어, 함수 시작 부분에는 func_label: 같은 레이블이 기록되고, 이 레이블은 심볼 테이블에 정의됩니다.&lt;/p&gt;
&lt;p&gt;함수 호출 또는 데이터 접근 시, 어셈블리 명령어 내에 심볼(레이블) 이름이 사용됩니다.
만약 호출 대상이 다른 모듈에 있다면, 해당 명령어는 외부 심볼로 기록되고, 재배치 엔트리에 포함됩니다.
링커는 이 정보를 기반으로 실제 메모리 주소로 치환합니다.&lt;/p&gt;
&lt;p&gt;최종적으로 어셈블리 코드로 작성된 명령어들은 .text 섹션에,
초기화된 데이터는 .data, 읽기 전용 데이터는 .rdata 섹션에 위치하게 됩니다.
각 섹션은 PE 파일의 섹션 테이블에 의해 메모리 내의 위치와 크기가 결정되며, OS 로더에 의해 적절히 매핑됩니다.
재배치 엔트리들은 해당 섹션 내에서 수정되어야 할 주소들을 지정하고, 링커 및 로더가 이를 참조하여 주소를 올바르게 수정합니다.&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/debug/pe-format&quot;&gt;MS_PE_설명서&lt;/a&gt;&lt;br /&gt;
Randel E Bryant, David R O&apos;Hallaron의 컴퓨터 시스템&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>프로그램 최적화 방법 소개</title><link>https://blog.ushiohayase.com/posts/program_optimizing_technique/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/program_optimizing_technique/</guid><description>프로그램을 컴파일러 친화적으로 더 빠르게 동작하도록 최적화하는 방법</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;프로그래머가 최적화를 알아야 하는 이유&lt;/h1&gt;
&lt;p&gt;현대의 컴파일러는 과거보다 훨씬 더 빠르게 프로그램을 작동하도록 최적화를 시킵니다.
심지어 초보 프로그래머가 최대한 최적화한 코드보다 컴파일러가 &lt;em&gt;-O1&lt;/em&gt;정도의 수준으로 최적화한 코드가 더 빠르게 실행될 수도 있습니다.
하지만 컴파일러가 프로그램을 최적화하는데는 한계가 있습니다.
컴파일러가 과도하게 최적화를 하려다가 언어의 표준이 보장하는 동작을 보장하지 못할 경우가 생길 수 있기에 실제로는 코드 논리 구조와 상관없는 최적화지만 최적화하진 못하는 경우가 생길 수 있기 때문입니다.&lt;/p&gt;
&lt;h1&gt;최적화 정도 측정 단위&lt;/h1&gt;
&lt;p&gt;코드의 속도를 측정하는 단위는 여러가지가 있겠지만 여기서는 CPE(Cycle Per Element)라는 단위를 사용할 것 입니다.
CPE는 CPU가 동작하는 클럭이 한 원소를 처리하는데 얼마나 걸리는지를 나타낸 지표이다.
예를 들어 루프에서 한 변수가 다음 루프까지 처리되는 사이클이 5번이면 CPE는 5.0인 것이다.&lt;/p&gt;
&lt;h1&gt;최적화 방법&lt;/h1&gt;
&lt;h2&gt;루프 비효율성 제거&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 데이터 타입을 data_t라고 가정
void func1(data_t* src, data_t* dest)
{
	long i;

	*dest = INITIAL_VALUE; // 초기값 설정

	for (i = 0; i &amp;lt; data_length(src); i++)
	{
		data_t val;
		get_data_from_container(src, i, &amp;amp;val); // 컨테이너로부터 데이터 가져오기
		*dest = *dest + val;
	}
}

void func2(data_t* src, data_t* dest)
{
	long i;
	long length = data_length(src);

	*dest = INITIAL_VALUE; // 초기값 설정

	for (i = 0; i &amp;lt; length; i++)
	{
		data_t val;
		get_data_from_container(src, i, &amp;amp;val); 
		*dest = *dest + val;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드의 두 함수의 차이는 반복문의 검사를 할 때 쓸 변수를 반복문 내에서 호출하느냐, 아니면 그 전에 초기화하느냐의 차이만 존재합니다.
저와 같은 초보 프로그래머는 두 함수의 속도가 얼마나 차이나느냐고 물을 수 있습니다.
하지만 만약 &lt;code&gt;data_length&lt;/code&gt;함수의 시간 복잡도가 상수가 아니라면 이야기가 달라집니다.
대표적으로 문자열과 같은 것의 길이 검사는 보통 $O(n)$의 시간 복잡도가 걸립니다.
즉, 그렇다면 위 &lt;code&gt;func1&lt;/code&gt;과 &lt;code&gt;func2&lt;/code&gt;의 시간복잡도는 $O(n^2)$과 $O(n)$으로 시간복잡도가 매우 크게 차이나게 됩니다.
이런 경우, 컴파일러가 최적화를 해야하느냐 할 수도 있지만 GCC 같은 경우는 반복문 안에서 src가 변할 수도 있기에 최적화를 수행하지 않습니다.
그렇기에, 이런 점을 프로그래머가 잘 캐치하여 최적화하여야 합니다.&lt;/p&gt;
&lt;h2&gt;프로시저 호출 줄이기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;void get_data_from_container(data_t* src, int i, data_t* dest)
{
	if (i &amp;lt; 0 || i &amp;gt; src-&amp;gt;len()) // data_t가 len이란 메소드를 가지고 있다 가정
		throw std::out_of_range();
	*dest = src[i];
}

void func2(data_t* src, data_t* dest)
{
	long i;
	long length = data_length(src);

	*dest = INITIAL_VALUE; // 초기값 설정

	for (i = 0; i &amp;lt; length; i++)
	{
		data_t val;
		get_data_from_container(src, i, &amp;amp;val); 
		*dest = *dest + val;
	}
}

void func3(data_t* src, data_t* dest)
{
	long i;
	long length = data_length(src);
	
	for (i = 0; i &amp;lt; length; i++)
		*dest = *dest + data[i];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 두 함수는 함수 호출에 따른 오버헤드도 있겠지만 주요 차이점은 인덱스 검사를 하는가 여부입니다.
이 코드는 최신 프로세서들에서는 분기예측같은 기술을 통해 성능저하가 거의 없지만
다른 코드에서 프로시저를 호출하면 호출할수록 속도가 느려집니다.&lt;/p&gt;
&lt;h2&gt;불필요한 메모리 참조의 제거&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;void func3(data_t* src, data_t* dest)
{
	long i;
	long length = data_length(src);
	
	for (i = 0; i &amp;lt; length; i++)
		*dest = *dest + data[i];
}

void func4(data_t* src, data_t* dest)
{
	long i;
	long length = data_length(src);
	long sum = *dest;
	
	for (i = 0; i &amp;lt; length; i++)
		sum = sum + data[i];

	*dest = sum;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서의 두 함수의 차이는 반복문 안에서 메모리 참조 여부입니다.
메모리 참조는 레지스터를 바로 사용못하고 메모리에 접근을 해야하기에 사이클을 많이 잡아먹는 작업입니다.
그래서 위의 &lt;code&gt;func3&lt;/code&gt;을 &lt;code&gt;func4&lt;/code&gt;처럼 바꾸면 기계어로 변환되었을때 &lt;code&gt;sum&lt;/code&gt;이 레지스터에서 작업되어 사이클을 적게 먹게 됩니다.&lt;/p&gt;
&lt;h2&gt;루프 풀기&lt;/h2&gt;
&lt;p&gt;루프풀기는 반복문에서 매 반복실행마다 계산되는 원소의 수를 증가시켜 루프의 실행횟수를 줄이는 방법입니다.&lt;br /&gt;
예시로 $2*1$ 루프풀기는 매 반복실행마다 계산되는 원소를 2배로 늘려 루프의 실행횟수를 절반으로 낮추고 루프 인덱스 계산과
조건부 분기같은 연산의 횟수를 줄입니다.&lt;/p&gt;
&lt;h2&gt;병렬성 높이기&lt;/h2&gt;
&lt;p&gt;병렬성 높이기의 대표적인 예시로는 1-100까지 모두 더할때 1-50과 51-100까지 더하는 변수를 따로 만들어 CPU 파이프라인을 더 효과적으로 쓸 수 있게합니다.&lt;/p&gt;
&lt;h2&gt;재결합 변환&lt;/h2&gt;
&lt;p&gt;재결합 변환은 &lt;code&gt;acc = (acc + data[i]) + data[i+1];&lt;/code&gt;같은 코드를 &lt;code&gt;acc = acc + (data[i] + data[i+1]);&lt;/code&gt;같은 코드로 변환시켜 데이터 의존성을 줄이는 방법입니다.&lt;/p&gt;
&lt;h2&gt;SIMD 활용&lt;/h2&gt;
&lt;p&gt;최신 프로세서들은 SIMD(Single Instruction, Multi Data)라고 하는 SSE(Streaming SIMD Extension), AVX(Advanced Vector Extension)같은 연산을 지원합니다. 이 명령어들의 관련된 레지스터는 아키텍처마다 다르지만 32비트 수 8개 또는 64비트 수 4개를 보관할 수 있으며 이 레지스터에 한번의 인스트럭션을 사용해 모두 연산할 수 있습니다. 컴파일러가 AVX를 잘 활용할 수 있게하고 int형(32비트) 사용시 루프풀기의 인자를 8로 하여 코드를 변환하면 매우 빠른 성능을 보이게됩니다.&lt;/p&gt;
&lt;h2&gt;레지스터 넘기기&lt;/h2&gt;
&lt;p&gt;레지스터 넘기기는 지금까지와는 다르게 사용되면 성능이 떨어집니다. 이것은 CPU에 있는 레지스터 수를 넘어가는 루프 변수들이 있다면 초과한 변수들을 스택에 할당하고 다음 사이클에 메모리에 접근하여 계산하기 때문입니다.&lt;/p&gt;
&lt;h2&gt;분기예측&lt;/h2&gt;
&lt;p&gt;현대 CPU들은 분기예측이라는 기술로 조건문에서 결과를 미리 계산하고 틀릴 경우에만 속도가 느려지는 그런 아키텍처를 가지고 있습니다.
따라서 CPU가 예측하기 쉬운 분기들은 속도를 거의 떨어뜨리지 않기에 코드 논리상 1번의 다른 결과만 나오는 범위 검사같은 코드는 성능을 떨어뜨리지 않습니다.
하지만 분기예측은 규칙적인 패턴에 대해서만 안정적이기에 예측하기 힘든 로직이 되면 좋지 않은 성능을 보이게 됩니다.
이를 최대한 해결하기위해 코드 스타일을 명령형 스타일(프로그램의 상태를 선택적으로 갱신하기 위해 조건문 사용)에서 기능적 스타일(값을 계산하고 이 값들로 프로그램의 상태를 갱신하기 위해 조건부 연산 사용)로 바꾸어 조건부 제어이동대신 조건부 데이터 이동을 사용하도록 할 수 있습니다. 코드예시로는 &lt;code&gt;if (a[i] &amp;gt; b[i]) {long t = a[i]; a[i] = b[i]; b[i] = t;}&lt;/code&gt; 같은 코드를 &lt;code&gt;long min = a[i] &amp;lt; b[i] ? a[i] : b[i]; long max = a[i] &amp;lt; b[i] ? b[i] : a[i]; a[i] = min; b[i]= max;&lt;/code&gt;와 같은 코드로 변환할 수 있습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;참조 : Randel E Bryant, David R O&apos;Hallaron의 컴퓨터 시스템&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>백준 18111번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-18111/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-18111/</guid><description>백준 18111번 C++ 풀이</description><pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 18111문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;팀 레드시프트는 대회 준비를 하다가 지루해져서 샌드박스 게임인 ‘마인크래프트’를 켰다. 마인크래프트는 1 × 1 × 1(세로, 가로, 높이) 크기의 블록들로 이루어진 3차원 세계에서 자유롭게 땅을 파거나 집을 지을 수 있는 게임이다.&lt;/p&gt;
&lt;p&gt;목재를 충분히 모은 lvalue는 집을 짓기로 하였다. 하지만 고르지 않은 땅에는 집을 지을 수 없기 때문에 땅의 높이를 모두 동일하게 만드는 ‘땅 고르기’ 작업을 해야 한다.&lt;/p&gt;
&lt;p&gt;?lvalue는 세로 &lt;em&gt;N&lt;/em&gt;, 가로 &lt;em&gt;M&lt;/em&gt; 크기의 집터를 골랐다. 집터 맨 왼쪽 위의 좌표는 (0, 0)이다. 우리의 목적은 이 집터 내의 땅의 높이를 일정하게 바꾸는 것이다. 우리는 다음과 같은 두 종류의 작업을 할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;좌표 (&lt;em&gt;i&lt;/em&gt;, &lt;em&gt;j&lt;/em&gt;)의 가장 위에 있는 블록을 제거하여 인벤토리에 넣는다.&lt;/li&gt;
&lt;li&gt;인벤토리에서 블록 하나를 꺼내어 좌표 (&lt;em&gt;i&lt;/em&gt;, &lt;em&gt;j&lt;/em&gt;)의 가장 위에 있는 블록 위에 놓는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1번 작업은 2초가 걸리며, 2번 작업은 1초가 걸린다. 밤에는 무서운 몬스터들이 나오기 때문에 최대한 빨리 땅 고르기 작업을 마쳐야 한다. ‘땅 고르기’ 작업에 걸리는 최소 시간과 그 경우 땅의 높이를 출력하시오.&lt;/p&gt;
&lt;p&gt;단, 집터 아래에 동굴 등 빈 공간은 존재하지 않으며, 집터 바깥에서 블록을 가져올 수 없다. 또한, 작업을 시작할 때 인벤토리에는 _B_개의 블록이 들어 있다. 땅의 높이는 256블록을 초과할 수 없으며, 음수가 될 수 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 &lt;em&gt;N, M&lt;/em&gt;, _B_가 주어진다. (1 ≤ &lt;em&gt;M&lt;/em&gt;, &lt;em&gt;N&lt;/em&gt; ≤ 500, 0 ≤ &lt;em&gt;B&lt;/em&gt; ≤ 6.4 × 107)&lt;/p&gt;
&lt;p&gt;둘째 줄부터 _N_개의 줄에 각각 _M_개의 정수로 땅의 높이가 주어진다. (&lt;em&gt;i&lt;/em&gt; + 2)번째 줄의 (&lt;em&gt;j&lt;/em&gt; + 1)번째 수는 좌표 (&lt;em&gt;i&lt;/em&gt;, &lt;em&gt;j&lt;/em&gt;)에서의 땅의 높이를 나타낸다. 땅의 높이는 256보다 작거나 같은 자연수 또는 0이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 땅을 고르는 데 걸리는 시간과 땅의 높이를 출력하시오. 답이 여러 개 있다면 그중에서 땅의 높이가 가장 높은 것을 출력하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
3 4 99&lt;br /&gt;
0 0 0 0&lt;br /&gt;
0 0 0 0&lt;br /&gt;
0 0 0 1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
2 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 2&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력 : &lt;/code&gt;&lt;br /&gt;
3 4 1&lt;br /&gt;
64 64 64 64&lt;br /&gt;
64 64 64 64&lt;br /&gt;
64 64 64 63&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력 : &lt;/code&gt;&lt;br /&gt;
1 64&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 출력 3&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력 : &lt;/code&gt;&lt;br /&gt;
3 4 0&lt;br /&gt;
64 64 64 64&lt;br /&gt;
64 64 64 64&lt;br /&gt;
64 64 64 63&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력 : &lt;/code&gt;&lt;br /&gt;
22 63&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./18111_first_try.drawio.png&quot; alt=&quot;아이디어&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

int N, B, M;
int* data;
int** arr;

int BinarySearch(const int lowHeight, const int highHeight, const int blocks)
{
    const int midHeight = (lowHeight + highHeight) / 2;

    int needBlocks = 0;
    int higherHeightTime = highHeight &amp;gt; 256 ? 500 * 500 * 256 : 0;
    int lowerHeightTime = lowHeight &amp;lt; 0 ? 500 * 500 * 256 : 0;

    for (int i = 0; i &amp;lt; N; ++i)
        for (int j = 0; j &amp;lt; M; ++j)
        {
            const int higherDeltaBlock = highHeight - arr[i][j];
            const int deltaBlock = midHeight - arr[i][j];
            const int lowerDeltaBlock = lowHeight - arr[i][j];

            if (higherDeltaBlock &amp;gt; 0)
                higherHeightTime += higherDeltaBlock;
            else if (higherHeightTime &amp;lt; 0)
                higherHeightTime -= 2 * higherDeltaBlock;

            if (lowerDeltaBlock &amp;gt; 0)
                lowerHeightTime += lowerDeltaBlock;
            else if (lowerDeltaBlock &amp;lt; 0)
                lowerHeightTime -= 2 * lowerDeltaBlock;

            needBlocks += deltaBlock;
        }

    if (highHeight - lowHeight &amp;lt;= 1) return higherHeightTime &amp;gt; lowerHeightTime ? lowerHeightTime : higherHeightTime;

    if (needBlocks &amp;gt; blocks)
        return BinarySearch(lowHeight, midHeight - 1, blocks);
    else if (needBlocks == blocks)
        return BinarySearch(lowHeight, midHeight, blocks);
    else
    {
        if (higherHeightTime &amp;gt; lowerHeightTime)
            return BinarySearch(lowHeight, midHeight, blocks);
        else
            return BinarySearch(midHeight, highHeight, blocks);
    }
}

int main()
{
    std::cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M &amp;gt;&amp;gt; B;

    data = new int[N * M];
    arr = new int*[N];

    for (int i = 0; i &amp;lt; N; ++i)
    {
        arr[i] = data + (i * M);
        for (int j = 0; j &amp;lt; M; ++j)
        {
            std::cin &amp;gt;&amp;gt; data[i * M + j];
            arr[i][j] = data[i * M + j];
        }
    }

    int resultHeight = BinarySearch(0, 256, B);

    int time = 0;

    for (int i = 0; i &amp;lt; N; ++i)
        for (int j = 0; j &amp;lt; M; ++j)
        {
            int deltaHeight = resultHeight - arr[i][j];
            if (deltaHeight &amp;gt; 0)
                time += deltaHeight;
            else if (deltaHeight &amp;lt; 0)
                time -= 2 * deltaHeight;
        }

    std::cout &amp;lt;&amp;lt; time &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; resultHeight;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution[결과]&lt;br /&gt;
틀렸습니다&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;1차 시도 실패 원인 분석&lt;/h2&gt;
&lt;p&gt;이분탐색으로 하려면 시간과 필요한 블럭, 2가지를 고려해야 해서 알고리즘을 더 복잡하게 만들어야 할 것 같아 브루스포트에 걸리는 시간을 계산해보니 $O(500&lt;em&gt;500&lt;/em&gt;256)$의 시간 복잡도가 들것 같아 브루스 포트로 시도해보기로했다.&lt;/p&gt;
&lt;h2&gt;2차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;  
#include &amp;lt;numeric&amp;gt;  
  
int N, B, M;  
int* data;  
int** arr;  
  
int main()  
{  
    std::cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M &amp;gt;&amp;gt; B;  
  
    data = new int[N * M];  
    arr = new int*[N];  
  
    for (int i = 0; i &amp;lt; N; ++i)  
    {  
        arr[i] = data + (i * M);  
        for (int j = 0; j &amp;lt; M; ++j)  
        {  
            std::cin &amp;gt;&amp;gt; data[i * M + j];  
            arr[i][j] = data[i * M + j];  
        }  
    }  
  
    int finalTime = std::numeric_limits&amp;lt;int&amp;gt;::max();  
    int finalHeight = 0;  
  
    for (int height = 0; height &amp;lt;= 256; ++height)  
    {  
        int time = 0;  
        int needBlocks = 0;  
        for (int i = 0; i &amp;lt; N; ++i)  
            for (int j = 0; j &amp;lt; M; ++j)  
            {  
                const int deltaBlock = arr[i][j] - height;  
                if (deltaBlock &amp;gt; 0)  
                    time += 2 * deltaBlock;  
                else if (deltaBlock &amp;lt; 0)  
                    time -= deltaBlock;  
                needBlocks -= deltaBlock;  
            }  
        if (time &amp;lt;= finalTime &amp;amp;&amp;amp; needBlocks &amp;lt;= B)  
        {  
            finalTime = time;  
            finalHeight = height;  
        }  
    }  
  
    std::cout &amp;lt;&amp;lt; finalTime &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; finalHeight;  
  
    delete[] arr;  
    delete[] data;  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/18111.%E2%80%85%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 2775번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-2775/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-2775/</guid><description>백준 2775번 C++ 풀이</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 2775번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;평소 반상회에 참석하는 것을 좋아하는 주희는 이번 기회에 부녀회장이 되고 싶어 각 층의 사람들을 불러 모아 반상회를 주최하려고 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;이 아파트에 거주를 하려면 조건이 있는데, “a층의 b호에 살려면 자신의 아래(a-1)층의 1호부터 b호까지 사람들의 수의 합만큼 사람들을 데려와 살아야 한다” 는 계약 조항을 꼭 지키고 들어와야 한다.&lt;/p&gt;
&lt;p&gt;아파트에 비어있는 집은 없고 모든 거주민들이 이 계약 조건을 지키고 왔다고 가정했을 때, 주어지는 양의 정수 k와 n에 대해 k층에 n호에는 몇 명이 살고 있는지 출력하라. 단, 아파트에는 0층부터 있고 각층에는 1호부터 있으며, 0층의 i호에는 i명이 산다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫 번째 줄에 Test case의 수 T가 주어진다. 그리고 각각의 케이스마다 입력으로 첫 번째 줄에 정수 k, 두 번째 줄에 정수 n이 주어진다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;각각의 Test case에 대해서 해당 집에 거주민 수를 출력하라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;제한&lt;/h1&gt;
&lt;p&gt;$$1 ≤ k, n ≤ 14$$&lt;/p&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
2&lt;br /&gt;
1&lt;br /&gt;
3&lt;br /&gt;
2&lt;br /&gt;
3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
6&lt;br /&gt;
10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;TMP(템플릿 메타 프로그래밍)을 이용해 컴파일 시간에 모든 가능한 값을 다 계산하고 꺼내 쓴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;array&amp;gt;
#include &amp;lt;iostream&amp;gt;

int K, N, T;

template &amp;lt;int N, int F&amp;gt;
constexpr int FiboFunc();

constexpr int returnNumber(int i) { return i; }

template &amp;lt;int T, int F&amp;gt;
class Fibo
{
   public:
    static constexpr int value = Fibo&amp;lt;T - 1, F&amp;gt;::value + Fibo&amp;lt;T, F - 1&amp;gt;::value;
};

template &amp;lt;&amp;gt;
class Fibo&amp;lt;1, 0&amp;gt;
{
   public:
    static constexpr int value = 1;
};

template &amp;lt;int N&amp;gt;
class Fibo&amp;lt;N, 0&amp;gt;
{
   public:
    static constexpr int value = N;
};

template &amp;lt;int F&amp;gt;
class Fibo&amp;lt;1, F&amp;gt;
{
   public:
    static constexpr int value = 1;
};

template &amp;lt;int n, int f&amp;gt;
inline constexpr int FiboFunc()
{
    int result = 0;

    if (f == 0 &amp;amp;&amp;amp; n == 1)
    {
        return Fibo&amp;lt;1, 0&amp;gt;::value;
    }
    else if (f == 0 &amp;amp;&amp;amp; n == 2)
    {
        return Fibo&amp;lt;2, 0&amp;gt;::value;
    }
    else if (f == 0)
        result = n;
    else

        for (int i = n; i &amp;lt;= 1; --i)
        {
            result += Fibo&amp;lt;n - i, f - 1&amp;gt;::value;
        }

    Fibo&amp;lt;f, n&amp;gt;::value = result;

    return result;
}

int main()
{
    std::cin &amp;gt;&amp;gt; T;

    for (int i = 0; i &amp;lt; T; ++i)
    {
        std::cin &amp;gt;&amp;gt; K &amp;gt;&amp;gt; N;
        switch (N)
        {
            case 1:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;1, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 2:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;2, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 3:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;3, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 4:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;4, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 5:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;5, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 6:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;6, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 7:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;7, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 8:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;8, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 9:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;9, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 10:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;10, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 11:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;11, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 12:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;12, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 13:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;13, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
                break;
            case 14:
                switch (K)
                {
                    case 1:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 1&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 2:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 2&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 3:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 3&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 4:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 4&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 5:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 5&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 6:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 6&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 7:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 7&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 8:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 8&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 9:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 9&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 10:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 10&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 11:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 11&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 12:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 12&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 13:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 13&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                    case 14:
                        std::cout &amp;lt;&amp;lt; Fibo&amp;lt;14, 14&amp;gt;::value &amp;lt;&amp;lt; &quot;\n&quot;;
                        break;
                }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Bronze/2775.%E2%80%85%EB%B6%80%EB%85%80%ED%9A%8C%EC%9E%A5%EC%9D%B4%E2%80%85%EB%90%A0%ED%85%8C%EC%95%BC&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 18870번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-18870/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-18870/</guid><description>백준 18870번 C++ 풀이</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 18870번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;수직선 위에 N개의 좌표 X1, X2, ..., XN이 있다. 이 좌표에 좌표 압축을 적용하려고 한다.&lt;/p&gt;
&lt;p&gt;Xi를 좌표 압축한 결과 X&apos;i의 값은 Xi &amp;gt; Xj를 만족하는 서로 다른 좌표 Xj의 개수와 같아야 한다.&lt;/p&gt;
&lt;p&gt;X1, X2, ..., XN에 좌표 압축을 적용한 결과 X&apos;1, X&apos;2, ..., X&apos;N를 출력해보자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N이 주어진다.&lt;/p&gt;
&lt;p&gt;둘째 줄에는 공백 한 칸으로 구분된 X1, X2, ..., XN이 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 X&apos;1, X&apos;2, ..., X&apos;N을 공백 한 칸으로 구분해서 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;제한&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;$1 ≤ N ≤ 1,000,000$
$-109 ≤ Xi ≤ 109$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5&lt;br /&gt;
2 4 -10 4 -9&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
2 3 0 3 1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 2&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력 : &lt;/code&gt;&lt;br /&gt;
6&lt;br /&gt;
1000 999 1000 999 1000 999&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력 : &lt;/code&gt;&lt;br /&gt;
1 0 1 0 1 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;해시 테이블에 존재하는 숫자를 저장하고 배열에 들어온 숫자를 저장합니다.&lt;/li&gt;
&lt;li&gt;배열을 복사하고 복사한 배열을 오름차순으로 정렬합니다.&lt;/li&gt;
&lt;li&gt;이진탐색으로 정렬된 배열을 탐색한뒤 인덱스를 각 요소마다 출력합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;set&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N;
vector&amp;lt;int&amp;gt; arr;
vector&amp;lt;int&amp;gt; sortedArr;
set&amp;lt;int&amp;gt; exist;

int binarySearch(int left, int right, int target)
{
    if (sortedArr[left] == target) return left;
    if (sortedArr[right] == target) return right;

    int mid = (left + right) / 2;

    if (sortedArr[mid] &amp;gt;= target) return binarySearch(left, mid, target);

    return binarySearch(mid + 1, right, target);
}

int main()
{
    cin &amp;gt;&amp;gt; N;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        if (!exist.contains(x))
        {
            sortedArr.push_back(x);

            exist.insert(x);
        }
        arr.push_back(x);
    }

    sort(sortedArr.begin(), sortedArr.end());

    for (int i = 0; i &amp;lt; arr.size(); ++i) cout &amp;lt;&amp;lt; binarySearch(0, sortedArr.size() - 1, arr[i]) &amp;lt;&amp;lt; &quot; &quot;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/18870.%E2%80%85%EC%A2%8C%ED%91%9C%E2%80%85%EC%95%95%EC%B6%95&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 21736번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-21736/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-21736/</guid><description>백준 21736번 C++ 풀이</description><pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 21736번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2020년에 입학한 헌내기 도연이가 있다. 도연이는 비대면 수업 때문에 학교에 가지 못해 학교에 아는 친구가 없었다. 드디어 대면 수업을 하게 된 도연이는 어서 캠퍼스 내의 사람들과 친해지고 싶다.&lt;/p&gt;
&lt;p&gt;도연이가 다니는 대학의 캠퍼스는
$N \times M$ 크기이며 캠퍼스에서 이동하는 방법은 벽이 아닌 상하좌우로 이동하는 것이다.
예를 들어, 도연이가 ($x$, $y$)에 있다면 이동할 수 있는 곳은 ($x+1$, $y$), ($x$, $y+1$), ($x-1$, $y$), ($x$, $y-1$)이다.&lt;br /&gt;
단, 캠퍼스의 밖으로 이동할 수는 없다.&lt;/p&gt;
&lt;p&gt;불쌍한 도연이를 위하여 캠퍼스에서 도연이가 만날 수 있는 사람의 수를 출력하는 프로그램을 작성해보자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에는 캠퍼스의 크기를 나타내는 두 정수
$N$  ( $1 \leq N \leq 600$ ),$M$ ($1 \leq M \leq 600$)이 주어진다.&lt;/p&gt;
&lt;p&gt;둘째 줄부터
$N$개의 줄에는 캠퍼스의 정보들이 주어진다. O는 빈 공간, X는 벽, I는 도연이, P는 사람이다. I가 한 번만 주어짐이 보장된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 도연이가 만날 수 있는 사람의 수를 출력한다. 단, 아무도 만나지 못한 경우 TT를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
3 5&lt;br /&gt;
OOOPO&lt;br /&gt;
OIOOX&lt;br /&gt;
OOOXP&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;
1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 2&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
3 3&lt;br /&gt;
IOX&lt;br /&gt;
OXP&lt;br /&gt;
XPP&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
TT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;DFS를 이용해 순차적으로 퍼져나가면서 탐색한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

int N, M;

char* data;
char** arr;
bool visit[600][600];
int dx[4]{1, -1, 0, 0};
int dy[4]{0, 0, 1, -1};

int dfs(int x, int y)
{
    visit[y][x] = true;
    int visitPeople = arr[y][x] == &apos;P&apos; ? 1 : 0;

    for (int i = 0; i &amp;lt; 4; ++i)
    {
        int newX = x + dx[i];
        int newY = y + dy[i];

        if (newX &amp;gt;= M || newX &amp;lt; 0 || newY &amp;gt;= N || newY &amp;lt; 0) continue;
        if (visit[newY][newX]) continue;
        if (arr[newY][newX] == &apos;X&apos;) continue;
        visitPeople += dfs(newX, newY);
    }

    return visitPeople;
}

int main()
{
    std::cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; M;

    int startX, startY;

    data = new char[N * M];
    arr = new char*[N];

    for (int i = 0; i &amp;lt; N; ++i)
    {
        arr[i] = data + (i * M);
        for (int j = 0; j &amp;lt; M; ++j)
        {
            arr[i][j] = data[i * M + j];
            char x;
            std::cin &amp;gt;&amp;gt; x;
            arr[i][j] = x;
            if (x == &apos;I&apos;)
            {
                startX = j;
                startY = i;
            }
        }
    }

    int result = dfs(startX, startY);

    if (result == 0)
        std::cout &amp;lt;&amp;lt; &quot;TT&quot;;
    else
        std::cout &amp;lt;&amp;lt; result;

    delete[] data;
    delete[] arr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/21736.%E2%80%85%ED%97%8C%EB%82%B4%EA%B8%B0%EB%8A%94%E2%80%85%EC%B9%9C%EA%B5%AC%EA%B0%80%E2%80%85%ED%95%84%EC%9A%94%ED%95%B4&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 1654번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-1654/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-1654/</guid><description>백준 1654번 C++ 풀이</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 1654번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.&lt;/p&gt;
&lt;p&gt;이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm는 버려야 한다. (이미 자른 랜선은 붙일 수 없다.)&lt;/p&gt;
&lt;p&gt;편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. N개보다 많이 만드는 것도 N개를 만드는 것에 포함된다. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 231-1보다 작거나 같은 자연수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N개를 만들 수 있는 랜선의 최대 길이를 센티미터 단위의 정수로 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
4 11&lt;br /&gt;
802&lt;br /&gt;
743&lt;br /&gt;
457&lt;br /&gt;
539&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
200&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;가장 큰 길이부터 하나하나 줄이며 나눠가며 가능한 길이 찾으면 출력&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

using namespace std;

int N, K;
int* lan;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;
    lan = new int[N];
    int maxValue = 0;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        cin &amp;gt;&amp;gt; lan[i];
        if (lan[i] &amp;gt; maxValue) maxValue = lan[i];
    }

    int cuttingLength = maxValue;

    while (true)
    {
        int numCuttingLan = 0;
        for (int i = 0; i &amp;lt; N; ++i) numCuttingLan += lan[i] / cuttingLength;

        if (numCuttingLan &amp;gt;= K)
        {
            cout &amp;lt;&amp;lt; cuttingLength;
            return 0;
        }
        cuttingLength--;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning[결과]
시간초과
:::&lt;/p&gt;
&lt;h2&gt;1차 시도 실패 원인&lt;/h2&gt;
&lt;p&gt;브루스포트로 시도했기에 $O(N^2)$의 시간복잡도를 가져 시간초과가 나왔습니다.&lt;br /&gt;
브루스포트를 이분탐색으로 바꾸어 2차 시도를 해보았습니다.&lt;/p&gt;
&lt;h2&gt;2차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numeric&amp;gt;

using namespace std;

unsigned long long N, K;
unsigned long long* lan;

unsigned long long func(unsigned long long left, unsigned long long right)
{
    unsigned long long mid = (left + right) / 2;

    unsigned long long numCuttingLan = 0;
    for (int i = 0; i &amp;lt; K; ++i) numCuttingLan += lan[i] / mid;

    if (right - left &amp;lt;= 1)
    {
        unsigned long long numCuttingLan = 0;
        for (int i = 0; i &amp;lt; K; ++i) numCuttingLan += lan[i] / (mid + 1);

        return numCuttingLan &amp;gt;= N ? right : left;
    }

    if (numCuttingLan &amp;gt;= N)
        return func(mid, right);
    else
        return func(left, mid - 1);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; K &amp;gt;&amp;gt; N;
    lan = new unsigned long long[K];
    unsigned long long sum = 0;

    for (int i = 0; i &amp;lt; K; ++i)
    {
        cin &amp;gt;&amp;gt; lan[i];
        sum += lan[i];
    }

    cout &amp;lt;&amp;lt; func(1, sum);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;h2&gt;2차 시도 Comment&lt;/h2&gt;
&lt;p&gt;테스트 케이스 중 랜선을 붙이는 것도 있는데 이해가 가지 않았습니다.&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/1654.%E2%80%85%EB%9E%9C%EC%84%A0%E2%80%85%EC%9E%90%EB%A5%B4%EA%B8%B0&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 2108번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-2108/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-2108/</guid><description>백준 2108번 C++ 풀이</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 2108번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;수를 처리하는 것은 통계학에서 상당히 중요한 일이다. 통계학에서 N개의 수를 대표하는 기본 통계값에는 다음과 같은 것들이 있다. 단, N은 홀수라고 가정하자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;산술평균 : N개의 수들의 합을 N으로 나눈 값&lt;/li&gt;
&lt;li&gt;중앙값 : N개의 수들을 증가하는 순서로 나열했을 경우 그 중앙에 위치하는 값&lt;/li&gt;
&lt;li&gt;최빈값 : N개의 수들 중 가장 많이 나타나는 값&lt;/li&gt;
&lt;li&gt;범위 : N개의 수들 중 최댓값과 최솟값의 차이&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;N개의 수가 주어졌을 때, 네 가지 기본 통계값을 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 수의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 단, N은 홀수이다. 그 다음 N개의 줄에는 정수들이 주어진다. 입력되는 정수의 절댓값은 4,000을 넘지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에는 산술평균을 출력한다. 소수점 이하 첫째 자리에서 반올림한 값을 출력한다.&lt;/p&gt;
&lt;p&gt;둘째 줄에는 중앙값을 출력한다.&lt;/p&gt;
&lt;p&gt;셋째 줄에는 최빈값을 출력한다. 여러 개 있을 때에는 최빈값 중 두 번째로 작은 값을 출력한다.&lt;/p&gt;
&lt;p&gt;넷째 줄에는 범위를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5&lt;br /&gt;
1&lt;br /&gt;
3&lt;br /&gt;
8&lt;br /&gt;
-2&lt;br /&gt;
2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
2&lt;br /&gt;
2&lt;br /&gt;
1&lt;br /&gt;
10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 2&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
1&lt;br /&gt;
4000&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
4000&lt;br /&gt;
4000&lt;br /&gt;
4000&lt;br /&gt;
0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 3&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5&lt;br /&gt;
-1&lt;br /&gt;
-2&lt;br /&gt;
-3&lt;br /&gt;
-1&lt;br /&gt;
-2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
-2&lt;br /&gt;
-2&lt;br /&gt;
-1&lt;br /&gt;
2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 4&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
3&lt;br /&gt;
0&lt;br /&gt;
0&lt;br /&gt;
-1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
0&lt;br /&gt;
0&lt;br /&gt;
0&lt;br /&gt;
1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;빈도를 저장할 배열 하나, 숫자를 저장할 배열 하나를 준비합니다.&lt;/li&gt;
&lt;li&gt;합을 저장할 변수, 최솟값, 최댓값을 저장할 변수를 만듭니다.&lt;/li&gt;
&lt;li&gt;들어오는 숫자를 합을 저장할 변수엔 더합니다.&lt;/li&gt;
&lt;li&gt;빈도 배열엔 들어오는 숫자를 인덱스로 1을 추가합니다.&lt;/li&gt;
&lt;li&gt;숫자 배열에 추가합니다.&lt;/li&gt;
&lt;li&gt;최소값, 최대값을 비교 및 갱신합니다.&lt;/li&gt;
&lt;li&gt;합 저장 변수를 N으로 나누고 반올림해줍니다.&lt;/li&gt;
&lt;li&gt;숫자 배열을 정렬하고 N/2번째 인덱스 값을 출력합니다.&lt;/li&gt;
&lt;li&gt;빈도수가 가장 많은 숫자를 찾고 빈도수가 가장 많이 있는 숫자 배열에 추가합니다.&lt;/li&gt;
&lt;li&gt;빈도수가 가장 많이 있는 숫자 배열의 사이즈가 2이상이면 정렬후 1번 인덱스 출력, 아니면 0번 인덱스를 출력합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, M;
vector&amp;lt;int&amp;gt; arr;
vector&amp;lt;int&amp;gt; freq(8e+3 + 1);

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N;

    double aver = 0;
    int minValue = 4000;
    int maxValue = -4000;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        aver += x;
        freq[x + 4000]++;
        arr.push_back(x);
        if (x &amp;gt; maxValue) maxValue = x;
        if (x &amp;lt; minValue) minValue = x;
    }

    int averageResult = round(aver / N);

    cout &amp;lt;&amp;lt; (averageResult == -0 ? 0 : averageResult) &amp;lt;&amp;lt; &quot;\n&quot;;

    sort(arr.begin(), arr.end());

    cout &amp;lt;&amp;lt; arr[N / 2] &amp;lt;&amp;lt; &quot;\n&quot;;
    vector&amp;lt;int&amp;gt; freqMaxList;
    int freqMax = 0;

    for (int i = 0; i &amp;lt;= 8e+3; ++i)
    {
        int x = freq[i];
        if (x &amp;gt; freqMax)
        {
            freqMax = x;
            freqMaxList.clear();
            freqMaxList.push_back(i - 4000);
        }
        else if (x == freqMax)
            freqMaxList.push_back(i - 4000);
    }

    if (freqMaxList.size() &amp;gt; 1)
    {
        sort(freqMaxList.begin(), freqMaxList.end());
        cout &amp;lt;&amp;lt; freqMaxList[1] &amp;lt;&amp;lt; &quot;\n&quot;;
    }
    else
        cout &amp;lt;&amp;lt; freqMaxList[0] &amp;lt;&amp;lt; &quot;\n&quot;;

    cout &amp;lt;&amp;lt; maxValue - minValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/2108.%E2%80%85%ED%86%B5%EA%B3%84%ED%95%99&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 10814번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-10814/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-10814/</guid><description>백준 10814번 C++ 풀이</description><pubDate>Sat, 08 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 10814번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;온라인 저지에 가입한 사람들의 나이와 이름이 가입한 순서대로 주어진다. 이때, 회원들을 나이가 증가하는 순으로, 나이가 같으면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 온라인 저지 회원의 수 N이 주어진다. (1 ≤ N ≤ 100,000)&lt;/p&gt;
&lt;p&gt;둘째 줄부터 N개의 줄에는 각 회원의 나이와 이름이 공백으로 구분되어 주어진다. 나이는 1보다 크거나 같으며, 200보다 작거나 같은 정수이고, 이름은 알파벳 대소문자로 이루어져 있고, 길이가 100보다 작거나 같은 문자열이다. 입력은 가입한 순서로 주어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄부터 총 N개의 줄에 걸쳐 온라인 저지 회원을 나이 순, 나이가 같으면 가입한 순으로 한 줄에 한 명씩 나이와 이름을 공백으로 구분해 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
3
21 Junkyu&lt;br /&gt;
21 Dohyun&lt;br /&gt;
20 Sunyoung&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
20 Sunyoung&lt;br /&gt;
21 Junkyu&lt;br /&gt;
21 Dohyun&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;힙 자료구조, C++ STL에서는 우선순위 큐를 이용한다.&lt;/li&gt;
&lt;li&gt;C++ STL의 priority_queue는 최대 힙(max heap)을 사용하기 때문에 최소 힙으로 만드는 `greater&amp;lt;&amp;gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;같은 나이는 넣은 순서대로 나와야하니 넣은 순서도 저장할 때 같이 저장한다.&lt;/li&gt;
&lt;li&gt;힙에 차례대로 넣고 차례대로 빼서 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int N, M;
priority_queue&amp;lt;pair&amp;lt;int, pair&amp;lt;int, string&amp;gt;&amp;gt;,
               vector&amp;lt;pair&amp;lt;int, pair&amp;lt;int, string&amp;gt;&amp;gt;&amp;gt;,
               greater&amp;lt;pair&amp;lt;int, pair&amp;lt;int, string&amp;gt;&amp;gt;&amp;gt;&amp;gt;
    container;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x;
        string y;
        cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
        container.push(make_pair(x, make_pair(i, y)));
    }
    for (int i = 0; i &amp;lt; N; ++i)
    {
        cout &amp;lt;&amp;lt; container.top().first &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; container.top().second.second
             &amp;lt;&amp;lt; &quot;\n&quot;;
        container.pop();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;h2&gt;1차 시도 Comment&lt;/h2&gt;
&lt;p&gt;먼저 넣은 순서대로 먼저 나오게 하기위해 priority_queue의 비교함수에 대해 추가적으로 공부했습니다.
priority_queue의 비교함수는 기존 값이 2번째 인자로 새로운 값이 첫번째 인자로 들어가고 true를 반환하면 1번째 인자의 우선순위가 낮고 반대는 두번째 인자의 우선순위가 낮도록 설계되어 있었습니다. 추가적으로 비교함수는 &lt;code&gt;operator()&lt;/code&gt;를 오버로딩해서 만들어야하고 a &amp;lt; a는 무조건 false를 반환, a &amp;lt; b, b &amp;lt; a가 같은 결과가 나오면 안되도록하는 조건이 있었습니다.&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/10814.%E2%80%85%EB%82%98%EC%9D%B4%EC%88%9C%E2%80%85%EC%A0%95%EB%A0%AC&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 10816번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-10816/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-10816/</guid><description>백준 10816번 C++ 풀이</description><pubDate>Sat, 08 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 10816번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 몇 개 가지고 있는지 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.&lt;/p&gt;
&lt;p&gt;셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이 수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 몇 개 가지고 있는지를 공백으로 구분해 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
10
6 3 2 10 10 10 -10 -10 7 3&lt;br /&gt;
8&lt;br /&gt;
10 9 -5 2 3 4 5 -10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
3 0 0 1 2 0 0 2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;STL의 map, 해시 테이블을 활용한다.&lt;/li&gt;
&lt;li&gt;새로운 요소가 들어오면 요소를 키로 추가하고 값을 1로 설정한다.&lt;/li&gt;
&lt;li&gt;기존 요소가 들어오면 값을 1 늘린다.&lt;/li&gt;
&lt;li&gt;숫자를 들어오는대로 해시 테이블에서 검색해 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int N, M;
map&amp;lt;int, int&amp;gt; container;

int main()
{
    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        if (container.find(x) == container.end())
            container[x] = 1;
        else
            container[x]++;
    }

    cin &amp;gt;&amp;gt; M;
    for (int i = 0; i &amp;lt; M; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        cout &amp;lt;&amp;lt; container[x] &amp;lt;&amp;lt; &quot; &quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
시간초과&lt;/p&gt;
&lt;h2&gt;1차 시도 실패 원인 분석&lt;/h2&gt;
&lt;p&gt;아무리 생각해도 내가 생각한 알고리즘 상에 문제는 없어 혹시 입출력 속도 문제일까 싶어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드를 넣어 동기화를 풀어봤다.&lt;/p&gt;
&lt;h2&gt;2차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int N, M;
map&amp;lt;int, int&amp;gt; container;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        if (container.find(x) == container.end())
            container[x] = 1;
        else
            container[x]++;
    }

    cin &amp;gt;&amp;gt; M;
    for (int i = 0; i &amp;lt; M; ++i)
    {
        int x;
        cin &amp;gt;&amp;gt; x;
        cout &amp;lt;&amp;lt; container[x] &amp;lt;&amp;lt; &quot; &quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/10816.%E2%80%85%EC%88%AB%EC%9E%90%E2%80%85%EC%B9%B4%EB%93%9C%E2%80%852&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 10828번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-10828/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-10828/</guid><description>백준 10828번 C++ 풀이</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 10828번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;정수를 저장하는 스택을 구현한 다음, 입력으로 주어지는 명령을 처리하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;p&gt;명령은 총 다섯 가지이다.&lt;/p&gt;
&lt;p&gt;push X: 정수 X를 스택에 넣는 연산이다.&lt;br /&gt;
pop: 스택에서 가장 위에 있는 정수를 빼고, 그 수를 출력한다. 만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력한다.&lt;br /&gt;
size: 스택에 들어있는 정수의 개수를 출력한다.&lt;br /&gt;
empty: 스택이 비어있으면 1, 아니면 0을 출력한다.&lt;br /&gt;
top: 스택의 가장 위에 있는 정수를 출력한다. 만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지 않은 명령이 주어지는 경우는 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;출력해야하는 명령이 주어질 때마다, 한 줄에 하나씩 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
14
push 1&lt;br /&gt;
push 2&lt;br /&gt;
top&lt;br /&gt;
size&lt;br /&gt;
empty&lt;br /&gt;
pop&lt;br /&gt;
pop&lt;br /&gt;
pop&lt;br /&gt;
size&lt;br /&gt;
empty&lt;br /&gt;
pop&lt;br /&gt;
push 3&lt;br /&gt;
empty&lt;br /&gt;
top&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
2&lt;br /&gt;
2&lt;br /&gt;
0&lt;br /&gt;
2&lt;br /&gt;
1&lt;br /&gt;
-1&lt;br /&gt;
0&lt;br /&gt;
1&lt;br /&gt;
-1&lt;br /&gt;
0&lt;br /&gt;
3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력 2&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력 :&lt;/code&gt;&lt;br /&gt;
7&lt;br /&gt;
pop&lt;br /&gt;
top&lt;br /&gt;
push 123&lt;br /&gt;
top&lt;br /&gt;
pop&lt;br /&gt;
top&lt;br /&gt;
pop&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력 : &lt;/code&gt;&lt;br /&gt;
-1&lt;br /&gt;
-1&lt;br /&gt;
123&lt;br /&gt;
123&lt;br /&gt;
-1&lt;br /&gt;
-1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;STL의 스택을 활용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;stack&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int N;
stack&amp;lt;int&amp;gt; q;

int main()
{
    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        string str;
        cin &amp;gt;&amp;gt; str;
        if (str == &quot;push&quot;)
        {
            int x;
            cin &amp;gt;&amp;gt; x;
            q.push(x);
        }
        else if (str == &quot;pop&quot;)
        {
            if (q.empty())
            {
                cout &amp;lt;&amp;lt; -1 &amp;lt;&amp;lt; &quot;\n&quot;;
                continue;
            }
            cout &amp;lt;&amp;lt; q.top() &amp;lt;&amp;lt; &quot;\n&quot;;
            q.pop();
        }
        else if (str == &quot;size&quot;)
        {
            cout &amp;lt;&amp;lt; q.size() &amp;lt;&amp;lt; &quot;\n&quot;;
        }
        else if (str == &quot;empty&quot;)
        {
            cout &amp;lt;&amp;lt; (q.empty() ? 1 : 0) &amp;lt;&amp;lt; &quot;\n&quot;;
        }
        else if (str == &quot;top&quot;)
        {
            if (q.empty())
            {
                cout &amp;lt;&amp;lt; -1 &amp;lt;&amp;lt; &quot;\n&quot;;
                continue;
            }
            cout &amp;lt;&amp;lt; q.top() &amp;lt;&amp;lt; &quot;\n&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/10828.%E2%80%85%EC%8A%A4%ED%83%9D&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 10989번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-10989/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-10989/</guid><description>백준 10989번 C++ 풀이</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 10989번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;N개의 수가 주어졌을 때, 이를 오름차순으로 정렬하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 수가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄부터 N개의 줄에 오름차순으로 정렬한 결과를 한 줄에 하나씩 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
10&lt;br /&gt;
5&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
1&lt;br /&gt;
4&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
5&lt;br /&gt;
1&lt;br /&gt;
7&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
1&lt;br /&gt;
1&lt;br /&gt;
2&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
3&lt;br /&gt;
4&lt;br /&gt;
5&lt;br /&gt;
5&lt;br /&gt;
7&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;숫자를 입력받습니다.&lt;/li&gt;
&lt;li&gt;sort를 이용해 정렬합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

int N;
int* arr;

int main()
{
    cin &amp;gt;&amp;gt; N;

    arr = new int[N];

    for (int i = 0; i &amp;lt; N; ++i) cin &amp;gt;&amp;gt; arr[i];

    sort(arr, arr + N);

    for (int i = 0; i &amp;lt; N; ++i) cout &amp;lt;&amp;lt; arr[i];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning[결과]
메모리초과
:::&lt;/p&gt;
&lt;h2&gt;실패 원인&lt;/h2&gt;
&lt;p&gt;애초에 문제 조건에 N이 10,000,000까지 되는데 각 숫자는 또 10,000까지 나올 수 있어 최소 short 자료형을 써야하기에 최대 용량은 20MB입니다. 하지만 정렬 문제라 순서가 상관없기에 10,000칸짜리 배열만들고 각 인덱스를 입력된 숫자로 취급하여 적용하면 풀 수 있습니다. 하지만 저는 답지를 보고 안 것이기에 문제를 풀지 않았습니다.&lt;/p&gt;
</content:encoded></item><item><title>백준 10845번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-10845/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-10845/</guid><description>백준 10845번 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 10845번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;정수를 저장하는 큐를 구현한 다음, 입력으로 주어지는 명령을 처리하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;p&gt;명령은 총 여섯 가지이다.&lt;/p&gt;
&lt;p&gt;push X: 정수 X를 큐에 넣는 연산이다.&lt;br /&gt;
pop: 큐에서 가장 앞에 있는 정수를 빼고, 그 수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.&lt;br /&gt;
size: 큐에 들어있는 정수의 개수를 출력한다.&lt;br /&gt;
empty: 큐가 비어있으면 1, 아니면 0을 출력한다.&lt;br /&gt;
front: 큐의 가장 앞에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.&lt;br /&gt;
back: 큐의 가장 뒤에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지 않은 명령이 주어지는 경우는 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;출력해야하는 명령이 주어질 때마다, 한 줄에 하나씩 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
15&lt;br /&gt;
push 1&lt;br /&gt;
push 2&lt;br /&gt;
front&lt;br /&gt;
back&lt;br /&gt;
size&lt;br /&gt;
empty&lt;br /&gt;
pop&lt;br /&gt;
pop&lt;br /&gt;
pop&lt;br /&gt;
size&lt;br /&gt;
empty&lt;br /&gt;
pop&lt;br /&gt;
push 3&lt;br /&gt;
empty&lt;br /&gt;
front&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;&lt;br /&gt;
1&lt;br /&gt;
2&lt;br /&gt;
2&lt;br /&gt;
0&lt;br /&gt;
1&lt;br /&gt;
2&lt;br /&gt;
-1&lt;br /&gt;
0&lt;br /&gt;
1&lt;br /&gt;
-1&lt;br /&gt;
0&lt;br /&gt;
3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;그대로 구현합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int N;
queue&amp;lt;int&amp;gt; q;

int main()
{
    cin &amp;gt;&amp;gt; N;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        string str;
        cin &amp;gt;&amp;gt; str;
        if (str == &quot;push&quot;)
        {
            int x;
            cin &amp;gt;&amp;gt; x;
            q.push(x);
        }
        else if (str == &quot;pop&quot;)
        {
            if (q.empty())
            {
                cout &amp;lt;&amp;lt; -1 &amp;lt;&amp;lt; &quot;\n&quot;;
                continue;
            }
            cout &amp;lt;&amp;lt; q.front() &amp;lt;&amp;lt; &quot;\n&quot;;
            q.pop();
        }
        else if (str == &quot;size&quot;)
        {
            cout &amp;lt;&amp;lt; q.size() &amp;lt;&amp;lt; &quot;\n&quot;;
        }
        else if (str == &quot;empty&quot;)
        {
            cout &amp;lt;&amp;lt; (q.empty() ? 1 : 0) &amp;lt;&amp;lt; &quot;\n&quot;;
        }
        else if (str == &quot;front&quot;)
        {
            if (q.empty())
            {
                cout &amp;lt;&amp;lt; -1 &amp;lt;&amp;lt; &quot;\n&quot;;
                continue;
            }
            cout &amp;lt;&amp;lt; q.front() &amp;lt;&amp;lt; &quot;\n&quot;;
        }
        else if (str == &quot;back&quot;)
        {
            if (q.empty())
            {
                cout &amp;lt;&amp;lt; -1 &amp;lt;&amp;lt; &quot;\n&quot;;
                continue;
            }
            cout &amp;lt;&amp;lt; q.back() &amp;lt;&amp;lt; &quot;\n&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!
:::&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/10845.%E2%80%85%ED%81%90&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 11050번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-11050/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-11050/</guid><description>백준 11051번 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 11050번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;자연수
$N$과 정수
$K$가 주어졌을 때 이항 계수
$\binom{N}{K}$를 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에
$N$과
$K$가 주어진다. (1 ≤
$N ≤ 10, 0 ≤
K ≤
N$)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;$\binom{N}{K}$를 출력한다.&lt;/p&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5 2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;
10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;숫자를 입력받는다.&lt;/li&gt;
&lt;li&gt;각 숫자까지 반복문을 이용해 곱하고 나눈다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

using namespace std;

int N, K, S;
int main()
{
    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;
    S = N - K;
    double result = 1;
    for (int i = 1; i &amp;lt;= N; ++i) result *= i;
    for (int i = 1; i &amp;lt;= K; ++i) result /= i;
    for (int i = 1; i &amp;lt;= S; ++i) result /= i;
    cout &amp;lt;&amp;lt; result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Bronze/11050.%E2%80%85%EC%9D%B4%ED%95%AD%E2%80%85%EA%B3%84%EC%88%98%E2%80%851&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 11650번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-11650/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-11650/</guid><description>백준 11651번 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 11650번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2차원 평면 위의 점 N개가 주어진다. 좌표를 x좌표가 증가하는 순으로, x좌표가 같으면 y좌표가 증가하는 순서로 정렬한 다음 출력하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 점의 개수 N (1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에는 i번점의 위치 xi와 yi가 주어진다. (-100,000 ≤ xi, yi ≤ 100,000) 좌표는 항상 정수이고, 위치가 같은 두 점은 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄부터 N개의 줄에 점을 정렬한 결과를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5&lt;br /&gt;
3 4&lt;br /&gt;
1 2&lt;br /&gt;
1 -1&lt;br /&gt;
2 2&lt;br /&gt;
3 3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;
1 -1
1 2
2 2
3 3
3 4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;요소를 pair쌍으로 배열에 담는다.&lt;/li&gt;
&lt;li&gt;정렬하고 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, K;
std::vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; arr;

bool func(pair&amp;lt;int, int&amp;gt; a, pair&amp;lt;int, int&amp;gt; b)
{
    if (a.first == b.first) return a.second &amp;lt; b.second;
    return a.first &amp;lt; b.first;
}

int main()
{
    cin &amp;gt;&amp;gt; N;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x, y;
        cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
        arr.emplace_back(make_pair(x, y));
    }
    sort(arr.begin(), arr.end(), func);

    for (const auto&amp;amp; iter : arr)
    {
        cout &amp;lt;&amp;lt; iter.first &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; iter.second &amp;lt;&amp;lt; &quot;\n&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/11650.%E2%80%85%EC%A2%8C%ED%91%9C%E2%80%85%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 11651번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-11651/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-11651/</guid><description>백준 11651번 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 11651번 문제 c++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2차원 평면 위의 점 N개가 주어진다. 좌표를 y좌표가 증가하는 순으로, y좌표가 같으면 x좌표가 증가하는 순서로 정렬한 다음 출력하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 점의 개수 N (1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에는 i번점의 위치 xi와 yi가 주어진다. (-100,000 ≤ xi, yi ≤ 100,000) 좌표는 항상 정수이고, 위치가 같은 두 점은 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄부터 N개의 줄에 점을 정렬한 결과를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt;&lt;br /&gt;
5&lt;br /&gt;
0 4&lt;br /&gt;
1 2&lt;br /&gt;
1 -1&lt;br /&gt;
2 2&lt;br /&gt;
3 3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;출력:&lt;/code&gt;
1 -1
1 2
2 2
3 3
0 4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;요소를 pair쌍으로 배열에 담는다.&lt;/li&gt;
&lt;li&gt;정렬하고 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int N, K;
std::vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; arr;

bool func(pair&amp;lt;int, int&amp;gt; a, pair&amp;lt;int, int&amp;gt; b)
{
    if (a.second == b.second) return a.first &amp;lt; b.first;
    return a.second &amp;lt; b.second;
}

int main()
{
    cin &amp;gt;&amp;gt; N;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        int x, y;
        cin &amp;gt;&amp;gt; x &amp;gt;&amp;gt; y;
        arr.emplace_back(make_pair(x, y));
    }
    sort(arr.begin(), arr.end(), func);

    for (const auto&amp;amp; iter : arr)
    {
        cout &amp;lt;&amp;lt; iter.first &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; iter.second &amp;lt;&amp;lt; &quot;\n&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]
맞았습니다!!&lt;/p&gt;
&lt;p&gt;깃헙 링크 : &lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/11651.%E2%80%85%EC%A2%8C%ED%91%9C%E2%80%85%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0%E2%80%852&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 11866번 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-11866/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-11866/</guid><description>백준 11866번 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 11866번 C++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;요세푸스 문제는 다음과 같다.&lt;/p&gt;
&lt;p&gt;1번부터 N번까지 N명의 사람이 원을 이루면서 앉아있고, 양의 정수 K(≤ N)가 주어진다. 이제 순서대로 K번째 사람을 제거한다. 한 사람이 제거되면 남은 사람들로 이루어진 원을 따라 이 과정을 계속해 나간다. 이 과정은 N명의 사람이 모두 제거될 때까지 계속된다. 원에서 사람들이 제거되는 순서를 (N, K)-요세푸스 순열이라고 한다. 예를 들어 (7, 3)-요세푸스 순열은 &amp;lt;3, 6, 2, 7, 5, 1, 4&amp;gt;이다.&lt;/p&gt;
&lt;p&gt;N과 K가 주어지면 (N, K)-요세푸스 순열을 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 ≤ K ≤ N ≤ 1,000)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;출력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;예제와 같이 요세푸스 순열을 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;예제 입력&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;입력:&lt;/code&gt; 7 3&lt;br /&gt;
&lt;code&gt;출력:&lt;/code&gt; &amp;lt;3, 6, 2, 7, 5, 1, 4&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;리스트로 원소를 저장한다.&lt;/li&gt;
&lt;li&gt;원형 큐처럼 끝과 끝을 이어 K번째마다 요소를 빼내고 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1차 시도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;list&amp;gt;

using namespace std;

int N, K;
std::list&amp;lt;int&amp;gt; arr;

int main()
{
    cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;
    for (int i = 0; i &amp;lt; N; ++i)
    {
        arr.emplace_back(i + 1);
    }

    cout &amp;lt;&amp;lt; &quot;&amp;lt;&quot;;

    int i = 1;
    auto pr = arr.begin();

    while (!arr.empty())
    {
        if (i % K == 0)
        {
            cout &amp;lt;&amp;lt; (arr.size() == N ? &quot;&quot; : &quot;, &quot;) &amp;lt;&amp;lt; *pr;
            pr = arr.erase(pr);
        }
        else
            pr++;
        i++;

        if (pr == arr.end())
        {
            pr = arr.begin();
        }
    }
    cout &amp;lt;&amp;lt; &quot;&amp;gt;&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[결과]&lt;br /&gt;
맞았습니다!!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/11866.%E2%80%85%EC%9A%94%EC%84%B8%ED%91%B8%EC%8A%A4%E2%80%85%EB%AC%B8%EC%A0%9C%E2%80%850&quot;&gt;깃헙 링크&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 15829 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-15829/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-15829/</guid><description>백준 15829번 문제 C++ 풀이</description><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 15829번 C++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;APC에 온 것을 환영한다. 만약 여러분이 학교에서 자료구조를 수강했다면 해시 함수에 대해 배웠을 것이다. 해시 함수란 임의의 &amp;gt;길이의 입력을 받아서 고정된 길이의 출력을 내보내는 함수로 정의한다. 해시 함수는 무궁무진한 응용 분야를 갖는데, 대표적으로 자료의 저장과 탐색에 쓰인다.&lt;/p&gt;
&lt;p&gt;이 문제에서는 여러분이 앞으로 유용하게 쓸 수 있는 해시 함수를 하나 가르쳐주고자 한다. 먼저, 편의상 입력으로 들어오는 문자&amp;gt;열에는 영문 소문자(a, b, ..., z)로만 구성되어있다고 가정하자. 영어에는 총 26개의 알파벳이 존재하므로 a에는 1, b에는 2, c에는 3, ..., z에는 26으로 고유한 번호를 부여할 수 있다. 결과적으로 우리는 하나의 문자열을 수열로 변환할 수 있다. 예를 들어서 문자열 &quot;abba&quot;은 수열 1, 2, 2, 1로 나타낼 수 있다.&lt;/p&gt;
&lt;p&gt;해시 값을 계산하기 위해서 우리는 문자열 혹은 수열을 하나의 정수로 치환하려고 한다. 간단하게는 수열의 값을 모두 더할 수도 &amp;gt;있다. 해시 함수의 정의에서 유한한 범위의 출력을 가져야 한다고 했으니까 적당히 큰 수 M으로 나눠주자. 짜잔! 해시 함수가 완성되었다. 이를 수식으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;$$H = \sum_{i=0}^{l-1}{a_i} \mod M$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;해시 함수의 입력으로 들어올 수 있는 문자열의 종류는 무한하지만 출력 범위는 정해져있다. 다들 비둘기 집의 원리에 대해서는 한&amp;gt; 번쯤 들어봤을 것이다. 그 원리에 의하면 서로 다른 문자열이더라도 동일한 해시 값을 가질 수 있다. 이를 해시 충돌이라고 하는데, 좋은 해시 함수는 최대한 충돌이 적게 일어나야 한다. 위에서 정의한 해시 함수는 알파벳의 순서만 바꿔도 충돌이 일어나기 때문에 나쁜 해시 함수이다. 그러니까 조금 더 개선해보자.&lt;/p&gt;
&lt;p&gt;어떻게 하면 순서가 달라졌을때 출력값도 달라지게 할 수 있을까? 머리를 굴리면 수열의 각 항마다 고유한 계수를 부여하면 된다는&amp;gt; 아이디어를 생각해볼 수 있다. 가장 대표적인 방법은 항의 번호에 해당하는 만큼 특정한 숫자를 거듭제곱해서 곱해준 다음 더하는 것이 있다. 이를 수식으로 표현하면 아래와 같다.&lt;br /&gt;
 &amp;gt;
$$H = \sum_{i=0}^{l-1}{a_ir^i} \mod M$$&lt;/p&gt;
&lt;p&gt;보통 r과 M은 서로소인 숫자로 정하는 것이 일반적이다. 우리가 직접 정하라고 하면 힘들테니까 r의 값은 26보다 큰 소수인 31로 &amp;gt; 하고 M의 값은 1234567891(놀랍게도 소수이다!!)로 하자.&lt;/p&gt;
&lt;p&gt;이제 여러분이 할 일은 위 식을 통해 주어진 문자열의 해시 값을 계산하는 것이다. 그리고 이 함수는 간단해 보여도 자주 쓰이니까&amp;gt; 기억해뒀다가 잘 써먹도록 하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;입력&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;첫 줄에는 문자열의 길이 L이 들어온다. 둘째 줄에는 영문 소문자로만 이루어진 문자열이 들어온다.&lt;/p&gt;
&lt;p&gt;입력으로 주어지는 문자열은 모두 알파벳 소문자로만 구성되어 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;출력&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;문제에서 주어진 해시함수와 입력으로 주어진 문자열을 사용해 계산한 해시 값을 정수로 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;입력 문자열을 숫자로 바꿉니다.&lt;/li&gt;
&lt;li&gt;for문을 이용하여 31의 n승과 숫자로 바꾼 문자를 계속 더해간다.&lt;/li&gt;
&lt;li&gt;1234567891로 나눈 나머지를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;h3&gt;1차시도&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

double N;
string arr;

int main()
{
    cin &amp;gt;&amp;gt; N;

    cin &amp;gt;&amp;gt; arr;

    long long result = 0;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        result += (static_cast&amp;lt;int&amp;gt;(arr[i] - &apos;a&apos; + 1) *
                   static_cast&amp;lt;long long&amp;gt;(pow(31, i))) %
                  1234567891;
    }

    cout &amp;lt;&amp;lt; result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과 : &lt;code&gt;50점&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;실패원인&lt;/h3&gt;
&lt;p&gt;자료형의 최대치를 넘겨 오버플로우가 일어나는 것으로 추정됩니다.&lt;/p&gt;
&lt;h3&gt;재도전&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

double N;
string arr;

int main()
{
    cin &amp;gt;&amp;gt; N;
    cin &amp;gt;&amp;gt; arr;

    unsigned long long result = 0;
    unsigned long long tmp = 1;

    for (int i = 0; i &amp;lt; N; ++i)
    {
        result += static_cast&amp;lt;int&amp;gt;(arr[i] - &apos;a&apos; + 1) * tmp % 1234567891;
        tmp *= 31;
        tmp %= 1234567891;
    }

    cout &amp;lt;&amp;lt; result % 1234567891;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과 : &lt;code&gt;맞혔습니다&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Bronze/15829.%E2%80%85Hashing&quot;&gt;풀이 링크&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>백준 18110 C++ 풀이</title><link>https://blog.ushiohayase.com/posts/bj-18110/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/bj-18110/</guid><description>백준 18110번 문제 C++ 풀이</description><pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;백준 18110번 C++ 풀이&lt;/h1&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;solved.ac는 Sogang ICPC Team 학회원들의 알고리즘 공부에 도움을 주고자 만든 서비스이다. 지금은 서강대뿐만 아니라 수많은 사람들이 solved.ac의 도움을 받아 알고리즘 공부를 하고 있다.&lt;/p&gt;
&lt;p&gt;ICPC Team은 백준 온라인 저지에서 문제풀이를 연습하는데, 백준 온라인 저지의 문제들에는 난이도 표기가 없어서, 지금까지는 다양한 문제를 풀어 보고 싶더라도 난이도를 가늠하기 어려워 무슨 문제를 풀어야 할지 판단하기 곤란했기 때문에 solved.ac가 만들어졌다. solved.ac가 생긴 이후 전국에서 200명 이상의 기여자 분들께서 소중한 난이도 의견을 공유해 주셨고, 지금은 약 7,000문제에 난이도 표기가 붙게 되었다.&lt;/p&gt;
&lt;p&gt;어떤 문제의 난이도는 그 문제를 푼 사람들이 제출한 난이도 의견을 바탕으로 결정한다. 난이도 의견은 그 사용자가 생각한 난이도를 의미하는 정수 하나로 주어진다. solved.ac가 사용자들의 의견을 바탕으로 난이도를 결정하는 방식은 다음과 같다.&lt;/p&gt;
&lt;p&gt;아직 아무 의견이 없다면 문제의 난이도는 0으로 결정한다.
의견이 하나 이상 있다면, 문제의 난이도는 모든 사람의 난이도 의견의 30% 절사평균으로 결정한다.
절사평균이란 극단적인 값들이 평균을 왜곡하는 것을 막기 위해 가장 큰 값들과 가장 작은 값들을 제외하고 평균을 내는 것을 말한다. 30% 절사평균의 경우 위에서 15%, 아래에서 15%를 각각 제외하고 평균을 계산한다. 따라서 20명이 투표했다면, 가장 높은 난이도에 투표한 3명과 가장 낮은 난이도에 투표한 3명의 투표는 평균 계산에 반영하지 않는다는 것이다.&lt;/p&gt;
&lt;p&gt;제외되는 사람의 수는 위, 아래에서 각각 반올림한다. 25명이 투표한 경우 위, 아래에서 각각 3.75명을 제외해야 하는데, 이 경우 반올림해 4명씩을 제외한다.&lt;/p&gt;
&lt;p&gt;마지막으로, 계산된 평균도 정수로 반올림된다. 절사평균이 16.7이었다면 최종 난이도는 17이 된다.&lt;/p&gt;
&lt;p&gt;사용자들이 어떤 문제에 제출한 난이도 의견 목록이 주어질 때, solved.ac가 결정한 문제의 난이도를 계산하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;입력&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;첫 번째 줄에 난이도 의견의 개수 n이 주어진다. (0 ≤ n ≤ 3 × 105)&lt;br /&gt;
이후 두 번째 줄부터 1 + n번째 줄까지 사용자들이 제출한 난이도 의견 n개가 한 줄에 하나씩 주어진다. 모든 난이도 의견은 1 이상 30 이하이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;출력&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;solved.ac가 계산한 문제의 난이도를 출력한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이디어&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;먼저 N을 입력받고 N의 15%를 구한 다음 반올림합니다.&lt;/li&gt;
&lt;li&gt;algorithm 헤더의 sort를 이용해 오름차순으로 정렬합니다.&lt;/li&gt;
&lt;li&gt;입력받은 난이도 의견 배열에 반올림한 값의 길이를 양쪽에서 뺀 길이의 원소를 다 더한다.&lt;/li&gt;
&lt;li&gt;더한 값을 N - 2 * 반올림값으로 나눠준걸 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;h3&gt;1차시도&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

double N;
int* arr;

int main()
{
    cin &amp;gt;&amp;gt; N;

    arr = new int[static_cast&amp;lt;int&amp;gt;(N)];
    for (int i = 0; i &amp;lt; N; ++i)
    {
        cin &amp;gt;&amp;gt; arr[i];
    }
    int exclude = round(0.15 * N);
    int result = 0;

    sort(arr, arr + static_cast&amp;lt;int&amp;gt;(N));

    for (int i = exclude; i &amp;lt; N - exclude; ++i)
    {
        result += arr[i];
    }
    cout &amp;lt;&amp;lt; round(result / (N - 2 * exclude));

    delete[] arr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과 : &lt;code&gt;틀렸습니다&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;실패원인&lt;/h3&gt;
&lt;p&gt;문제 조건에서 0일때 0 출력하라는걸 못봤습니다.&lt;/p&gt;
&lt;h3&gt;재도전&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

double N;
int* arr;

int main()
{
    cin &amp;gt;&amp;gt; N;

    if (N == 0)
    {
        cout &amp;lt;&amp;lt; 0;
        return 0;
    }

    arr = new int[static_cast&amp;lt;int&amp;gt;(N)];
    for (int i = 0; i &amp;lt; N; ++i)
    {
        cin &amp;gt;&amp;gt; arr[i];
    }
    int exclude = round(0.15 * N);
    int result = 0;

    sort(arr, arr + static_cast&amp;lt;int&amp;gt;(N));

    for (int i = exclude; i &amp;lt; N - exclude; ++i)
    {
        result += arr[i];
    }
    cout &amp;lt;&amp;lt; round(result / (N - 2 * exclude));

    delete[] arr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과 : &lt;code&gt;맞혔습니다&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Ushio-Hayase/Baekjoon/tree/main/%EB%B0%B1%EC%A4%80/Silver/18110.%E2%80%85solved%EF%BC%8Eac&quot;&gt;풀이 링크&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>윈도우 게임 루프와 프레임 동기화</title><link>https://blog.ushiohayase.com/posts/winapi-3/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/winapi-3/</guid><description>QueryPerformanceCounter/Frequency를 이용하여 프레임 동기화</description><pubDate>Sun, 02 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;목표&lt;/h1&gt;
&lt;p&gt;이 글에서는 Performance Counter를 이용해서 프로그램의 FPS(Frame Per Second)를 60으로 동기화하는 작업을 할 것입니다.&lt;/p&gt;
&lt;h1&gt;개념&lt;/h1&gt;
&lt;h2&gt;QueryPerformaceCounter&lt;/h2&gt;
&lt;p&gt;QueryPerformanceCounter 함수는 Performace Counter의 타임 스탬프를 반환하는 함수로 이를 이용하면 정확한 시간을 알 수 있습니다.&lt;/p&gt;
&lt;h2&gt;QueryPerformaceFrequency&lt;/h2&gt;
&lt;p&gt;이 함수는 Performance Counter의 초당 카운트 횟수를 검색하는 함수로 위 함수와 같이 사용해 카운터를 실제 시간 단위로 변환할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;구현&lt;/h1&gt;
&lt;h2&gt;구현 계획&lt;/h2&gt;
&lt;p&gt;윈도우 API의 PeekMessage 함수를 이용해서 렌더링을 논블로킹으로 하고 QueryPerformanceCounter와 QueryPerformanceFrequency 함수를 Sleep 함수에 사용하여 남는 시간동안 프로그램이 놀도록 할 계획입니다.&lt;/p&gt;
&lt;h3&gt;1차 시도&lt;/h3&gt;
&lt;p&gt;1차 시도에서의 주요 코드는 다음과 같았습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while (running)
{
    // 윈도우 메시지 처리
    while (PeekMessage(&amp;amp;msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT) running = false;
        TranslateMessage(&amp;amp;msg);
        DispatchMessage(&amp;amp;msg);
    }

    // 프레임 시간 측정
    QueryPerformanceCounter(&amp;amp;currentTime);
    deltaTime =
        static_cast&amp;lt;double&amp;gt;(currentTime.QuadPart - prevTime.QuadPart) /
        frequency.QuadPart;

    double sleepTime = FRAME_TIME - deltaTime;

    if (sleepTime &amp;gt; 0) Sleep(1000 * sleepTime);
    prevTime = currentTime;

    fpsTime += deltaTime + sleepTime;
    frameCount++;

    // 화면 렌더링 (FPS 표시)
    RenderGraphics(hWnd, frameCount);

    // 1초마다 FPS 출력
    if (fpsTime &amp;gt;= 1.0)
    {
        HDC hdc = GetDC(hWnd);
        TCHAR str[50];
        StringCbPrintf(str, sizeof(str) * sizeof(TCHAR), TEXT(&quot;FPS: %d&quot;),
                       frameCount);
        TextOut(hdc, 300, 300, str, lstrlen(str));
        ReleaseDC(hWnd, hdc);
        frameCount = 0;
        fpsTime = 0.0;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드로 메시지는 PeekMessage 함수로 논블로킹으로 처리하고 남는 시간을 Sleep 함수로 놀도록 할 계획이였지만 결과는 65-66 FPS가 나오면서 정확한 60FPS 동작하지 않았습니다.&lt;/p&gt;
&lt;h3&gt;원인 분석&lt;/h3&gt;
&lt;p&gt;Sleep 함수는 윈도우의 기본 타이머 해상도가 CPU 부하 및 기타 이슈로 15.6ms라 Sleep(1)이 1ms이 아닌 경우가 많기에 이런 오류가 발생하는 것으로 추정됐다.&lt;/p&gt;
&lt;h3&gt;1차 해결책&lt;/h3&gt;
&lt;p&gt;윈도우의 타이머 해상도를 더 정확하게 만들기 위해 &lt;code&gt;timeBeginPeriod(1);&lt;/code&gt;를 호출하기 위해서서 &lt;code&gt;Windows.h, timeapi.h&lt;/code&gt; 헤더를 포함하고 &lt;code&gt;winmm.lib&lt;/code&gt; 라이브러리를 링크했다.&lt;/p&gt;
&lt;h3&gt;2차 시도&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;timeBeginPeriod(1);

while (running)
{
    // 윈도우 메시지 처리
    while (PeekMessage(&amp;amp;msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT) running = false;
        TranslateMessage(&amp;amp;msg);
        DispatchMessage(&amp;amp;msg);
    }

    // 프레임 시간 측정
    QueryPerformanceCounter(&amp;amp;currentTime);
    deltaTime =
        static_cast&amp;lt;double&amp;gt;(currentTime.QuadPart - prevTime.QuadPart) /
        frequency.QuadPart;

    double sleepTime = FRAME_TIME - deltaTime;

    if (sleepTime &amp;gt; 0) Sleep(1000 * sleepTime);
    prevTime = currentTime;

    fpsTime += deltaTime + sleepTime;
    frameCount++;

    // 화면 렌더링 (FPS 표시)
    RenderGraphics(hWnd, frameCount);

    // 1초마다 FPS 출력
    if (fpsTime &amp;gt;= 1.0)
    {
        HDC hdc = GetDC(hWnd);
        TCHAR str[50];
        StringCbPrintf(str, sizeof(str) * sizeof(TCHAR), TEXT(&quot;FPS: %d&quot;),
                       frameCount);
        TextOut(hdc, 300, 300, str, lstrlen(str));
        ReleaseDC(hWnd, hdc);
        frameCount = 0;
        fpsTime = 0.0;
    }
}

timeEndPeriod(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2차 시도 코드는 다음과 같았고 결과는 60FPS가 잘 나오면서 성공했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./image.png&quot; alt=&quot;성공&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;결론 및 느낀점&lt;/h1&gt;
&lt;p&gt;처음에 생각하기엔 쉬워보였는데 생각외로 찾아볼게 있는 프로젝트였다.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Ushio-Hayase/WindowAPI-Timer&quot;}&lt;/p&gt;
</content:encoded></item><item><title>C++과 스택 (Stack)</title><link>https://blog.ushiohayase.com/posts/%EC%8A%A4%ED%83%9D/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/%EC%8A%A4%ED%83%9D/</guid><description>스택의 개념과 STL에서의 구현 알아보기</description><pubDate>Wed, 26 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;스택&lt;/h1&gt;
&lt;p&gt;스택 (Stack)&lt;/p&gt;
&lt;h2&gt;추상적 자료형으로서의 스택&lt;/h2&gt;
&lt;p&gt;스택은 데이터를 차곡차곡 쌓아 올린 형태의 자료구조로 다음과 같은 특징을 가집니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;LIFO(Last In, First Out; 후입선출)의 구조로 가장 먼저 넣은 데이터가 가장 나중에 나옵니다.&lt;/li&gt;
&lt;li&gt;스택의 top라 불리는 지점에서만 데이터의 삽입과 삭제가 이루어집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./%EC%8A%A4%ED%83%9D.jpg&quot; alt=&quot;스택&quot; /&gt;&lt;/p&gt;
&lt;p&gt;스택은 간단하고 효율적인 구조라 구현이 쉽고 데이터의 추가, 삭제가 빨라 많은 곳에서 사용됩니다.&lt;br /&gt;
대표적으로 뒤로가기 기능(undo), 재귀 알고리즘 구현 등등에서 사용됩니다.&lt;/p&gt;
&lt;h3&gt;스택이 정의하는 연산&lt;/h3&gt;
&lt;p&gt;스택은 여러가지 연산을 지원하지만 주로 말하는 연산은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Push : 스택에 데이터를 넣는 연산&lt;/li&gt;
&lt;li&gt;Pop : 스택에서 데이터를 빼내는 연산&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 연산들은 모두 스택의 top라는 곳에서 이루어지게 됩니다.&lt;/p&gt;
&lt;h3&gt;스택을 구현하는 방법&lt;/h3&gt;
&lt;p&gt;스택을 구현하는 방법은 당연히 여러가지가 있지만 대표적으로 이 3가지를 주로 사용합니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;배열을 이용한 구현:&lt;br /&gt;
고정 크기의 배열을 사용합니다.
top 변수로 스택의 최상단 요소를 추적합니다.
push 연산은 top을 증가시키고 해당 위치에 데이터를 저장합니다.
pop 연산은 top의 데이터를 반환하고 top을 감소시킵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./%EC%8A%A4%ED%83%9D%EB%B0%B0%EC%97%B4%EA%B5%AC%ED%98%84.jpg&quot; alt=&quot;스택배열구현&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;동적 배열을 이용한 구현:&lt;br /&gt;
배열이 가득 차면 크기를 동적으로 늘립니다.
push 연산 시 배열이 가득 찼다면, 더 큰 배열을 할당하고 데이터를 복사합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;연결 리스트를 이용한 구현:&lt;br /&gt;
각 노드가 데이터와 다음 노드를 가리키는 포인터를 갖습니다.
push 연산은 새 노드를 생성하여 리스트의 맨 앞에 추가합니다.
pop 연산은 첫 번째 노드를 제거하고 그 데이터를 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./%EC%8A%A4%ED%83%9D%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8%EA%B5%AC%ED%98%84.jpg&quot; alt=&quot;스택연결리스트구현&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;C++에서의 스택&lt;/h2&gt;
&lt;p&gt;C++에서 스택은 다음과 같이 선언합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stack&amp;gt;

std::stack&amp;lt;int&amp;gt; stack;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;스택의 주요 멤버 변수&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Ty, class _Container = deque&amp;lt;_Ty&amp;gt;&amp;gt;
class stack
{
    // 기타 코드...
protected:
    _Container c{};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드는 MSVC 2022에서 stack 헤더에 있는 코드를 가져온 것입니다.&lt;br /&gt;
보이는 것과 같이 스택은 덱을 이용하여 구현하고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;스택의 주요 메서드&lt;/h3&gt;
&lt;p&gt;스택의 메소드 역시 덱의 메소드를 호출하는 형태로 구현되어 있는 걸 아래 코드를 통해 보실 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_NODISCARD_EMPTY_MEMBER_NO_CLEAR bool empty() const noexcept(noexcept(c.empty())) /* strengthened */ {
    return c.empty();
}

_NODISCARD size_type size() const noexcept(noexcept(c.size())) /* strengthened */ {
    return c.size();
}

_NODISCARD reference top() noexcept(noexcept(c.back())) /* strengthened */ {
    return c.back();
}

_NODISCARD const_reference top() const noexcept(noexcept(c.back())) /* strengthened */ {
    return c.back();
}

void push(const value_type&amp;amp; _Val) {
    c.push_back(_Val);
}

void push(value_type&amp;amp;&amp;amp; _Val) {
    c.push_back(_STD move(_Val));
}

void pop() noexcept(noexcept(c.pop_back())) /* strengthened */ {
    c.pop_back();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 메서드는 위쪽부터&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스택이 비었는지 확인&lt;/li&gt;
&lt;li&gt;스택의 크기 확인&lt;/li&gt;
&lt;li&gt;스택의 가장 위쪽 요소 반환&lt;/li&gt;
&lt;li&gt;스택에 요소 넣기&lt;/li&gt;
&lt;li&gt;스택에서 요소 제거
기능들을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>C++과 큐 (Queue), 덱 (Deque)</title><link>https://blog.ushiohayase.com/posts/%ED%81%90%EC%99%80-%EB%8D%B1/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/%ED%81%90%EC%99%80-%EB%8D%B1/</guid><description>큐와 덱의 개념과 STL에서의 구현 알아보기</description><pubDate>Wed, 26 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;큐&lt;/h1&gt;
&lt;p&gt;큐(Queue)&lt;/p&gt;
&lt;h2&gt;추상적 자료형으로서의 큐&lt;/h2&gt;
&lt;h3&gt;큐 정의&lt;/h3&gt;
&lt;p&gt;큐는 FIFO(First In, First Out; 선입선출)의 자료구조로
먼저 들어간 자료가 먼저 출력되는 형태입니다.&lt;br /&gt;
그림으로는 아래와 같이 나타낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%81%90.jpg&quot; alt=&quot;큐.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;데이터가 들어가는 쪽인 큐의 뒤쪽은 rear라 부르고&lt;br /&gt;
데이터가 나오는 쪽인 큐의 앞쪽은 front라 부릅니다.&lt;/p&gt;
&lt;p&gt;큐는 데이터의 삽입, 삭제가 빠르고 구현이 어렵지 않아 많은 곳에서 사용됩니다.&lt;br /&gt;
대표적으로 Window API의 메시지 큐, 네트워크, BFS(넓이 우선 탐색) 알고리즘 구현등 여러곳에서 사용되고 있습니다.&lt;/p&gt;
&lt;h3&gt;큐를 정의하는 동작&lt;/h3&gt;
&lt;p&gt;큐가 정의하는 연산은 여러가지있지만 핵심적인 연산은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인큐(Enqueue) : 큐에 새로운 데이터를 추가하는 연산입니다. 데이터는 큐의 맨 뒤(rear)에 삽입됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./enqueue.png&quot; alt=&quot;인큐&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;디큐(Dequeue) : 큐에서 데이터를 꺼내는 연산입니다. 큐의 맨 앞(front)에 있는 데이터를 제거하고 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./Dequeue.jpg&quot; alt=&quot;디큐&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;피크(Peek) : 큐의 맨 앞(front)에 있는 데이터를 조회하는 연산입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./Peek.jpg&quot; alt=&quot;피크&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;IsEmpty : 큐가 비어있는지 확인하는 연산입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./IsEmpty.jpg&quot; alt=&quot;IsEmpty&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;큐를 구현하는 방법&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;배열을 이용한 큐 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;배열을 사용한 큐 구현의 주요 특징은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;front와 rear 변수를 사용하여 큐의 앞과 뒤를 관리합니다.&lt;/p&gt;
&lt;p&gt;enqueue 연산: rear를 1 증가시키고 해당 위치에 데이터를 삽입합니다.&lt;/p&gt;
&lt;p&gt;dequeue 연산: front를 1 증가시켜 데이터를 논리적으로 제거합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ArrayQueue {
public:
    ArrayQueue(int size) {
        arr = new int[size];
        capacity = size;
        front = rear = -1;
    }

    void enqueue(int x) {
        if (isFull()) {
            cout &amp;lt;&amp;lt; &quot;Queue is full&quot; &amp;lt;&amp;lt; endl;
            return;
        }
        if (isEmpty()) front = 0;
        rear = (rear + 1) % capacity;
        arr[rear] = x;
    }

    int dequeue() {
        if (isEmpty()) {
            cout &amp;lt;&amp;lt; &quot;Queue is empty&quot; &amp;lt;&amp;lt; endl;
            return -1;
        }
        int item = arr[front];
        if (front == rear) front = rear = -1;
        else front = (front + 1) % capacity;
        return item;
    }

    bool isEmpty();
    bool isFull();
private:
    int* arr;
    int front, rear;
    int capacity;

};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%81%90.jpg&quot; alt=&quot;배열큐&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;연결 리스트를 이용한 큐 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;연결 리스트를 사용한 큐 구현의 주요 특징은 다음과 같습니다:&lt;/p&gt;
&lt;p&gt;노드를 사용하여 데이터를 저장하고 연결합니다.&lt;/p&gt;
&lt;p&gt;front와 rear 포인터를 사용하여 큐의 앞과 뒤를 관리합니다.&lt;/p&gt;
&lt;p&gt;enqueue 연산: 새 노드를 생성하여 rear에 연결합니다.&lt;/p&gt;
&lt;p&gt;dequeue 연산: front 노드를 제거하고 다음 노드로 이동합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Node {
    int data;
    Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

class LinkedListQueue {
public:
    LinkedListQueue() : front(nullptr), rear(nullptr) {}

    void enqueue(int x) {
        Node* temp = new Node(x);
        if (rear == nullptr) {
            front = rear = temp;
            return;
        }
        rear-&amp;gt;next = temp;
        rear = temp;
    }

    int dequeue() {
        if (isEmpty()) {
            cout &amp;lt;&amp;lt; &quot;Queue is empty&quot; &amp;lt;&amp;lt; endl;
            return -1;
        }
        Node* temp = front;
        int item = front-&amp;gt;data;
        front = front-&amp;gt;next;
        if (front == nullptr) rear = nullptr;
        delete temp;
        return item;
    }

    bool isEmpty();

private:
    Node *front, *rear;

};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./LinkedListQueue.jpg&quot; alt=&quot;LinkedListQueue&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;원형 큐 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;배열을 사용한 구현의 문제점을 해결하기 위해 원형 큐를 사용합니다:&lt;/p&gt;
&lt;p&gt;배열의 끝에 도달하면 다시 처음으로 순환합니다.&lt;/p&gt;
&lt;p&gt;(rear + 1) % capacity(큐가 꽉 차면 처음부터 다시 채움)를 사용하여 원형 구조를 구현합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CircularQueue {
public:
    CircularQueue(int size) {
        arr = new int[size];
        capacity = size;
        front = rear = -1;
    }

    void enqueue(int x) {
        if (isFull()) {
            cout &amp;lt;&amp;lt; &quot;Queue is full&quot; &amp;lt;&amp;lt; endl;
            return;
        }
        if (isEmpty()) front = 0;
        rear = (rear + 1) % capacity;
        arr[rear] = x;
    }

    int dequeue() {
        if (isEmpty()) {
            cout &amp;lt;&amp;lt; &quot;Queue is empty&quot; &amp;lt;&amp;lt; endl;
            return -1;
        }
        int item = arr[front];
        if (front == rear) front = rear = -1;
        else front = (front + 1) % capacity;
        return item;
    }

    bool isEmpty();
    bool isFull();

private:
    int* arr;
    int front, rear;
    int capacity;

};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./CircularQueue.jpg&quot; alt=&quot;원형큐&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;C++에서의 큐&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;queue&amp;gt;

std::queue&amp;lt;int&amp;gt; q; // int 형의 큐 선언
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;큐의 주요 멤버 변수&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Ty, class _Container = deque&amp;lt;_Ty&amp;gt;&amp;gt;
class queue
{
    // 기타코드...
protected:
    // 덱을 주어진 템플릿 인자의 자료형으로 생성
    _Container c{};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드처럼 C++ STL에서의 큐는 STL의 덱을 이용하도록 설계되어 있습니다.&lt;/p&gt;
&lt;h3&gt;큐의 주요 메소드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 큐가 비었는지 확인
_NODISCARD_EMPTY_MEMBER_NO_CLEAR bool empty() const noexcept(noexcept(c.empty())) /* strengthened */ {
    return c.empty();
}

// 큐의 크기 확인
_NODISCARD size_type size() const noexcept(noexcept(c.size())) /* strengthened */ {
    return c.size();
}

// 큐의 front(앞)의 참조자 반환
_NODISCARD reference front() noexcept(noexcept(c.front())) /* strengthened */ {
    return c.front();
}


// 큐의 front(앞)의 상수 참조자 반환 
_NODISCARD const_reference front() const noexcept(noexcept(c.front())) /* strengthened */ {
    return c.front();
}

// 큐의 rear(뒤)의 참조자 반환 
_NODISCARD reference back() noexcept(noexcept(c.back())) /* strengthened */ {
    return c.back();
}

// 큐의 rear(뒤)의 상수 참조자 반환환
_NODISCARD const_reference back() const noexcept(noexcept(c.back())) /* strengthened */ {
    return c.back();
}

// 큐에 요소 추가 (l-value;좌측값)
void push(const value_type&amp;amp; _Val) {
    c.push_back(_Val);
}

// 큐에 요소 추가 (r-value;우측값)
void push(value_type&amp;amp;&amp;amp; _Val) {
    c.push_back(_STD move(_Val));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마찬가지로 큐의 메소드 또한 덱의 메소드를 호출하는 형태로 구현되어있습니다.&lt;/p&gt;
&lt;h1&gt;덱&lt;/h1&gt;
&lt;p&gt;덱(Deque; Double-Ended Queue)&lt;/p&gt;
&lt;h2&gt;추상적 자료형으로서의 덱&lt;/h2&gt;
&lt;h3&gt;덱의 정의&lt;/h3&gt;
&lt;p&gt;덱은 양쪽 끝에서 삽입과 삭제가 가능한 자료구조입니다.
덱은 큐와 스택의 특성을 모두 가지고 있어, 데이터의 FIFO(First In, First Out; 선입선출)와 LIFO(Last In, First Out; 후입선출)을 유연하게 조합할 수 있습니다.
덱은 리스트와 유사하지만, 항목의 추가와 삭제가 주로 양 끝에서 이루어집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%EB%8D%B1.jpg&quot; alt=&quot;덱.jpg&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;덱의 주요 동작&lt;/h3&gt;
&lt;p&gt;덱이 정의하는 동작은 여러가지가 있지만 주요한 동작은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;덱 앞 쪽에 요소 추가&lt;/li&gt;
&lt;li&gt;덱 뒤 쪽에 요소 추가&lt;/li&gt;
&lt;li&gt;덱 앞 쪽의 요소 반환&lt;/li&gt;
&lt;li&gt;덱 뒤 쪽의 요소 반환&lt;/li&gt;
&lt;li&gt;덱 앞 쪽 요소 삭제&lt;/li&gt;
&lt;li&gt;덱 뒤 쪽 요소 삭제&lt;/li&gt;
&lt;li&gt;덱이 가득 찼는지 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 연산들은 일반적으로 $O(1)$의 시간복잡도를 가집니다.&lt;/p&gt;
&lt;h2&gt;C++ STL에서의 덱&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;deque&amp;gt;

std::deque&amp;lt;int&amp;gt; dq;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;덱의 주요 멤버 변수&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Ty, class _Alloc = allocator&amp;lt;_Ty&amp;gt;&amp;gt;
class deque
{
    using _Alty = _Rebind_alloc_t&amp;lt;_Alloc, _Ty&amp;gt;;
    using _Scary_val = _Deque_val&amp;lt;conditional_t&amp;lt;_Is_simple_alloc_v&amp;lt;_Alty&amp;gt;, _Deque_simple_types&amp;lt;_Ty&amp;gt;,
    _Deque_iter_types&amp;lt;_Ty, typename _Alty_traits::size_type, typename _Alty_traits::difference_type,
        typename _Alty_traits::pointer, typename _Alty_traits::const_pointer, _Mapptr&amp;gt;&amp;gt;&amp;gt;;

    // 메모리를 할당하고 관리하는 객체 
    _Compressed_pair&amp;lt;_Alty, _Scary_val&amp;gt; _Mypair;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STL에서 덱은 위 코드처럼 템플릿을 이용하여 작성되어 있습니다. &lt;code&gt;_Compressed_pair&lt;/code&gt;는 위 코드에서 &lt;code&gt;_Alty&lt;/code&gt;가 상태를 가지지 않을 때 메모리 오버헤드를 줄이기 위해 사용합니다. &lt;code&gt;_Alty&lt;/code&gt;는 Allocator라고 부르는 덱의 메모리 할당과 해제를 관리하는 객체입니다. STL의 덱에서는 메모리 상에서 연속된 단일 배열을 사용하지않고 한 블록,청크 단위로 메모리를 할당합니다.&lt;br /&gt;
Allocator는 &lt;code&gt;std::allocator_traits::allocate()&lt;/code&gt;를 사용하여 새로운 청크를 할당하고 &lt;code&gt;std::allocator_traits::construct()&lt;/code&gt;를 호출하여 개별 객체의 생성자를 호출합니다. 특정 청크, 블록이 필요없어지면 &lt;code&gt;std::allocator_traits::destroy()&lt;/code&gt;를 호출하여 객체의 소멸자를 실행하고 객체가 모두 소멸되면 &lt;code&gt;std::allocator_traits::deallocate()&lt;/code&gt;를 호출하여 메모리를 해제합니다.&lt;/p&gt;
&lt;p&gt;덱은 각 청크를 포인터 배열을 이용해서 관리합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;class _Val_types&amp;gt;
class _Deque_val : public _Container_base12
{
    public:
    using value_type      = typename _Val_types::value_type;
    using size_type       = typename _Val_types::size_type;
    using difference_type = typename _Val_types::difference_type;
    using pointer         = typename _Val_types::pointer;
    using const_pointer   = typename _Val_types::const_pointer;
    using reference       = value_type&amp;amp;;
    using const_reference = const value_type&amp;amp;;
    using _Mapptr         = typename _Val_types::_Mapptr;

    // 기타 코드...

    _Mapptr _Map; // pointer to array of pointers to blocks
    size_type _Mapsize; // size of map array, zero or 2^N
    size_type _Myoff; // offset of initial element
    size_type _Mysize; // current length of sequence
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;_Map&lt;/code&gt;이 그 역할을 수행하고 포인터 배열의 사이즈는 2의 n승을 가집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%ED%8F%AC%EC%9D%B8%ED%84%B0%20%EB%B0%B0%EC%97%B4.jpg&quot; alt=&quot;구조.jpg&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;덱의 주요 메소드&lt;/h3&gt;
&lt;p&gt;덱의 주요한 메소드는&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;push_back()&lt;/li&gt;
&lt;li&gt;push_front()&lt;/li&gt;
&lt;li&gt;pop_back()&lt;/li&gt;
&lt;li&gt;pop_front()
등이 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;push_back과 push_front는 해당 블록이 가득차면 새 블록을 추가하는 방식이고&lt;br /&gt;
pop_back과 pop_front도 지우는 데이터가 블록의 마지막 데이터면 블록도 같이 할당 해제하는 방식으로 구현됩니다.&lt;/p&gt;
</content:encoded></item><item><title>PeekMessage와 GetMessage 비교</title><link>https://blog.ushiohayase.com/posts/winapi-1/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/winapi-1/</guid><description>Window API의 GetMessage와 PeekMessage를 비교</description><pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;메시지 큐&lt;/h1&gt;
&lt;p&gt;윈도우 시스템에서는 메시지 큐가 필요한 스레드가 있으면 메시지 큐를 만듭니다.
각 스레드에서의 메시지 루프는 메시지 큐에서 메시지를 가져오고 적절한 창 프로시저로 전달합니다.
만약 최상위 창이 몇 초 이상 메시지에 응답하지 않는 경우 시스템은 창이 응답하지 않는 것으로 간주하고 z 순서, 위치, 크기 및 시각적 특성이 동일한 고스트 창으로 바꿉니다.
이렇게 하면 사용자가 이동하거나 크기를 조정하거나 애플리케이션을 닫을 수 있습니다.&lt;/p&gt;
&lt;h1&gt;메시지 루프&lt;/h1&gt;
&lt;p&gt;메시지 루프에서는 &lt;code&gt;GetMessage&lt;/code&gt;함수와 &lt;code&gt;DispatchMessage&lt;/code&gt;함수를 사용하여 메시지를 가져오고 전달합니다.
프로그램이 사용자로부터 문자 입력을 가져와야 하는 경우 루프에 &lt;code&gt;TranslateMessage&lt;/code&gt;함수를 포함합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MSG Message;

while (GetMessage(&amp;amp;Message, NULL, 0, 0))
{
    TranslateMessage(&amp;amp;Message);
    DispatchMessage(&amp;amp;Message);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;GetMessage&lt;/h2&gt;
&lt;p&gt;GetMessage의 함수 선언은 이렇습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BOOL GetMessage(
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;첫번째 매개변수인 lpMsg는 스레드의 메시지 큐에서 메시지 정보를 수신하는 MSG 구조체에 대한 포인터입니다.
두번째 매개변수인 hWnd는 메시지를 검색할 창에 대한 핸들입니다.
이 때 창은 현재 스레드에 속해야합니다.
만약 hWnd가 NULL이라면 GetMessage 함수는 현재 스레드에 속한 모든 창에 대한 메시지 또는 hWnd 값이 NULL인 메시지를 가져옵니다.
hWnd가 -1이라면 GetMessage는 hwnd 값이 NULL인 현재 스레드의 메시지 큐, 즉 PostMessage에서 게시한 스레드 메시지(hWnd 매개 변수가 NULL인 경우) 또는 PostThreadMessage에서 메시지만 검색합니다.
세번째, 네번째 매개변수는 검색할 가장 낮은 메시지 값의 정수 값과 높은 정수값입니다.
만약 wMsgFilterMin 및 wMsgFilterMax가 모두 0이면 GetMessage는 사용 가능한 모든 메시지를 반환합니다.&lt;/p&gt;
&lt;p&gt;:::tip
이 함수는 WM_QUIT 이외의 메시지를 가져오면 0이 아닌 값, WM_QUIT 메시지를 가져오면 0을 반환합니다.
:::&lt;/p&gt;
&lt;p&gt;:::warning
추가적으로 이 함수를 메시지 큐에서 WM_PAINT 메시지를 제거하지 않습니다.
:::&lt;/p&gt;
&lt;h2&gt;PeekMessage&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;HWND hwnd; 
BOOL fDone; 
MSG msg; 

 
fDone = FALSE; 
while (!fDone) 
{ 
    fDone = DoLengthyOperation();
 
    while (PeekMessage(&amp;amp;msg, hwnd,  0, 0, PM_REMOVE)) 
    { 
        switch(msg.message) 
        { 
            case WM_LBUTTONDOWN: 
                break;
            case WM_RBUTTONDOWN: 
                break;
            case WM_KEYDOWN: 
                fDone = TRUE; 
                break;
        } 
    } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PeekMessage 함수 선언은 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BOOL PeekMessageA(
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax,
    UINT  wRemoveMsg
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GetMessage와 비슷하지만 마지막 매개변수로 메시지를 처리할 방법에 대한 인자가 들어갑니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PM_NOREMOVE&lt;/td&gt;
&lt;td&gt;PeekMessage 함수를 처리한 후 큐에서 메시지가 제거되지 않습니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PM_REMOVE&lt;/td&gt;
&lt;td&gt;PeekMessage 함수를 처리한 후 큐에서 메시지가 제거됩니다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이외에도 PM_QS_...과 같은 플래그가 있습니다.&lt;/p&gt;
&lt;p&gt;이 함수는 메시지를 사용 불가하면 0, 사용 가능하면 0이 아닌 값을 반환합니다.&lt;/p&gt;
</content:encoded></item><item><title>윈도우 메시지 루프 논블로킹 메시지 처리</title><link>https://blog.ushiohayase.com/posts/winapi-2/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/winapi-2/</guid><description>Window API의 GetMessage와 PeekMessage를 애니메이션 동작을 이용하여 비교</description><pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;목표&lt;/h1&gt;
&lt;p&gt;Windows 메시지 루프는 게임 개발에서 매우 중요한 개념입니다.
이 글에서는 GetMessage()와 PeekMessage()의 차이를 비교하고, 이를 활용해 간단한 애니메이션을 구현할 것입니다.&lt;/p&gt;
&lt;h1&gt;비교할 코드 스니펫&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;GetMessage를 활용한 코드드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;while (true)
{
    while (GetMessage(&amp;amp;Message, NULL, 0, 0))
    {
        TranslateMessage(&amp;amp;Message);
        DispatchMessage(&amp;amp;Message);
    }
    UpdateGameLogic(hWnd);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;PeekMessage를 활용한 코드드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;while (true)
{
    while (PeekMessage(&amp;amp;Message, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&amp;amp;Message);
        DispatchMessage(&amp;amp;Message);
    }
    UpdateGameLogic(hWnd);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;실행 결과&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;./Blocking.png&quot; alt=&quot;GetMessage&quot; /&gt;&lt;/p&gt;
&lt;p&gt;GetMessage를 활용한 코드는 첫 위치에서 더 이상 움직이지 않는걸 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./NonblockingFront.png&quot; alt=&quot;PeekMessage1&quot; /&gt;
&lt;img src=&quot;./NonblockingBack.png&quot; alt=&quot;PeekMessage2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;반면, PeekMessage를 활용한 코드는 원이 좌우로 움직이는 걸 볼 수 있습니다.&lt;/p&gt;
&lt;h1&gt;원리&lt;/h1&gt;
&lt;p&gt;GetMessage 함수는 WM_QUIT 메시지를 만날 때까지 반환하지 않지만 PeekMessage 함수는 즉시 반환되기 때문입니다.&lt;/p&gt;
&lt;p&gt;프로젝트 깃헙
::github{repo=&quot;Ushio-Hayase/WinAPI-MessageRoop&quot;}&lt;/p&gt;
</content:encoded></item><item><title>C++에서 배열 포인터와 포인터 배열</title><link>https://blog.ushiohayase.com/posts/c%EC%97%90%EC%84%9C-%EB%B0%B0%EC%97%B4-%ED%8F%AC%EC%9D%B8%ED%84%B0%EC%99%80-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EB%B0%B0%EC%97%B4/</link><guid isPermaLink="true">https://blog.ushiohayase.com/posts/c%EC%97%90%EC%84%9C-%EB%B0%B0%EC%97%B4-%ED%8F%AC%EC%9D%B8%ED%84%B0%EC%99%80-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EB%B0%B0%EC%97%B4/</guid><description>배열 포인터와 포인터 배열간의 비교와 다차원 배열에서의 사용법</description><pubDate>Sat, 22 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;배열 포인터와 포인터 배열&lt;/h1&gt;
&lt;p&gt;배열 포인터(pointer to an array)와 포인터 배열(array of pointers)은 다릅니다.
&lt;code&gt;배열 포인터&lt;/code&gt;는 배열을 가르키는 포인터를 말하는 것이고
&lt;code&gt;포인터 배열&lt;/code&gt;은 포인터로 된 배열을 말하는 것입니다.
이 차이는 다차원 동적 배열을 할당할 때 두드러집니다.&lt;/p&gt;
&lt;p&gt;배열 포인터로 2차원 배열을 동적 할당하는 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int (*arr)[열의 수] = new int[행의 수][열의 수];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 경우에는 2차원 배열이 메모리에 연속적으로 저장됩니다.
하지만 이 방법으로 할당할 시에는 열의 수가 &lt;code&gt;컴파일 시간 상수&lt;/code&gt;여야 할 조건을 가집니다.
완전히 동적으로 할당하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;포인터 배열을 2차원 동적 할당하는 법은 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; int** arr = new int*[행의 수];
 for (int i = 0; i &amp;lt; 행의 수; ++i)
 {
	 arr[i] = new int[열의 수];
 }
 
 for (int i = 0; i &amp;lt; 행의 수; ++i)
 {
	 delete[] arr[i];
 }
 delete[] arr;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방법으로 할당할 시에는 행과 열의 길이 모두 런타임에 정할 수 있지만 메모리에 연속적으로 배치되지 않습니다.&lt;/p&gt;
&lt;p&gt;메모리를 더 사용하여 메모리에 연속되어 배치되어있는 2차원 배열을 사용하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int rows = 3;
int cols = 4;
int **arr = new int*[rows];
int *data = new int[rows * cols];

for (int i = 0; i &amp;lt; rows; i++) {
    arr[i] = data + i * cols;
}

delete[] data;
delete[] arr;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방법에서 arr 변수를 사용해 일반적인 2차원 배열처럼 사용이 가능합니다.
위 방법을 사용하면 데이터가 들어있는 배열이 연속되어져 배치되어 있어 캐시 메모리를 더 효율적으로 사용할 수 있습니다.&lt;/p&gt;
</content:encoded></item></channel></rss>