[C/C++] 13. printf / scanf 문자 입출력
앞서 함수와 반복문의 기본 개념과 흐름 제어를 알아보았다. 이번 글에서는 콘솔 환경에서 데이터를 화면에 출력하고 사용자로부터 입력을 받는 표준 입출력 함수인 와 , 그리고 서식 지정자와 메모리 주소 전달 방식에 대해 정리한다. 프로그램 실행 시 나타나는 검은 창을 흔히 콘솔 창(Windows의 명령 프롬프트, Linux의 쉘)이라고 부른다. 콘솔은 컴퓨터와 사용자 간의 가장 기본적인 텍스트 기반 입출력 인터페이스이다. C/C++에서는 표준 라이브러리를 통해 이러한 콘솔 환경에 텍스트를 출력하거나 입력을 받아오는 함수를 기본 제공한다. - * (또는 ):* , 와 같은 함수는 직접 구현하지 않고 표준 입출력 헤더 파일을 포함(Include)하여 사용한다. 컴파일러가 번역하기 전, 전처리기(Preprocessor) 단계에서 해당 헤더를 소스 코드에 결합한다. 는 문자열을 콘솔 화면에 출력(Print Formatted)하는 함수다. 문자열 내에 특정 데이터를 원하는 위치에 끼워 넣기 위해 서식 지정자(치환 문자)를 사용한다. - * (Decimal):* 10진수 정수형 데이터를 출력할 때 사용한다. - * (Float):* 실수형 데이터를 출력할 때 사용한다. - 디버깅 도구로서의 활용: 중단점(Breakpoint)을 잡는 디버깅 외에도, 코드 중간중간 를 통해 변수의 변화나 반복문의 흐름을 콘솔 창에서 직관적으로 추적할 수 있다. 는 콘솔 창을 통해 사용자로부터 키보드 입력을 받아 지정한 변수에 저장하는 함수다. 1. 입력 대기(Blocking): 가 호출되면 프로그램은 사용자가 값을 입력하고 엔터(Enter)를 누를 때까지 실행을 멈추고 대기(무한 루프) 상태에 들어간다. 2. * (주소 연산자) 사용 이유:* 변수 이름 앞에 를 붙이는 것은 해당 변수의 메모리 시작 주소를 넘겨주겠다는 의미다. 함수 내부에서 외부 변수 의 실제 메모리 위치를 찾아가 직접 값을 덮어써야 하기 때문이다. 3. : 최신 Visual Studio 환경에서는 버퍼 오버플로우 보안 취약점을 방지하기 위해 표준 대신 안전 버전인 사용을 권장한다. 와 는 단순한 출력·입력 함수처럼 보이지만, 내부적으로는 매우 복잡한 저수준(Low-level) 메커니즘으로 동작한다. - 문자 및 문자열의 메모리 저장 구조 (ASCII 코드, 널 종료 문자 ) - 메모리 주소 체계와 포인터(Pointer)의 개념 - 스택 프레임(Stack Frame) 및 가변 인자(Variadic Arguments) 처리 방식 이러한 선행 개념들을 확실히 학습한 뒤, 추후 심화 단계에서 와 의 동작 원리를 메모리 관점에서 다시 상세히 분석할 예정이다. - 는 서식 지정자(, 등)를 통해 특정 위치에 데이터를 치환하여 콘솔에 출력한다. - 는 사용자 입력을 받을 때까지 실행을 대기하며, 입력값을 저장할 변수의 메모리 주소()를 요구한다. - 입출력 함수의 온전한 내부 동작 방식을 이해하기 위해서는 포인터와 메모리 구조에 대한 선행 학습이 필수적이다.
[C/C++] 12. 함수(1) / 반복문
C/C++ 실행 구조의 핵심인 메인 함수의 역할과, 프로그램이 지속해서 동작할 수 있도록 만들어주는 반복문(Loop)의 개념 및 사용법에 대해 알아본다. C/C++에서 함수는 프로그램의 진입점(Entry Point)이자 시작점이다. - 프로그램 실행 흐름 1. 프로그램이 시작되면 가장 먼저 함수가 호출된다. 2. 함수 내부에서 필요에 따라 다른 사용자 정의 함수(예: 등)를 호출하여 처리 결과를 받아온다. 3. 함수의 모든 코드가 실행을 마치고 반환()되면 프로그램 전체가 함께 종료된다. > ❓ 일반 프로그램은 왜 바로 종료되지 않고 계속 실행될까? > > 게임이나 일반 응용 프로그램은 사용자가 직접 끄기 전까지 계속 실행된다. 이는 함수 내부 전체를 감싸고 있는 거대한 반복문(메인 루프)이 존재하여 지속적으로 코드를 유지하기 때문이다. 특정 조건이 참(true)인 동안 코드 블록을 반복적으로 수행할 때 사용하며, 대표적으로 문과 문이 있다. 반복 횟수가 비교적 명확할 때 자주 사용하며, 초기화식, 조건식, 증감식이 한 줄에 작성되어 구조가 깔끔하다. - 동작 순서: 초기화 (최초 1회) $\rightarrow$ 조건 체크 $\rightarrow$ 코드 수행 $\rightarrow$ 증감식 수행 $\rightarrow$ 조건 체크 반복 - 문 내부에서 선언된 변수 는 해당 문 안에서만 유효한 지역 변수다. 조건식 중심의 반복문으로, 반복자 관리(초기화 및 증감)는 프로그래머가 코드 내부에서 직접 처리해 주어야 한다. 반복문 수행 도중 흐름을 변경해야 할 때 사용하는 특수 키워드다. - * - 현재 회차의 남은 아래 코드를 실행하지 않고 건너뛰어 다음 반복 회차(증감식 단계)로 넘어간다. - 예: 특정 홀수/짝수 회차만 건너뛰고 출력을 계속하고 싶을 때 사용 - * - 조건 성립 여부와 상관없이 반복문을 즉시 탈출(강제 종료)한다. - 예: 사용자의 특정 입력이나 종료 명령어(Alt+F4 등)를 감지하여 프로그램 메인 루프를 끝낼 때 사용 - 메인 함수는 프로그램의 진입점이자 시작점으로 이 함수가 종료되면 프로그램도 함께 종료된다. - 반복문인 for문과 while문을 활용하면 조건이 완료될 때까지 특정 코드 블록을 반복적으로 실행할 수 있다. - 반복문 내부에서 continue를 사용하면 현재 회차를 건너뛰고, break를 사용하면 반복문 자체를 즉시 강제 종료할 수 있다.
[C/C++] 11. 변수
프로그래밍을 작성할 때 변수를 어디에 선언하느냐에 따라 변수의 수명과 접근할 수 있는 범위가 완전히 달라진다. 이번 시간에는 지역 변수와 전역 변수의 개념, 함수와 모듈화의 중요성, 그리고 중첩 블록에서의 변수 우선순위에 대해 알아본다. 변수를 나누는 가장 명확한 기준은 바로 '함수() 내부인가, 외부인가'이다. - 지역 변수 (Local Variable) - 함수 내부나 특정 중첩 블록() 안에서 선언된 변수다. - 선언된 해당 함수 또는 블록 내부에서만 유효하며, 블록을 벗어나면 메모리에서 소멸하여 접근할 수 없다. - 지금까지 작성했던 메인 함수 내부의 변수들이나, 함수의 매개변수(인자)들이 모두 지역 변수에 해당한다. - 전역 변수 (Global Variable) - 특정 함수 외부에 선언된 변수다. - 프로그램 전체 어디서나 접근 및 수정이 가능하며, 프로그램이 종료될 때까지 메모리에 유지된다. 프로그래밍에서 함수는 단순한 코드의 집합이 아닌, 독립적인 minimum 단위의 기능을 의미한다. - 기능의 분리 - 거대한 시스템(예: 게임 캐릭터 커스터마이징, 전투 시스템 등)도 결국 잘게 쪼개진 작은 기능들의 조합으로 이루어진다. - 재사용성과 확장성 - 하나의 함수에 너무 많은 기능을 집어넣으면 다른 곳에서 해당 기능을 재가공하여 쓰기 어렵다. - 바닥부터 작은 단위의 기능을 명확히 쪼개어(모듈화) 구현해야 필요할 때 해당 모듈을 가져다 재사용할 수 있다. C/C++에서는 함수 내부에서도 중괄호()를 사용해 새로운 지역(중첩 블록)을 생성할 수 있다. - 동일 지역 변수명 중복 불가 - 같은 지역 안에서는 이름이 같은 변수를 중복 선언할 수 없다 (컴파일 에러 발생). - 중첩 블록에서의 변수 가리기 (Variable Shadowing) - 외부 지역에 존재하더라도, 내부 지역에서 동일한 이름으로 변수를 다시 선언하면 내부 지역에서는 해당 이름으로 선언된 변수가 우선순위를 가진다. - 내부 블록이 끝나는 순간, 그 안에서 선언되었던 지역 변수는 소멸하며 다시 바깥쪽 지역 변수를 참조하게 된다. - 변수는 선언된 위치에 따라 함수 내부의 지역 변수와 외부의 전역 변수로 구분되며, 이들은 함수라는 기준을 통해 명확하게 나뉜다. - 프로그래밍에서의 함수는 특정 기능을 최소 단위로 모듈화하여 관리하는 것이며, 잘게 쪼개진 모듈을 효율적으로 조합할수록 복잡한 기능을 체계적으로 구현할 수 있다. - 동일한 지역 내에서는 변수명의 중복이 불가능하나, 지역 안에 새로운 지역을 선언할 경우 내부 지역의 변수가 우선순위를 가지며 외부 변수와의 모호성을 해결한다.
[C/C++] 10. 비트 연산자(2)
지난 시간에는 비트 시프트 연산과 비트 논리 연산자, 그리고 매크로의 기초 개념을 다루었다. 이번 시간에는 비트 연산자가 실무 및 게임 프로그래밍에서 실제로 어떻게 활용되는지 핵심적인 사용처인 상태 플래그(State Flag) 관리에 대해 알아본다. 32비트 정수형 변수() 하나는 총 32개의 0 또는 1(Bit)을 가질 수 있다. 이를 이용하면 변수 하나로 최대 32가지의 서로 다른 상태를 동시에 관리할 수 있다. - 비트 마스킹의 원리 - 각 비트 자리에 고유한 의미(예: 배고픔, 목마름, 중독 등)를 부여한다. - 해당 비트가 이면 상태 활성화(ON), 이면 비활성화(OFF)를 나타낸다. - 각 상태의 값은 2의 거듭제곱($1, 2, 4, 8, 16 \dots$) 형태를 띤다. > 💡 16진수(0x) 표기법 활용 > > 상태 값을 정의할 때는 10진수보다 16진수() 를 주로 사용한다. 16진수는 비트 4개 단위로 깔끔하게 떨어지기 때문에 각 비트의 위치를 직관적으로 파악하고 관리하기 쉽다. 상태 플래그를 관리할 때 자주 쓰이는 추가, 검사, 제거의 3가지 연산 패턴은 반드시 숙지해야 한다. 특정 비트 자리를 로 만들어 상태를 온(ON)시킨다. 다른 비트 자리에는 영향을 주지 않는다. 특정 상태가 활성화되어 있는지 확인한다. 해당 비트 자리가 이면 이 아닌 참(True)의 값을 반환한다. 지우고자 하는 상태 비트만 으로 뒤집은 뒤() AND 연산을 수행하여 안전하게 상태를 제거한다. > ⚠️ XOR() 연산으로 상태를 제거하면 안 되는 이유 > > XOR 연산은 비트가 다르면 , 같으면 을 반환하므로 이미 해당 상태가 켜져 있을 땐 꺼지지만, 상태가 꺼져 있을 때 적용하면 오히려 상태가 켜져 버리는 문제가 발생한다. 따라서 상태 제거 시에는 안전하게 패턴을 사용한다. - 게임 캐릭터의 버프/디버프 관리 - 캐릭터가 현재 어떤 상태(비행, 무적, 독, 둔화 등)에 걸려 있는지 32비트 또는 64비트 변수 하나로 집약하여 빠르게 처리한다. - 복잡한 설정을 하나의 인자로 전달 - 윈도우창 스타일 설정(최소화 버튼 유무, 닫기 버튼 유무, 창 크기 조절 여부 등)처럼 수많은 설정값을 하나의 변수에 묶어서 함수에 전달할 수 있다. - 32비트 정수형 변수의 각 비트 자리를 개별적인 상태 값으로 활용하여 최대 32가지의 상태를 동시에 효율적으로 관리할 수 있다. - 비트 OR 연산으로 특정 상태를 추가하고, 비트 AND와 반전 연산을 조합하여 특정 상태를 안전하게 제거하는 패턴은 게임 개발에서 빈번하게 사용된다. - 복잡한 매개변수를 하나로 통합 전달하거나 윈도우 스타일 설정 등 여러 상태 값을 하나의 변수에 담아 효율적인 시스템 설계가 가능하다.
[C/C++] 9. define / 비트 연산자(1)
C/C++을 공부하다 보면 처음엔 다소 생소하지만, 게임 프로그래밍 및 실무에서 정말 자주 쓰이는 핵심 개념 두 가지를 만나게 된다. 바로 비트 연산자와 * 전처리기다. 자주 사용되지만 시간이 지나면 동작 방식을 잊어버리기 쉬운 개념인 만큼, 기본 원리부터 차근차근 정리해 본다. 비트 시프트 연산은 데이터의 비트(Bit)를 화살표가 가리키는 방향으로 한 칸씩 밀어주는 연산자다. - *왼쪽 시프트 ()* - 비트를 왼쪽으로 $N$칸 이동시킨다. - 2진법 특성상 자릿수가 하나씩 올라가므로, *$2^N$배 증가하는 효과를 가진다. - 예: 은 $x \times 2^3$ ($x \times 8$)과 동일 - 대입 연산자와 결합하면 변수 값에 2의 배수를 곱해 바로 재대입할 수 있다. - **오른쪽 시프트 () - 비트를 오른쪽으로 $N$칸 이동시킨다. - 오른쪽으로 밀리면서 범위를 벗어난 비트는 소실(삭제)된다. - 수학적으로는 $2^N$으로 나눈 몫을 구하는 원리와 같다. > 💡 한 줄 요약 > > 왼쪽 시프트는 $2^N$을 곱한 값, 오른쪽 시프트는 $2^N$으로 나눈 몫을 반환한다. 각 자릿수별로 비트 단위 연산을 수행한다. |연산자|이름|설명|특징| |---|---|---|---| ||**AND** (비트 곱)|두 비트가 모두 1일 때만 **1**|특정 비트를 추출하거나 가릴 때 사용| ||OR (비트 합)|두 비트 중 하나라도 1이면 1|특정 비트를 켜거나 추가할 때 사용| ||**XOR** (배타적 논리합)|두 비트의 값이 **다르면 1, 같으면 0**|비트 반전 및 상태 체크에 유용| ||NOT (비트 반전)|0은 1로, 1은 0으로 뒤집음|비트 전체 반전| 비트 연산자가 실제 코드에서 어떻게 유용하게 활용되는지 이해하기 위해, 먼저 전처리기 을 알아본다. 컴파일러가 코드를 실제 기계어로 번역하기 전, 가장 먼저 수행되는 처리 과정이다. 기호로 시작하는 명령어가 이에 해당한다. 은 코드 내 지정한 구문(심볼)을 특정 값(숫자, 문자열 등)으로 단순 치환(Replace)해 준다. 1. 코드의 가독성 향상 - 단순 숫자 대신 처럼 의미가 명확한 단어를 사용하여 코드를 읽고 이해하기 쉽게 만든다. 2. 유지보수 효율 극대화 - 설정값이나 상수가 변경될 때, 코드 전체를 수정할 필요 없이 으로 정의된 부분만 수정하면 일괄 적용된다. - 예: 자판기의 최대 음료 수량()이 변경될 경우, 상단의 설정값 하나만 변경 - 비트 시프트 연산은 $2^N$의 곱셈과 나눗셈 몫을 빠르게 처리하는 수학적 원리를 갖는다. - 비트 논리 연산자(AND, OR, XOR, NOT)의 자릿수별 처리 방식을 파악하여 상태 체크 등에 활용하자. - 매크로로 의미 있는 이름을 부여하여 코드 가독성과 유지보수성을 극대화하자.