Devin.KR

자료형 크기와 정수 범위

85분 안팎

학습 목표

sizeof와 고정 폭 정수로 기록 한 건의 저장 크기를 계산합니다.

개념

샘플을 담기 전에 크기를 확인합니다

앞 모듈에서는 값을 읽는 즉시 출력했습니다. 이제 측정 시각과 원시값을 함께 기억해야 합니다. 기록 한 건의 크기를 모르면 16건을 저장할 때 필요한 공간도 판단하기 어렵습니다. 자료형의 이름만 보고 메모리를 계산하는 습관은 PC에서는 우연히 맞아도 보드에서 틀릴 수 있습니다. 이 레슨은 sizeof로 실제 크기를 관찰하고, 숫자를 좁은 자료형으로 옮기기 전에 허용 범위를 검사하는 일을 다룹니다.

이 모듈의 실습은 모두 로컬 C입니다. 각 starter를 별도 폴더에 풀고 Makefile이 있는 위치에서 make test를 실행합니다. gcc 명령을 제공하는 컴파일러, make, Python 3를 사용하며 마지막 레슨에는 LLDB도 씁니다. 컴파일 옵션은 -std=c11 -Wall -Wextra -Werror -g -O0입니다. make clean은 해당 실습의 생성 파일만 지웁니다. 테스트 파일을 고쳐 통과시키지 말고 구현 파일의 TODO를 수정합니다.

따라하기는 solution으로 확인한 실행 결과입니다. starter에서는 일부 테스트가 실패하는 것이 출발 상태입니다. 첫 세 실습은 make demo 후 ./demo로 작은 관찰 프로그램을 실행할 수 있습니다. 명령과 출력은 다른 칸이며 출력 문장을 소스에 붙이지 않습니다. 맥의 gcc는 Apple Clang일 수 있으므로 자신의 gcc --version과 CPU를 기록합니다. 여기에서 측정한 바이트 수를 모든 MCU의 값으로 옮기지 않습니다.

범위와 크기는 다른 질문입니다

Sample은 uint32_t timestamp_ms와 uint16_t raw를 담습니다. stdint.h의 고정 폭 정수형이 제공되는 구현에서는 이름에 적힌 비트 폭을 갖습니다. uint16_t에 담을 수 있는 값은 0부터 65535까지지만 프로젝트의 센서 계약은 0부터 4095까지입니다. 저장할 수 있다는 사실이 측정값으로 받아도 된다는 뜻은 아닙니다. 하한과 상한을 먼저 확인한 다음 uint16_t로 변환합니다.

raw_convert는 int raw와 uint16_t *out을 받고 성공하면 1, 거부하면 0을 반환합니다. out은 결과를 넣을 객체의 주소입니다. 지금은 NULL이면 쓸 객체가 없으므로 거부한다는 계약부터 사용합니다. 성공할 때만 *out에 변환값을 기록합니다. 거부한 경우 기존 out 값을 유지하면 호출자가 이전 값과 실패를 구분해 처리할 수 있습니다. 실패한 반환값을 무시하고 out을 새 측정값으로 쓰는 것은 호출자의 오류입니다.

음수 -1을 먼저 uint16_t로 바꾼 뒤 검사하면 원래 음수였다는 정보가 사라집니다. 이런 변환은 좁은 부호 없는 형의 범위에 맞춘 값으로 이어질 수 있어 센서 검사와 맞지 않습니다. 형 변환은 검증을 대신하지 않습니다. if (!out || raw < 0 || raw > 4095) return 0;을 쓰고, 이후 *out = (uint16_t)raw;로 옮기는 순서가 목적을 드러냅니다.

시각은 이 프로젝트에서 밀리초 단위의 uint32_t 값입니다. UINT32_MAX로 최대치를 출력해 자료형의 한계를 확인합니다. 부호 없는 시각은 끝을 넘으면 순환할 수 있고, 부호 있는 int의 오버플로는 결과를 약속할 수 없습니다. 이번 미션은 최대 100만 토큰을 100ms 간격으로 모델링하므로 해당 구간에서는 시각이 넘치지 않습니다. 장기 가동의 순환 처리와 실제 타이머 설계는 뒤의 주기 작업 모듈에서 다룹니다.

sizeof를 사용한 메모리 예산

sizeof의 결과형은 size_t입니다. printf에서는 %zu로 출력합니다. sizeof(Sample)은 자료형 크기이고 sizeof sample은 해당 객체의 크기입니다. 일반적인 고정 크기 객체에서는 실제 값이 0인지 4095인지에 따라 크기가 바뀌지 않습니다. 크기를 반환하는 record_bytes 함수는 필드를 더하지 말고 sizeof(Sample)을 반환합니다. 테스트는 특정 PC 숫자 8이 아니라 그 환경에서의 sizeof와 일치하는지 비교합니다.

C에서 바이트는 sizeof(char)가 1인 단위입니다. 한 바이트의 비트 수는 limits.h의 CHAR_BIT로 확인합니다. 이 실행 환경에서는 CHAR_BIT가 8이라 uint32_t는 4바이트, uint16_t는 2바이트입니다. 다른 대상에서도 항상 char 한 칸이 8비트라는 가정을 숨기지 않습니다. 지원 대상에 고정 폭 형이 있는지와 바이트 표현을 빌드 과정에서 확인하는 것이 통신 코드를 옮길 때 필요합니다.

필드 크기 합은 6이지만 Sample 전체는 이 맥에서 8입니다. 구조체 안이나 끝에는 정렬 조건을 맞추기 위한 패딩이 들어갈 수 있습니다. 배열의 다음 원소도 적절한 위치에서 시작해야 하므로 전체 구조체 크기에 끝 패딩이 포함될 수 있습니다. Sample[16]은 sizeof(Sample) 곱하기 16으로 계산합니다. 여기서는 128바이트이지만 아직 길이 필드나 다른 상태 객체는 포함하지 않은 샘플 배열의 예산입니다.

메모리 예산을 적을 때 샘플 배열, 현재 저장 건수, 별도 입출력 공간을 나눕니다. 뒤의 Buffer에는 size_t length가 추가되므로 전체 크기는 배열만의 크기와 다를 수 있습니다. 반대로 사용 중인 기록이 3건이라고 정적 배열의 예약 공간이 3건으로 줄지는 않습니다. 용량은 예약한 칸 수이고 길이는 그중 의미 있는 칸 수입니다. 이 차이를 다음 레슨의 함수 인자로 연결합니다.

크기 관찰을 안전한 구현으로 바꿉니다

실습에서 size: 6 cases라는 줄은 구조체 크기 반환, 정상 양쪽 경계, 음수와 상한 초과 거부, NULL 거부를 검사한 결과입니다. starter는 패딩을 빼고 크기를 계산하고 범위 검사 없이 변환합니다. 먼저 반환값을 sizeof로 바꾼 뒤 raw_convert의 검사 순서를 고칩니다. 정상 0도 성공해야 하므로 raw가 참인지로 검사하지 않습니다. out의 초기값 99가 실패 뒤에도 남는지 봅니다.

format specifies type 경고가 sizeof 출력 줄을 가리키면 %d를 %zu로 바꿨는지 확인합니다. uint32_t 출력에는 inttypes.h의 PRIu32를 사용합니다. printf("max=%" PRIu32 "\n", t);처럼 문자열과 매크로를 이어 씁니다. 숫자 출력 형식을 억지로 캐스팅해 경고만 지우기 전에 출력하려는 자료형이 무엇인지 읽습니다. -Werror 때문에 경고가 나면 실행 파일이 갱신되지 않을 수 있습니다.

이번 구조체의 메모리 배치를 그대로 UART나 파일 형식으로 사용하지 않습니다. 필드 폭이 같아도 패딩과 바이트 순서는 대상마다 달라질 수 있습니다. 이 레슨의 계산은 RAM 예산이며 직렬화 규격이 아닙니다. 더 읽기에서 기본형 범위와 고정 폭 출력 방식을 보충하고, 다음 레슨에서는 이미 예약한 배열의 유효 구간을 주소와 길이로 전달합니다.

따라하기

검증 실패에서 구현 시작

starter 폴더에서 실행합니다. 실패 개수는 수정 정도에 따라 달라지므로 자신의 결과를 확인합니다.

make test

관찰 실행 파일 만들기

구현을 수정한 뒤 실행합니다. 컴파일 성공은 출력 없이 끝납니다.

make -s demo

값과 계약 관찰

solution과 같은 구현에서 실행한 결과입니다. 숫자 크기는 이 맥의 측정값입니다.

./demo

실행 결과

CHAR_BIT=8 timestamp=4 raw=2 sample=8 buffer=128
accepted=0 unchanged=99
max=4294967295

같은 테스트 재실행

수정 후 기대 반환값과 상태 유지가 함께 확인됩니다.

make -s test

실행 결과

size: 6 cases, 0 failures

확인 문제

실습

lab.c의 record_bytes와 raw_convert를 구현합니다. sizeof(Sample)과 동일한 크기를 반환하고 0~4095만 변환합니다. NULL과 범위 오류에서 out을 유지합니다. make test로 6개 검사를 통과시키고 실제 자료형 크기를 메모합니다.

시작 코드·테스트 내려받기

실행 명령

make test

기대 결과

size: 6 cases, 0 failures

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 배열과 포인터를 사용할 때 메모리 오류를 확인하는 방법을 설명해 주시면 됩니다.