이번 프로젝트에서는 Spring AI의 RAG 공식 문서를 대상으로 로컬 RAG 파이프라인을 구축하고, 검색 방법을 변경하면서 검색 성능이 어떻게 달라지는지 실험했다. 단순히 RAG를 구현하는 것에서 끝내지 않고, 문서 구조에 맞는 Chunking Dense Retrieval 기반 Baseline BM25 + Dense Retrieval Hybrid Search RRF k 값 변화 Embedding 모델 및 입력 방식 비교 Offline 실행 검증 검색 실패 가능성에 대한 진단 까지 단계적으로 확인했다. 이번 실험에서 사용한 Spring AI 공식 문서는 RAG의 기본 구조뿐 아니라 QuestionAnswerAdvisor, RetrievalAugmentationAdvisor, VectorStoreDocumentRetriever, RewriteQueryTransformer, MultiQueryExpander 등의 RAG 구성 요소를 계층적으로 설명하고 있어 실험용 기술 문서로 활용하기 적합했다. 프로젝트 목표 RAG(Retrieval-Augmented Generation)는 LLM이 질문에 대한 답변을 생성하기 전에 외부 문서에서 관련 정보를 검색하고, 검색된 내용을 LLM의 입력으로 활용하는 방식이다. 이번 프로젝트에서는 다음과 같은 로컬 RAG 구조를 구현했다. Spring AI 공식 문서 ↓ 문서 전처리 / Chunking ↓ BGE-M3 Embedding ↓ Qdrant Vector DB ↓ Dense Retrieval ↓ 검색 결과 ↓ Qwen2.5-7B-Instruct ↓ 최종 답변 또한 검색 성능을 정량적으로 비교하기 위해 Hit@3를 평가 지표로 사용했다. Hit@3는 질문에 대한 정답 문서가 검색 결과 상위 3개 안에 포함되었는지를 측정하는 지표이다. 문서 선정과 Chunking 2-1. 문서 선정 이번 실험에서는 Spring AI의 공식 RAG 문서를 사용했다. 문서는 다음과 같이 여러 단계의 제목 구조를 가지고 있다. Retrieval Augmented Generation ├─ Advisors │ ├─ QuestionAnswerAdvisor │ └─ RetrievalAugmentationAdvisor ├─ Naive RAG ├─ Advanced RAG ├─ Pre-Retrieval │ ├─ Query Transformation │ ├─ Query Expansion │ └─ ... ├─ Retrieval │ └─ Document Search ├─ Post-Retrieval └─ Generation 따라서 단순히 일정한 글자 수로 문서를 나누기보다는 문서의 Markdown 제목 구조를 유지하는 방식을 선택했다. 2-2. Chunking 전략 MarkdownHeaderTextSplitter를 사용하여 #, ##, ### 헤더를 기준으로 문서를 분할했다. headers_to_split_on = [ ("#", "문서"), ("##", "주제"), ("###", "세부주제") ] 이 방식의 장점은 각 Chunk가 어떤 주제에 속하는지를 Metadata로 유지할 수 있다는 것이다. 예를 들어, 주제 = Retrieval 세부주제 = VectorStoreDocumentRetriever 와 같이 문서의 계층 정보를 함께 보존할 수 있다. RAG에서는 검색된 Chunk 자체의 내용뿐 아니라 해당 내용이 문서의 어떤 영역에서 나온 것인지도 중요하기 때문에 문서 구조를 보존하는 방향으로 Chunking을 구성했다. 평가 질문 설계 검색 성능을 정량적으로 비교하기 위해 총 15개의 평가 질문을 구성했다. 질문은 크게 두 종류로 나누었다. Semantic Question 문서의 내용을 이해해야 답할 수 있는 질문이다. 예: Advanced RAG에서는 어떤 접근 방법을 사용하는가? Identifier Question 특정 기술 구성요소나 클래스명을 직접 찾는 질문이다. 예: QuestionAnswerAdvisor는 어떤 역할을 하는가? Identifier 질문에는 다음과 같은 기술 용어를 포함했다. QuestionAnswerAdvisor RetrievalAugmentationAdvisor VectorStoreDocumentRetriever RewriteQueryTransformer 이렇게 질문 유형을 구분한 이유는 단순한 의미 검색뿐 아니라 정확한 기술 식별자 검색 능력도 함께 평가하기 위해서이다. Baseline RAG 구축 Baseline에서는 BGE-M3 + Qdrant 기반 Dense Retrieval을 사용했다. Question ↓ BGE-M3 Embedding ↓ Qdrant Vector Search ↓ Top-3 Documents 검색된 결과 중 정답 Chunk가 Top-3 안에 포함되면 Hit로 판단했다. Baseline 결과 Hit@3 = 86.7% 15개의 평가 질문 중 정답 문서가 상위 3개 안에 포함되는 비율을 측정한 결과이다. 따라서 이후 모든 개선 실험에서는 이 값을 기준선으로 사용했다. 개선 실험 1 — Hybrid Search Dense Retrieval만 사용하는 경우 의미적으로 유사한 문서를 잘 찾을 수 있지만, 기술 문서에서는 클래스명이나 메서드명과 같은 정확한 문자열 검색도 중요하다. 이를 보완하기 위해 BM25를 추가했다. Question ├── Dense Retrieval │ └── BGE-M3 │ └── Lexical Retrieval └── BM25 ↓ RRF Fusion ↓ Top-K Dense Retrieval은 의미적 유사도를 활용하고, BM25는 단어의 등장 여부와 빈도를 활용한다. 두 검색 결과를 RRF(Reciprocal Rank Fusion) 방식으로 결합했다. 결과 실험 Hit@3 Baseline 대비 Baseline 86.7% +0.0%p Hybrid Search 86.7% +0.0%p Hybrid Search를 적용했지만 Hit@3에는 변화가 없었다. 개선 실험 2 — RRF k 값 비교 Hybrid Search에서는 Dense Retrieval과 BM25의 검색 결과를 RRF로 결합했다. RRF의 k 값을 변경하면서 결과가 달라지는지도 확인했다. RRF k Hit@3 Baseline 대비 10 86.7% +0.0%p 60 86.7% +0.0%p 100 86.7% +0.0%p 200 86.7% +0.0%p 모든 설정에서 동일한 결과가 나타났다. 따라서 이번 평가 데이터에서는 특정 RRF k 값이 다른 설정에 비해 더 좋은 결과를 만들었다고 판단할 수 없었다. Embedding 비교 — E5 Prefix 실험 추가로 BGE-M3와 다른 Embedding 모델인 E5를 사용하여 검색 결과를 비교했다. E5 계열 모델은 검색 상황에 따라 query와 passage에 구분된 입력 형식을 사용하는 방식이 일반적으로 사용된다. 이번 실험에서는 prefix 적용 여부를 비교했다. 실험 Hit@3 Baseline 대비 BGE-M3 86.7% +0.0%p E5 prefix 80.0% -6.7%p E5 no-prefix 86.7% +0.0%p E5에 prefix를 적용했을 때는 80.0%로 성능이 낮아졌지만, prefix를 제거하자 86.7%로 다시 증가했다. 이번 데이터에서는 E5 no-prefix가 BGE-M3와 동일한 Hit@3를 기록했다. 이를 통해 Embedding 모델을 변경할 때는 모델 자체뿐 아니라 실제 문서와 질문에 맞는 입력 방식도 함께 검증해야 한다는 점을 확인했다. Silent Failure 진단 검색 성능이 낮은 경우 단순히 모델의 성능 문제라고 판단하기 전에 문서 자체의 특성도 확인할 필요가 있다. 특히 긴 Section이 Embedding 모델의 입력 길이 제한 때문에 제대로 표현되지 않을 가능성을 확인했다. 긴 Section을 별도로 검색한 결과는 다음과 같았다. 긴 Section 검색 순위 : 1 Top-3 포함 여부 : True 이번 실험에서는 긴 Section이 검색 결과 1위에 위치했기 때문에 문서가 길다는 이유만으로 검색 실패가 발생했다고 보기는 어려웠다. 이와 같은 진단을 통해 검색 성능을 평가할 때 단순한 최종 점수뿐 아니라 실제 검색 결과와 문서 특성도 함께 확인할 필요가 있음을 확인했다. Offline 실행 검증 이번 프로젝트의 RAG 파이프라인은 외부 API에 의존하지 않고 로컬 모델을 사용하는 것을 목표로 했다. Embedding과 LLM을 로컬에서 실행한 후 Offline 환경을 가정하여 다음 설정으로 검증했다. import os os.environ["HF_HUB_OFFLINE"] = "1" os.environ["TRANSFORMERS_OFFLINE"] = "1" Offline 환경에서는 Hugging Face Hub에 접근하지 않고 로컬에 다운로드된 모델을 사용하도록 설정했다. 이를 통해 모델 다운로드가 필요한 환경과 실제 RAG 실행 환경을 분리할 수 있었다. 최종 결과 분석 전체 실험 결과를 정리하면 다음과 같다. 구분 변경 내용 Hit@3 Baseline 대비 Baseline BGE-M3 Dense Retrieval 86.7% +0.0%p 개선 1 Dense + BM25 Hybrid / RRF k=60 86.7% +0.0%p RRF 실험 Hybrid + RRF k=10 86.7% +0.0%p RRF 실험 Hybrid + RRF k=60 86.7% +0.0%p RRF 실험 Hybrid + RRF k=100 86.7% +0.0%p RRF 실험 Hybrid + RRF k=200 86.7% +0.0%p 추가 실험 E5 prefix 80.0% -6.7%p 추가 실험 E5 no-prefix 86.7% +0.0%p 이번 실험에서는 개선 기법을 적용했음에도 Baseline보다 높은 Hit@3를 얻지는 못했다. 특히 Hybrid Search와 RRF k 변경 실험에서 모두 동일한 86.7%가 나타났다. 이번 평가 질문은 Spring AI RAG 문서의 섹션명이나 기술 용어와 비교적 직접적으로 연결되어 있기 때문에 BGE-M3 Dense Retrieval만으로도 대부분의 정답 문서를 Top-3 안에서 찾을 수 있었던 것으로 해석할 수 있다. 따라서 BM25를 추가하거나 RRF의 k 값을 변경했지만 최종적인 Top-3 정답 포함 여부에는 변화가 발생하지 않았다. 또한 E5 prefix 실험에서는 오히려 Hit@3가 80.0%로 낮아졌다. prefix를 제거한 경우에는 86.7%로 회복되었다. 이번 결과를 통해 검색 시스템의 복잡도를 높이는 것이 항상 검색 성능 향상으로 이어지는 것은 아니라는 점을 확인할 수 있었다. Reflection 이번 프로젝트를 통해 RAG의 전체 구조를 직접 구현하면서 문서 전처리부터 검색, Vector DB, LLM 생성까지의 연결 과정을 확인할 수 있었다. 특히 Chunking 단계에서 문서의 내용을 단순히 일정한 길이로 나누는 것보다 원본 문서의 구조와 의미 단위를 고려하는 것이 중요하다는 점을 확인했다. 또한 Dense Retrieval에 BM25를 추가한 Hybrid Search가 항상 성능을 향상시키는 것은 아니었다. 이번 데이터에서는 Baseline의 검색 성능이 이미 높았기 때문에 Hybrid Search와 RRF k 변경이 최종 Hit@3에 영향을 주지 않았다. 반대로 E5 prefix 실험에서는 검색 성능이 낮아지는 결과도 확인했다. 이를 통해 RAG 시스템을 개선할 때는 새로운 기법을 무조건 추가하기보다 현재 문서의 특성, 질문 유형, 검색 실패 사례를 먼저 분석하고 그에 맞는 개선 방법을 선택해야 한다는 점을 배웠다. 또한 성능이 향상되지 않은 실험도 결과 자체를 기록하고 원인을 분석하면 의미 있는 실험이 될 수 있다는 점을 확인했다. 프로젝트 요약 이번 프로젝트에서는 Spring AI 공식 RAG 문서를 대상으로 로컬 RAG 시스템을 구축하고 검색 성능을 비교했다. 사용 기술 Document: Spring AI Retrieval Augmented Generation 공식 문서 Embedding: BGE-M3 Vector DB: Qdrant LLM: Qwen2.5-7B-Instruct Retrieval: Dense Retrieval Hybrid Search: BM25 + Dense Retrieval Fusion: RRF Evaluation: Hit@3 Offline Verification: Hugging Face Offline Mode 주요 결과 Baseline BGE-M3 86.7% Hybrid Search 86.7% RRF k=10 86.7% RRF k=60 86.7% RRF k=100 86.7% RRF k=200 86.7% E5 prefix 80.0% E5 no-prefix 86.7% 이번 프로젝트의 핵심은 단순히 높은 검색 점수를 만드는 것이 아니라, 동일한 평가 기준에서 여러 RAG 검색 전략을 실험하고 그 결과를 분석하는 과정에 있었다. 특히 기존의 Java/Spring 개발 경험과 연결되는 Spring AI 문서를 직접 RAG 대상으로 활용하면서, 기존 백엔드 개발 경험을 RAG 및 LLM 애플리케이션 개발 학습으로 확장해 보는 것을 목표로 했다.
해외선물 플랫폼을 만들면서 제일 처음 틀렸던 게 거래시간이었습니다. "나스닥 선물은 월요일 아침 7시에 열린다"를 그대로 코드에 넣었더니, 11월 첫 주가 지나자 개장 시각이 한 시간씩 어긋났습니다. 미국 서머타임이 끝났기 때문입니다. 이 글은 해외선물 거래시간을 코드로 다룰 때 지켜야 할 원칙과, 실제로 쓰는 판정 함수를 정리한 것입니다. 결론부터: 한국시간으로 저장하지 않는다 거래시간은 거래소 현지 시간 + 시간대 이름 으로 저장하고, 화면에 보여 줄 때만 한국시간으로 바꿉니다. 나쁜 예: open_kst = "07:00" 좋은 예: tz = "America/Chicago" , open = "17:00" 한국은 서머타임이 없어서 KST로 저장하면 1년에 두 번 전부 틀어집니다. 거래소 규정 자체가 현지 시각 기준으로 쓰여 있으니, 규정 그대로 저장하는 것이 가장 덜 틀립니다. CME 해외선물 거래시간 (나스닥·S&P·금·원유) 나스닥100, S&P500, 금, 원유 같은 CME 계열 주요 선물은 거의 같은 틀을 씁니다. CME 공식 기준은 시카고 시간(CT)입니다. 개장: 일요일 17:00 CT 매일 휴식: 월 목 16:00 17:00 CT (1시간) 마감: 금요일 16:00 CT 토요일: 종일 휴장 이걸 한국시간으로 바꾸면 계절에 따라 달라집니다. 구분 미국 서머타임 기간 미국 표준시 기간 주간 개장 월 07:00 월 08:00 매일 휴식 06:00~07:00 07:00~08:00 주간 마감 토 06:00 토 07:00 그래서 "해외선물 거래시간 주말"을 물으면 답은 토요일 새벽 마감부터 월요일 아침 개장까지 휴장 입니다. 2026년 미국 서머타임은 3월 8일에 시작해서 11월 1일에 끝납니다. 판정 함수 현재 시각을 받아서 개장, 휴식, 휴장 중 하나를 돌려주는 함수입니다. 핵심은 먼저 거래소 시간대로 바꾼 다음 판단한다 는 것 하나입니다. 서머타임 계산은 표준 라이브러리 zoneinfo 가 알아서 합니다. from datetime import datetime, time, timezone from zoneinfo import ZoneInfo CT = ZoneInfo("America/Chicago") def cme_status(now_utc: datetime) -> str: t = now_utc.astimezone(CT) # 거래소 현지 시각으로 변환 wd, hm = t.weekday(), t.time() # 월=0 ... 일=6 if wd == 5: # 토요일 종일 휴장 return "closed" if wd == 6: # 일요일은 17:00 CT 부터 개장 return "open" if hm >= time(17) else "closed" if wd == 4 and hm >= time(16): # 금요일 16:00 CT 주간 마감 return "closed" if time(16) <= hm < time(17): # 월~목 매일 1시간 휴식 return "break" return "open" print(cme_status(datetime.now(timezone.utc))) 한국시간으로 개장 시각을 보여 주고 싶으면 반대로 변환만 하면 됩니다. from datetime import datetime from zoneinfo import ZoneInfo open_ct = datetime(2026, 11, 8, 17, 0, tzinfo=ZoneInfo("America/Chicago")) print(open_ct.astimezone(ZoneInfo("Asia/Seoul"))) # 2026-11-09 08:00 (월) 서버 시계는 UTC로 두고, 입력은 항상 UTC로 받는 것을 추천합니다. 서버 시간대가 섞이면 같은 코드가 서버마다 다르게 동작합니다. 함정: 미국과 유럽의 서머타임은 날짜가 다르다 여러 거래소 종목을 같이 다루면 이 부분에서 한 번 더 틀립니다. 미국과 유럽은 서머타임 시작일과 종료일이 다릅니다. 미국: 3월 둘째 일요일 ~ 11월 첫째 일요일 (2026년 3/8 ~ 11/1) 유럽: 3월 마지막 일요일 ~ 10월 마지막 일요일 (2026년 3/29 ~ 10/25) 그래서 2026년 10월 25일부터 11월 1일까지 한 주 동안은 유럽만 표준시로 돌아간 상태 입니다. 이 기간에 독일 DAX 현물장 개장(09:00 현지)은 한국시간 16:00에서 17:00으로 바뀌지만, CME 개장은 아직 07:00 그대로입니다. 봄에도 3월 8일부터 29일까지 3주 동안 비슷하게 어긋납니다. 두 거래소의 시차를 "항상 몇 시간"으로 박아 두면 이 기간에만 틀리고, 1년에 몇 주뿐이라 테스트에서 잘 안 잡힙니다. 종목마다 자기 시간대를 들고 있으면 이 문제는 저절로 사라집니다. 서머타임이 없는 거래소도 섞인다 홍콩 항셍 선물처럼 서머타임이 없는 거래소도 있습니다. 항셍 선물 주간 세션은 현지 기준 09:15 12:00, 13:00 16:30이고 점심 휴식이 있습니다. 홍콩은 한국보다 1시간 늦으니 한국시간으로는 10:15 13:00, 14:00 17:30입니다. 야간 세션도 따로 있습니다. 즉 세션은 "하루 한 구간"이 아니라 여러 구간의 목록 으로 저장해야 합니다. SESSIONS = { "NQ": {"tz": "America/Chicago", "rule": "cme_globex"}, "HSI": {"tz": "Asia/Hong_Kong", "day": [("09:15", "12:00"), ("13:00", "16:30")], "night": [("17:15", "03:00")]}, # 자정을 넘는 구간 } 자정을 넘는 구간(17:15~03:00처럼 끝이 시작보다 작은 경우)은 "시작 이후이거나 끝 이전"으로 판정해야 합니다. 이걸 빼먹으면 야간 세션 절반이 휴장으로 나옵니다. 휴장일과 조기 마감은 별도 테이블 미국 공휴일에는 휴장하거나 정오에 조기 마감합니다. 추수감사절 다음 날처럼 짧게 열리는 날도 있습니다. 이건 규칙으로 계산하지 말고 거래소가 매년 공지하는 휴장 일정을 테이블로 넣는 편이 안전합니다. CREATE TABLE market_holidays ( exchange VARCHAR(16) NOT NULL, day DATE NOT NULL, -- 거래소 현지 날짜 kind VARCHAR(16) NOT NULL, -- 'closed' | 'early_close' close_at TIME NULL, -- 조기 마감 시각(현지) PRIMARY KEY (exchange, day) ); 판정 함수는 세션 규칙보다 이 테이블을 먼저 봅니다. 관리자 화면에서 운영자가 직접 추가할 수 있게 해 두면 연말에 개발자를 부를 일이 없습니다. 화면 표시: 휴장인지 피드 장애인지 구분 마지막으로, 시세가 멈췄을 때 그게 휴장인지 시세 장애인지 구분해 줘야 합니다. 세션 판정 결과와 마지막 시세 수신 시각을 같이 봅니다. 세션이 closed 또는 break : "장 마감" 또는 "휴식 중" 표시, 주문 차단 세션이 open 인데 마지막 시세가 몇 분 이상 없음: "시세 지연" 표시 세션이 open 이고 시세가 들어옴: 정상 이 구분이 없으면 휴식 시간마다 "왜 가격이 안 움직이냐"는 문의를 받게 됩니다. 정리 거래시간은 거래소 현지 시각 + 시간대 이름으로 저장하고, KST 변환은 화면에서만 합니다. CME 주요 선물은 일요일 17:00 CT 개장, 평일 16:00~17:00 CT 휴식, 금요일 16:00 CT 마감입니다. 한국시간으로는 서머타임 기간 월 07:00, 표준시 기간 월 08:00 개장입니다. 미국과 유럽의 서머타임 날짜가 달라서 봄 3주, 가을 1주는 두 거래소 시차가 바뀝니다. 세션은 여러 구간 목록으로, 휴장일은 별도 테이블로 관리합니다. 휴장과 시세 장애를 화면에서 구분합니다.
Top 10 Platforms for Discovering Old GitHub Accounts An Meta description: Explore GitHub history and developer platforms to build digital skills. ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/product/buy-old-gmail-accounts/ Interest in old GitHub accounts can also lead to valuable questions about digital history, project development, programming communities, and online learning. Public GitHub activity can show how projects evolve, how developers organize information, and how technical knowledge is documented over time. For educational purposes, the most useful approach is to explore publicly available repositories and resources while building an independent learning record. This guide introduces ten platforms and resources worth exploring when studying GitHub-related projects and developer communities. The focus is on practical applications, educational benefits, everyday digital skills, and independent learning. Readers looking for general information can also use usasmmti as a source of guidance when exploring GitHub, open-source development, version control, and related subjects. GitHub for Studying Project History GitHub is the primary place to explore public repositories, documentation, project structures, and development activity. Older public repositories can demonstrate how software projects change as developers add features and improve documentation. For learners, this provides a practical way to study version control and project organization. Reading project files and documentation can also strengthen research, observation, and technical reading skills. GitLab for Collaborative Learning GitLab provides another environment for studying repositories and collaborative development. Exploring public projects can help learners understand how teams organize digital work and record project progress. The lessons are useful beyond programming. Planning, communication, task organization, and documentation are everyday skills that can support school, work, and personal projects. ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/product/buy-old-gmail-accounts/ Bitbucket for Understanding Version Control Bitbucket can introduce learners to repository management and development workflows. Comparing its concepts with those found on GitHub helps learners understand the broader principles behind version-control systems. This can make digital learning more flexible. Understanding concepts instead of memorizing one interface makes it easier to adapt to unfamiliar software. Codeberg for Open-Source Exploration Codeberg provides access to open-source projects and community-based software development. Learners can examine project descriptions, documentation, and repository structures to understand how collaborative projects are presented. Open-source exploration can also improve communication skills. Clear instructions, organized information, and readable documentation demonstrate how technical knowledge can be shared effectively. Stack Overflow for Problem-Solving Stack Overflow can be studied as a large collection of programming questions and explanations. Learners can observe how technical problems are described and broken into manageable parts. A useful exercise is to read a question, identify the main issue, and develop an answer independently before reviewing the explanations provided by other developers. SourceForge for Software Research SourceForge provides access to software projects and related project information. Exploring different projects can help learners understand how applications are described, organized, and maintained. It can also become a research exercise. Learners can compare technologies, project purposes, documentation styles, and development approaches while improving their ability to evaluate technical information. Open-Source Documentation Websites ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/product/buy-old-gmail-accounts/ Documentation websites connected with open-source projects are useful educational resources. They often explain features, terminology, project structures, and practical procedures. Learning to read documentation is valuable in daily digital life. Many applications, websites, and technical tools depend on users being able to understand written instructions. 8. Developer Community Forums Developer communities contain discussions about programming, software development, project organization, and technical questions. Reading these discussions exposes learners to different approaches to solving problems. These communities can also teach effective communication. Learners can observe how useful questions provide context and how detailed answers organize complicated information. 9. Online Coding Education Platforms Structured coding platforms can complement GitHub research by explaining concepts through lessons and exercises. Learners can first study a concept and then look for examples of that concept in public repositories. This creates a practical learning cycle: • Study the concept. • Examine a real example. • Practice independently. • Document the result. • Review the outcome. 10. Personal GitHub Learning Projects A personal GitHub account can become an organized record of learning. Instead of depending on an established account history, learners can create repositories that document their own progress. Projects might include programming exercises, documentation, data analysis, research notes, educational materials, or small experiments. Over time, these projects can form a useful digital learning archive. ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/product/buy-old-gmail-accounts/ Case Studies and Examples of Learning Case Study 1: A Student Exploring Python A beginner interested in Python searches public GitHub repositories for simple educational projects. The student begins by reading README files rather than immediately examining every line of code. After identifying a basic programming concept, the student creates a small calculator application. The project includes documentation describing what the student learned and which concepts still require practice. The exercise develops several skills simultaneously: • Programming fundamentals • Research techniques • Technical reading • Documentation • Project organization Case Study 2: Comparing Two Public Projects A web-development student finds two public projects with similar purposes. One contains detailed documentation, while the other uses shorter explanations. The student compares their structures and writes notes about which information is easiest to understand. This transforms repository exploration into a lesson about technical communication and information design. Case Study 3: Creating a Personal Knowledge Archive A professional studying data analysis creates separate repositories for small exercises. Each repository contains the project, a short explanation, and notes about the concepts studied. After several months, the collection becomes a personal learning archive. The professional can revisit earlier work, refresh forgotten concepts, and choose appropriate subjects for future study. These examples show that the strongest educational outcome comes from turning online exploration into active practice and personal documentation. ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/product/buy-old-gmail-accounts/ Step-by-Step Guide to Exploring GitHub-Related Platforms Step 1: Choose One Learning Objective Begin with a specific goal rather than exploring every topic at once. Possible objectives include: • Learning Git fundamentals • Studying Python • Understanding web development • Improving technical writing • Exploring open-source collaboration • Learning project organization A clear goal makes online research more manageable. Step 2: Select Appropriate Platforms Choose two or three resources that match your objective. For example, use GitHub for project examples, an educational platform for structured lessons, and a developer community for discussions. Using complementary resources can provide a broader understanding without making the learning process complicated. Step 3: Find Public Projects Search for public repositories related to your chosen subject. Look for projects with understandable descriptions and documentation. Pay attention to: • Project purpose • Technologies used • File organization • Documentation quality • Development history Step 4: Read Before Practicing Start with the README and other introductory material. Identify unfamiliar terms and write down questions. Try to explain the project's purpose in your own words. This simple exercise improves comprehension. Step 5: Examine the Structure Look at how files and folders are arranged. Consider why different parts of the project may be separated. This teaches both technical concepts and information organization. Step 6: Take Structured Notes Create a simple learning record containing: New concept — What did I discover? Example — Where did I see it? Practice — How can I apply it? Question — What should I study next? Structured notes make future revision easier. Step 7: Build an Independent Exercise Choose one concept and create something original. ➥ 24 Hours Reply/Contact ➤Telegram: @itusasmm ➤Whatsapp:+1 (681) 538-1764 ➤Email : itusasmm@gmail.com ➤Visit our website:usasmmti.com ➤Product Visit Link: https://usasmmti.com/
개발자를 위한 넓고 얕은 컴퓨터 지식, 넓얕컴지 Java, Python, JavaScript처럼 메모리를 직접 관리하지 않는 언어를 사용하더라도 프로그램이 실행되는 아래쪽에서는 결국 메모리에 값을 저장하고, 주소를 따라 객체에 접근하고, 필요 없는 메모리를 회수하는 일이 반복된다. 이번 글에서는 C를 중심으로 저수준 메모리와 시스템 언어(Systems Language)의 공통 개념을 살펴본다. 고수준 언어를 사용하면 new , 객체, 배열 같은 추상화 뒤에서 메모리가 자동으로 관리된다. 하지만 메모리 누수(Memory Leak), 복사 비용, 동시성 문제, 가비지 컬렉션(Garbage Collection), 네이티브 라이브러리 연동, 성능 문제를 이해하려면 그 아래에서 데이터가 어떻게 저장되고 이동하는지 알아야 한다. 이번 글의 흐름은 값의 표현 → 주소와 포인터 → 메모리 영역 → 동적 메모리 → 사용자 정의 자료형 → 빌드 과정 → 소유권 → 동시성 모델 순서로 이어진다. 1️⃣ 값은 메모리에 어떻게 저장될까 1-1. 기본 타입(Primitive Type) ⭐️⭐️⭐️ 컴퓨터의 메모리는 결국 바이트(Byte)의 연속된 공간이다. 프로그래밍 언어의 타입은 이 바이트들을 얼마나 읽을 것인지, 어떤 의미로 해석할 것인지 결정한다. C에서는 char , int , float , double 처럼 타입마다 일반적으로 서로 다른 크기와 표현 방법을 사용한다. 예를 들어 같은 비트 패턴이라도 정수(Integer)로 읽는지, 문자(Character)로 읽는지에 따라 의미가 달라진다. Memory ┌────────┬────────┬────────┬────────┐ │ Byte │ Byte │ Byte │ Byte │ └────────┴────────┴────────┴────────┘ ↑ 어떤 타입으로 해석할 것인가? 고수준 언어에서는 이런 세부사항을 직접 다루는 일이 적지만 데이터베이스 타입, 네트워크 패킷, 파일 형식, 직렬화, GPU 메모리처럼 시스템 경계에 가까워질수록 다시 중요해진다. 1-2. 부호 있는 정수와 부호 없는 정수(Signed & Unsigned) ⭐️⭐️ 정수는 음수를 표현할 수 있는 부호 있는 정수(Signed Integer)와 0 이상의 값만 표현하는 부호 없는 정수(Unsigned Integer)로 나눌 수 있다. 같은 비트 수를 사용하면 부호 없는 정수는 음수를 표현하지 않는 대신 양수 영역을 더 넓게 사용할 수 있다. 문제는 두 타입을 섞어서 계산하거나 범위를 벗어나는 값을 다룰 때 예상하지 못한 변환이나 오버플로(Overflow)가 발생할 수 있다는 점이다. 따라서 숫자 타입을 선택할 때는 단순히 int면 충분하다 가 아니라 값의 범위, 음수 가능성, 다른 타입과의 연산, 직렬화 형식 까지 함께 고려해야 한다. 2️⃣ 포인터(Pointer) 2-1. 주소와 포인터(Address & Pointer) ⭐️⭐️⭐️ 메모리의 각 위치에는 주소(Address)가 있다. 포인터(Pointer)는 다른 데이터가 저장된 메모리 주소를 값으로 가지는 변수 다. 변수 p ┌──────────┐ │ 0x1000 │ └──────────┘ │ ▼ 주소 0x1000 ┌──────────┐ │ 42 │ └──────────┘ C에서 & 는 변수의 주소를 가져오고 * 는 포인터가 가리키는 위치의 값에 접근하는 데 사용한다. int value = 42; int *p = &value; printf("%d", *p); 여기에서 p 가 저장하는 것은 42 자체가 아니라 value 가 존재하는 주소다. *p 를 사용하면 그 주소로 이동해 실제 값을 읽는다. 포인터를 이해하면 참조(Reference), 객체 주소, 배열, 동적 메모리, 함수 포인터(Function Pointer), 네이티브 인터페이스를 이해하기 훨씬 쉬워진다. 2-2. 배열과 문자열(Array & String) ⭐️⭐️⭐️ C의 배열(Array)은 같은 타입의 값들이 메모리에 연속적으로 배치되는 구조다. int arr[4] 주소 값 1000 → [10] 1004 → [20] 1008 → [30] 1012 → [40] 따라서 배열의 시작 주소를 알고 각 요소의 크기를 알면 다음 요소의 주소를 계산할 수 있다. C에서 문자열(String) 역시 문자 배열을 기반으로 표현하며 마지막에 문자열의 끝을 나타내는 널 문자(Null Character) \0 를 사용한다. "H i ! \0" 고수준 언어에서는 문자열이 별도의 객체처럼 보이지만 저수준에서는 결국 메모리에 연속적으로 놓인 데이터와 그 데이터를 해석하는 규칙 이 중요하다. 2-3. 포인터 연산(Pointer Arithmetic) ⭐️⭐️ 포인터에 1 을 더한다고 주소 숫자가 항상 정확히 1 증가하는 것은 아니다. 포인터의 타입에 따라 해당 타입의 크기만큼 이동한다. 예를 들어 4바이트 int 를 가리키는 포인터라면 p + 1 은 다음 int 가 있는 위치를 가리킨다. p → arr[0] p + 1 → arr[1] p + 2 → arr[2] 배열 순회가 포인터와 자연스럽게 연결되는 이유다. 반대로 잘못된 주소까지 이동하면 프로그램이 소유하지 않은 메모리에 접근할 수 있다. 이런 자유로움 때문에 C는 강력하지만 메모리 안전성(Memory Safety)을 개발자가 직접 책임져야 하는 부분도 많다. 3️⃣ 프로그램의 메모리 영역 3-1. 스택과 힙(Stack & Heap) ⭐️⭐️⭐️ 프로그램의 메모리는 역할에 따라 여러 영역으로 나누어 생각할 수 있다. 대표적으로 코드 영역, 전역·정적 영역, 스택(Stack), 힙(Heap)이 있다. 스택은 주로 함수 호출 과정에서 만들어지는 지역 변수, 매개변수, 함수 실행 정보 등을 관리한다. 함수 호출과 반환에 따라 자연스럽게 생성되고 제거되므로 관리 비용이 비교적 작다. 힙은 실행 중 필요한 크기의 메모리를 동적으로 할당하는 공간이다. 객체의 크기나 생명주기를 컴파일 시점에 알기 어려운 경우 사용된다. Process Memory ┌────────────────────┐ │ Code │ ├────────────────────┤ │ Global / Static │ ├────────────────────┤ │ Heap │ │ ↓ │ │ │ │ ↑ │ │ Stack │ └────────────────────┘ 언어와 운영체제에 따라 실제 구현은 더 복잡하지만 Stack = 함수 호출과 밀접 , Heap = 동적으로 관리되는 메모리 라는 기본적인 구분은 여러 시스템을 이해할 때 반복해서 사용된다. 3-2. 전역 영역과 정적 영역(Global & Static) ⭐️⭐️ 전역 변수(Global Variable)와 정적 변수(Static Variable)는 일반적인 지역 변수보다 훨씬 긴 생명주기를 가진다. 프로그램 실행 동안 계속 존재할 수 있기 때문에 여러 코드에서 공유되는 상태를 만들기 쉽다. 공유 상태는 편리하지만 의존성 증가, 테스트 어려움, 동시성 문제, 변경 위치 추적의 어려움으로 이어질 수 있다. 따라서 static 이나 전역 상태를 볼 때는 단순히 메모리에 어디에 저장되는지만 보는 것이 아니라 누가 상태를 읽고 변경하는지, 생명주기가 얼마나 긴지 까지 함께 보는 것이 중요하다. 4️⃣ 동적 메모리 관리 4-1. malloc/free ⭐️⭐️⭐️ C에서는 힙 메모리를 직접 할당하고 해제할 수 있다. 대표적인 함수가 malloc , calloc , realloc , free 다. malloc 은 필요한 크기의 메모리를 할당하고, calloc 은 여러 요소를 위한 메모리를 할당하면서 초기화한다. realloc 은 기존에 할당된 메모리의 크기를 변경하고, free 는 더 이상 필요하지 않은 메모리를 해제한다. int *numbers = malloc(sizeof(int) * 100); /* 사용 */ free(numbers); 핵심은 malloc 보다 free 다. 메모리를 할당했다면 누가, 언제 해제할 책임이 있는가 가 반드시 결정되어야 한다. 이 질문은 이후 C++의 RAII, Rust의 소유권(Ownership), Java·Python·JavaScript의 가비지 컬렉션으로 이어진다. 4-2. 메모리 누수(Memory Leak) ⭐️⭐️⭐️ 필요한 메모리를 계속 할당하면서 더 이상 사용하지 않는 메모리를 해제하지 않으면 메모리 누수가 발생한다. malloc() ↓ 사용 ↓ 참조를 잃음 ↓ free() 불가능 ↓ Memory Leak 짧게 실행되는 프로그램에서는 눈에 띄지 않을 수 있지만 서버처럼 며칠 또는 몇 달 동안 계속 실행되는 프로세스에서는 작은 누수도 누적되어 장애로 이어질 수 있다. 가비지 컬렉션을 사용하는 언어에서도 메모리 누수는 사라지지 않는다. 사용하지 않는 객체가 다른 객체에서 계속 참조되고 있다면 가비지 컬렉터가 해당 객체를 제거할 수 없기 때문이다. 4-3. 댕글링 포인터(Dangling Pointer) ⭐️⭐️⭐️ 댕글링 포인터는 이미 생명주기가 끝난 메모리를 계속 가리키는 포인터 다. p ───▶ Object ↓ free() p ───▶ ??? 메모리를 해제했다고 포인터 변수 자체가 자동으로 사라지는 것은 아니다. 그 주소를 다시 사용하면 이미 다른 데이터가 들어갔거나 접근할 수 없는 영역일 수 있다. 메모리 누수가 사용하지 않는 메모리를 계속 가지고 있는 문제 라면 댕글링 포인터는 이미 사용할 수 없는 메모리를 다시 사용하는 문제 에 가깝다. 5️⃣ 구조체, 공용체, 열거형, 함수 포인터 5-1. 구조체(Struct) ⭐️⭐️ 구조체(Struct)는 서로 다른 타입의 값을 하나의 논리적인 데이터로 묶을 수 있다. struct User { int id; char *name; }; 고수준 언어의 객체나 데이터 클래스와 비슷하게 보이지만 구조체는 실제 메모리 배치와 훨씬 직접적으로 연결된다. 필드 사이에는 정렬(Alignment)을 맞추기 위한 패딩(Padding)이 들어갈 수 있기 때문에 선언된 필드의 크기를 단순히 더한 값과 구조체 전체 크기가 다를 수도 있다. 5-2. 공용체(Union) ⭐️ 공용체(Union)는 여러 필드가 같은 메모리 공간을 공유 한다. Union ┌─────────────┐ │ int │ │ float │ ← 같은 메모리 │ char[...] │ └─────────────┘ 한 시점에 여러 값을 동시에 별도로 저장하는 구조체와 달리 공용체는 같은 메모리를 여러 타입으로 해석할 수 있다. 메모리가 제한적인 시스템, 네트워크 프로토콜, 하드웨어와 가까운 코드에서 볼 수 있다. 5-3. 열거형(Enum) ⭐️ 열거형(Enum)은 가능한 값의 범위를 의미 있는 이름으로 제한할 때 사용한다. Status ├─ READY ├─ RUNNING └─ DONE 단순한 숫자 0 , 1 , 2 보다 코드의 의미를 명확하게 표현할 수 있으며 상태 머신(State Machine), 프로토콜 상태, 결과 타입 등을 표현할 때 자주 사용된다. 5-4. 함수 포인터(Function Pointer) ⭐️ 함수 역시 메모리의 특정 위치에 존재하는 코드이므로 함수의 주소를 저장하고 전달할 수 있다. 이를 함수 포인터(Function Pointer)라고 한다. 함수 포인터는 콜백(Callback), 이벤트 처리, 동작 교체, 플러그인 구조 같은 패턴의 저수준 기반이 된다. 고수준 언어에서 함수를 변수에 저장하거나 콜백으로 넘기는 코드 역시 추상화 수준은 다르지만 비슷한 아이디어와 연결된다. 6️⃣ 소스 코드는 어떻게 실행 파일이 될까 6-1. 전처리기, 헤더, 번역 단위(Preprocessor, Header, Translation Unit) C/C++ 코드가 바로 CPU에서 실행되는 것은 아니다. 일반적으로 소스 코드는 전처리, 컴파일, 링크 등의 과정을 거친다. Source Code ↓ Preprocessor ↓ Translation Unit ↓ Compiler ↓ Object File ↓ Linker ↓ Executable 전처리기(Preprocessor)는 #include , #define 같은 지시문을 처리한다. 헤더 파일(Header File)은 여러 소스 파일이 공유하는 선언을 제공하며, 전처리가 끝난 하나의 컴파일 단위를 번역 단위(Translation Unit)라고 한다. 각 번역 단위는 별도로 컴파일될 수 있고 최종 단계에서 링커(Linker)가 결과들을 연결해 실행 파일을 만든다. 이 과정을 이해하면 왜 헤더가 필요한가 , 컴파일 오류와 링크 오류가 왜 다른가 , 라이브러리를 연결한다는 것이 무엇인가 를 구분할 수 있다. 6-2. 정적 라이브러리와 동적 라이브러리(Static & Dynamic Library) 라이브러리를 프로그램에 포함하는 방식도 크게 정적 연결(Static Linking)과 동적 연결(Dynamic Linking)로 나눌 수 있다. 정적 연결은 필요한 라이브러리 코드를 최종 실행 파일에 포함하는 방식이고, 동적 연결은 실행 시점에 별도의 공유 라이브러리(Shared Library)를 사용하는 방식이다. Static Application + Library ↓ Executable Dynamic Application ──▶ Shared Library 정적 연결은 배포가 단순해질 수 있지만 실행 파일 크기가 커질 수 있고, 동적 연결은 여러 프로그램이 라이브러리를 공유할 수 있지만 라이브러리 버전과 실행환경의 영향을 받을 수 있다. 7️⃣ 프로그래밍 패러다임(Programming Paradigm) 프로그래밍 언어는 단순히 문법만 다른 것이 아니라 상태와 동작을 조직하는 방식 도 다르다. 대표적인 패러다임에는 절차지향(Procedural), 객체지향(Object-Oriented), 함수형(Functional), 선언형(Declarative), 논리형(Logic), 데이터 지향(Data-Oriented) 등이 있다. 절차지향은 수행할 명령의 순서를 중심으로 구성하고, 객체지향은 상태와 동작을 객체 단위로 묶으며, 함수형은 상태 변경을 줄이고 함수 조합을 강조한다. 선언형은 어떻게 수행할 것인가 보다 어떤 결과가 필요한가 를 표현하는 방식에 가깝다. 실제 애플리케이션은 하나의 패러다임만 사용하는 경우보다 여러 방식을 혼합하는 경우가 많다. JavaScript나 Python에서도 객체지향 코드와 함수형 코드가 함께 사용된다. 중요한 것은 패러다임을 서로 경쟁하는 방식으로 보는 것이 아니라 문제를 어떤 형태로 모델링하는 것이 가장 적절한지를 판단하는 도구 로 보는 것이다. 8️⃣ 소유권과 빌림(Ownership & Borrowing) Rust에서 대표적으로 사용되는 소유권(Ownership) 모델은 메모리와 자원의 생명주기를 컴파일 단계에서 관리하려는 접근이다. 하나의 값에는 해당 값을 정리할 책임을 가진 소유자(Owner)가 존재하고, 값의 소유권을 다른 곳으로 이동(Move)하거나 일정 기간 빌려서(Borrow) 사용할 수 있다. Owner A │ ├─ Borrow ──▶ Function │ └─ Move ────▶ Owner B 전통적인 C에서는 개발자가 malloc/free 의 책임을 직접 관리한다. 가비지 컬렉션 기반 언어에서는 런타임이 사용하지 않는 객체를 찾아 정리한다. Rust는 이 사이에서 컴파일 시점의 규칙으로 잘못된 메모리 사용을 줄이는 방식 을 선택한다. 소유권을 반드시 Rust 문법 수준까지 깊게 알 필요가 없더라도 서버 개발자에게 중요한 질문은 같다. 이 자원의 소유자는 누구인가? 누가 변경할 수 있는가? 언제까지 사용할 수 있는가? 누가 정리해야 하는가? 여러 실행 흐름에서 동시에 접근해도 되는가? 이 질문은 메모리뿐 아니라 파일, 데이터베이스 연결, Lock, Transaction, Socket과 같은 자원에도 그대로 적용된다. 9️⃣ 동시성 모델(Concurrency Model) 여러 작업을 동시에 처리하는 방법도 언어와 런타임에 따라 다르다. 9-1. 스레드와 공유 메모리(Thread & Shared Memory) 여러 스레드(Thread)가 같은 메모리에 접근하는 방식이다. 공유 데이터에 동시에 접근하면 경쟁 상태(Race Condition)가 발생할 수 있기 때문에 Lock, Mutex 같은 동기화 도구가 필요하다. Thread A ─┐ ├──▶ Shared State Thread B ─┘ 공유 메모리는 직접적이고 빠르지만 상태를 안전하게 관리하기 어려워질 수 있다. 9-2. 액터 모델(Actor Model) 액터(Actor)는 각자 자신의 상태를 가지고 다른 액터와 메시지를 주고받는다. 상태를 직접 공유하기보다 메시지 전달(Message Passing)을 중심으로 동시성을 구성한다. Actor A ──message──▶ Actor B 공유 상태를 줄일 수 있지만 메시지 순서, 장애 처리, 상태 분산 같은 새로운 문제를 고려해야 한다. 9-3. CSP와 채널(CSP & Channel) CSP(Communicating Sequential Processes)는 독립적인 실행 흐름이 채널(Channel)을 통해 통신하는 방식이다. Go의 Goroutine과 Channel이 대표적으로 이 모델의 영향을 받았다. Worker A ──▶ Channel ──▶ Worker B 상태를 직접 공유하는 대신 데이터 전달 경계를 명확하게 만들 수 있다. 9-4. 비동기와 코루틴(Async & Coroutine) 비동기(Async) 모델은 하나의 작업이 I/O 결과를 기다리는 동안 다른 작업을 처리할 수 있도록 한다. Node.js의 Event Loop, Python의 asyncio , Kotlin Coroutine 등은 세부 구현은 다르지만 I/O 대기 시간을 효율적으로 활용한다 는 공통 목적을 가진다. 스레드, 액터, 채널, 비동기는 서로 완전히 배타적인 모델이 아니다. 실제 시스템에서는 여러 방식을 함께 사용할 수도 있다. 🔟 핵심 개념 연결 Value Representation ↓ Address ↓ Pointer ↓ Memory Layout ↓ Stack / Heap / Global / Static ↓ malloc / free ↓ Resource Lifetime ↓ Ownership ↓ Concurrency 고수준 언어를 사용할 때도 아래 질문으로 연결해서 생각할 수 있다. 이 데이터는 메모리에 어떤 형태로 존재하는가? 이 변수는 값 자체를 가지는가, 다른 객체를 가리키는가? 이 객체는 스택과 힙 중 어디에서 어떤 생명주기로 관리되는가? 메모리나 자원의 소유자는 누구인가? 누가 자원을 정리할 책임을 가지는가? 같은 상태에 여러 실행 흐름이 동시에 접근할 수 있는가? 공유 상태 대신 메시지나 비동기 작업으로 분리할 수 있는가? 1️⃣1️⃣ 더 알아보기 RAII(Resource Acquisition Is Initialization) C++에서 자원의 생명주기를 객체의 생명주기와 연결하는 방식이다. 객체가 생성될 때 자원을 확보하고 객체가 소멸될 때 자원을 해제하도록 만들어 malloc/free , 파일, Lock 같은 자원 관리 실수를 줄일 수 있다. 스마트 포인터(Smart Pointer) C++에서는 unique_ptr , shared_ptr , weak_ptr 같은 스마트 포인터를 통해 객체 소유권을 표현할 수 있다. unique_ptr 는 단독 소유, shared_ptr 는 공유 소유, weak_ptr 는 객체의 생명주기를 연장하지 않는 참조를 표현한다. 가상 함수와 동적 디스패치(Virtual Function & Dynamic Dispatch) C++의 가상 함수(Virtual Function)는 실제 객체의 타입에 따라 실행할 메서드를 런타임에 선택한다. 내부적으로 가상 함수 테이블(vtable) 같은 구현과 연결될 수 있으며 객체지향 다형성(Polymorphism)의 저수준 구현을 이해하는 데 도움이 된다. STL 자료구조 C++ 표준 템플릿 라이브러리(STL)에는 vector , list , deque , map , unordered_map , set 등의 자료구조가 있다. 고수준 언어의 Collection을 이해할 때도 메모리 연속성, 탐색 방식, 삽입·삭제 비용을 비교하는 기준이 된다. 평가 전략과 의미론(Evaluation & Semantics) 즉시 평가와 지연 평가(Eager/Lazy Evaluation), 값에 의한 호출(Call-by-Value), 참조에 의한 호출(Call-by-Reference), 조작적 의미론(Operational S