[왕초보 임베디드-C 공부 일기] 14. 배열(Array)

임베디드 시스템에서 센서가 1초에 100개의 데이터를 쏟아낼 때, 이를 저장하기 위해 100개의 변수를 각각 선언하는 것은 불가능에 가깝다. C언어의 배열은 동일한 규격의 데이터를 메모리 상에 빈틈없이 줄 세워 관리하는 가장 원초적이고 강력한 자료구조이다.

1. 정의

배열은 동일한 자료형(Data Type)을 가진 데이터들이 메모리 공간에 연속적으로 할당된 집합이다. 일반 변수들이 메모리의 임의의 주소에 흩어져서 할당된다면, 배열은 선언된 크기만큼의 물리적 메모리 블록을 한 번에 확보한다.

간단 예시

//4바이트 크기의 uint32_t 데이터 5개를 담을 수 있는 배열 선언
//총 20바이트(4 * 5)의 연속된 메모리 공간이 확보됨

uint32_t sensor_buffer[5];
만약 sensor_buffer의 시작 주소가 0x20001000이라면, 두 번째 요소인 sensor_buffer[1]의 주소는 정확히 4바이트 뒤인 0x20001004에 물리적으로 위치하게 된다. 이러한 연속성(Contiguity) 이 배열의 가장 중요한 공학적 특성이다. --------------

2. 포인터와 배열의 절대적 관계 (Array Decay)

C언어에서 배열과 포인터는 일심동체의 관계이다. 컴파일러는 배열의 이름을 만났을 때, 이를 특별한 포인터로 취급한다.

- 배열의 이름 : 시작 주소(Base Address)

배열의 이름(sensor_buffer)은 그 배열의 첫 번째 요소가 위치한 메모리의 시작 주소(&snesor_buffer[0])를 가리키는 상수 포인터(Constant Pointer)로 분해(decay)하여 해석한다.

- 인덱싱(Indexing)의 비밀

흔히 사용하는 대괄호 인덱싱 sensor_buffer[2]는 컴파일러 내부에서 포인터 연산인 *(sensor_buffer + 2) 로 완벽하게 치환되어 기계어로 번역된다. 즉, "시작 주소에서부터 자료형의 보폭 크기만큼 2칸 이동한 뒤, 그곳의 데이터를 간접 참조(*)하여 읽어오기"라는 명령과 완전히 동일하다. ---------------------------

3. 개념의 통합

배열 선언 시 명시하는 자료형(uint8_t, uint32_t 등)은 단순히 저장될 데이터의 종류뿐만 아니라, 포인터 연산 시 오프셋(Offset)이 이동하는 물리적 바이트 단위의 보폭을 결정한다. 거대한 크기의 배열을 함수로 전달할 때, 배열 전체를 복사하지 않는다. 배열의 이름(시작 주소 포인터)만을 인자로 넘겨주어, 호출된 함수가 원본 배열 메모리에 직접 접근하도록 설계하여 오버헤드(메모리 낭비)를 없앤다. -------------------------------

4. 활용 사례

사례 A : 센서 데이터 수집용 버퍼

로버가 주행하며 일정 시간 동안 연속으로 들어오는 센서 데이터를 임시로 보관하는 메모리 공간(SRAM)으로 사용한다. 주로 포인터 변수와 결합하여 버퍼의 현재 위치를 추적한다.
// 배열 내 방의 개수 문자화
#define BUFFER_SIZE 100

uint16_t adc_buffer[BUFFER_SIZE]; // ADC 센서 값을 저장할 배열 버퍼
uint8_t current_index = 0;        // 현재 데이터가 채워질 위치

// 센서 데이터를 버퍼에 연속으로 저장하는 함수
void save_sensor_data(uint16_t new_data) {
	if (current_index < BUFFER_SIZE) {
		// 배열 인덱싱을 통해 메모리에 순차적 접근 및 저장
		adc_buffer[current_index] = new_data;
		current_index++;
	}
}

사례 B : 룩업 테이블 (Look-Up Table, LUT)과 ROM

복잡한 수학 연산(예 : 삼각함수, 로버의 모터 가속 프로파일)을 CPU가 매번 계산하게 두면 성능이 심각하게 저하된다. 정해진 결과값들을 배열로 미리 만들어두고, 인덱스를 통해 '찾아 쓰기만'하도록 만드는 기법이다. 이때 const 키워드를 배열 앞에 붙이면, 이 배열은 수정이 불가능한 상수로 취급되어 RAM이 아닌 비휘발성 플래시 메모리(ROM) 에 저장된다. (제한된 RAM 용량을 아끼는 방법이다.)
// 모터 제어를 위한 PWM 변환 테이블 (0~9단계)
// const 키워드를 통해 RAM을 소모하지 않고 내장 플래시(ROM)에 배열을 물리적으로 저장
const uint8_t pwm_lookup_table[10] = {0, 10, 25, 45, 70, 100, 135, 175, 215, 255};

// 5단계의 속도를 원할 때, 계산 없이 ROM에 저장된 배열의 5번 인덱스를 즉시 읽어옴
void set_motor_speed(uint8_t speed_level) {
	uint8_t target_pwm = pwm_lookup_table[speed_level];
	// 모터 제어 레지스터에 target_pwm 값 적용!
}