비트 연산과 저수준 프로그래밍 — 하드웨어를 직접 제어하다
C언어가 임베디드, 운영체제, 디바이스 드라이버 분야에서 절대적인 위치를 차지하는 이유가 바로 여기에 있습니다. 메모리 안의 0과 1(비트)을 직접 켜고 끄며 하드웨어 레지스터와 소통하는 저수준(Low-Level) 제어 기법을 배웁니다.
학습 목표
<<, >>) 연산이 2를 곱하거나 나누는 효과가 있음을 이해한다.volatile 키워드의 역할을 이해한다.비트, 2진수, 그리고 16진수
컴퓨터의 가장 작은 데이터 단위는 0(OFF)과 1(ON)로 이루어진 비트(Bit)입니다. 8개의 비트가 모여 1바이트(Byte)를 이룹니다.
C언어에서는 긴 2진수(ex: 1111 1111)를 짧게 표기하기 위해 16진수를 자주 사용합니다. 4개의 비트는 정확히 16진수 한 자리(0~F)와 매칭됩니다. 따라서 8비트 데이터는 항상 0xFF 처럼 두 자리 16진수로 표기할 수 있습니다.
C언어에서 16진수를 나타낼 때는 숫자 앞에 0x를 붙입니다. (예: 십진수 15 = 2진수 1111 = 16진수 0x0F)
비트 논리 연산자 (&, |, ^, ~)
조건문에서 쓰던 논리 연산자(&&, ||)와 다르게, 비트 단위 연산자는 기호를 하나만 씁니다. 같은 위치에 있는 비트끼리 논리 연산을 수행합니다.
- AND (&) : 둘 다 1일 때만 1. 특정 비트가 켜져 있는지 확인할 때 사용.
- OR (|) : 하나라도 1이면 1. 특정 비트를 강제로 켤(ON) 때 사용.
- XOR (^) : 서로 다르면 1. 특정 비트를 반전(Toggle)시킬 때 사용.
- NOT (~) : 0은 1로, 1은 0으로 전체 반전.
시프트 연산자 (<<, >>)
비트를 통째로 왼쪽이나 오른쪽으로 이동시킵니다. 밀려난 비트는 버려지고 빈자리는 0으로 채워집니다.
1을 왼쪽으로 n칸 밀면 1 << n 이 되며, 이는 우리가 원하는 'n번째 위치의 비트만 1인 마스크(Mask)'를 만들어내는 매우 중요한 기법입니다.
// 1을 왼쪽으로 0칸 이동 (0번째 비트) int val = 1 << 0; // 0000 0001 (10진수: 1) // 1을 왼쪽으로 3칸 이동 (3번째 비트) int val2 = 1 << 3; // 0000 1000 (10진수: 8)
반드시 외워야 할 비트 제어 4대 공식
임베디드나 시스템 제어에서 하드웨어 레지스터를 다룰 때 숨 쉬듯이 사용하는 4가지 기법입니다. (n번째 비트 제어)
- 비트 ON
value |= (1 << n);// n번 비트를 1로 설정 - 비트 OFF
value &= ~(1 << n);// n번 비트만 0으로 지움 - 비트 Toggle
value ^= (1 << n);// n번 비트 반전 (0->1, 1->0) - 비트 확인
if(value & (1 << n))// n번 비트가 켜져있는지 검사
플래그(Flag)와 하드웨어 제어
왜 이렇게 귀찮게 비트 연산을 할까요? 하드웨어 칩 내부에는 모터, 센서, LED 등의 상태를 기록하는 레지스터(Register)라는 특수 메모리가 있습니다. 이 레지스터는 메모리 낭비를 줄이기 위해 1바이트(8비트) 속에 8개의 서로 다른 장치 ON/OFF 스위치를 압축해 놓습니다. 이것이 바로 비트 플래그(Flag)입니다.
하드웨어 레지스터의 값은 소프트웨어와 무관하게 센서 값 변화 등으로 외부에서 값이 변할 수 있습니다. 컴파일러가 이를 모르고 "값이 안 변했으니 캐싱(최적화)해야지" 해버리면 오작동이 납니다. 따라서 레지스터를 다루는 포인터에는 반드시 volatile unsigned int *reg 처럼 volatile(휘발성의, 변덕스러운) 키워드를 붙여 최적화를 금지해야 합니다.
STEP 18 최종 프로젝트
8비트 장치 상태 관리 시스템 구축
4개의 정수형 변수(16바이트)를 사용하는 대신, 단 하나의 unsigned char 변수(1바이트)로 전원, LED, 모터, 센서의 4가지 ON/OFF 상태를 모두 관리하는 프로그램을 작성하세요.
- [1]
#define POWER (1 << 0)처럼 비트 마스크 매크로를 정의합니다. - [2] 사용자 입력에 따라
|=를 이용해 해당 장치를 ON시킵니다. - [3]
&= ~를 이용해 특정 장치만 OFF시킵니다. - [4]
if (status & POWER)처럼 비트 마스킹으로 상태를 읽어 화면에 텍스트(ON/OFF)로 출력합니다.
STEP 18 완료 체크리스트
지금까지는 메모리 상에서 데이터의 논리적인 덧셈 뺄셈만 했다면, 이 비트 연산을 배우는 순간부터 여러분의 코드는 실제 모터를 돌리고 LED를 켜며 센서값을 읽어들이는 물리적인 영역과 맞닿게 됩니다. 이제 C언어의 고급 문법과 메모리 구조(Stack, Heap)를 다루는 STEP 19. C언어 고급 문법과 메모리 구조로 넘어가 시스템 아키텍처의 큰 그림을 완성해보겠습니다!