자료형과 선행처리기
1) 자료형
- 자료형(data type) = 값을 어떻게 비트로 표현·저장·연산할지에 대한 약속.
- 이유: 같은 “숫자”라도 타입에 따라 범위/연산/메모리 크기/해석이 달라짐.
2) C의 주요 자료형
- 정수형: char, short, int, long, long long(각각 signed/unsigned 가능)
- char는 signed(-128~127bits)인지 unsigned(0~255bits)인지 구현 정의(플랫폼마다 다름).
- sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long) 만 보장. 정확한 바이트 수는 표준이 고정하지 않음.
- 실수형: float, double, long double
- 보통 IEEE-754를 따르지만 C가 강제하진 않음(플랫폼 따라 다름).
- 열거형: enum { A=0, B=1, ... }처럼 이름 붙인 정수 상수 집합.
- 파생형: 배열, 구조체, 공용체, 포인터.
플랫폼 차이(중요)
- 64비트 리눅스(x86-64, LP64)에서는 long이 8바이트인 경우가 일반적.
- 윈도우즈 64비트(MSVC, LLP64)에서는 long이 4바이트.
- long double은 GCC(리눅스)에서 흔히 16바이트, MSVC에서는 8바이트(double과 동일).
→ 항상 sizeof(T)와 <limits.h>, <float.h>로 확인하자.
#include <stdio.h>
#include <limits.h> // INT_MIN, INT_MAX 등 최소-최대값 확인
#include <float.h>
int main(void) {
printf("sizeof: char=%zu, short=%zu, int=%zu, long=%zu, long long=%zu\n",
sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long));
printf("int 범위: %d ~ %d\n", INT_MIN, INT_MAX);
printf("double 범위: %e ~ %e\n", DBL_MIN, DBL_MAX);
return 0;
}
3) 정수/실수 리터럴 정확히 쓰기
- 정수 리터럴 접미사: U(u, 무부호, unsigned), L(l, long), LL(ll, long long)
- 예: 10U, 1234L, 0xFFULL
- 8진수 0으로 시작 (10 = 012)
- 16진수 0x로 시작 0~9~a~f, (10 = 0x0a)
- 실수 리터럴 접미사: f/F(float), l/L(long double)
- 예: 3.14f(float), 3.14(double), 3.14L(long double)
- 지수(승 수) 표기: 1.2e3(=1200.0)
- C99 헥사 플로트(알아두면 좋음): 0x1.8p10 (= 1.5 × 2^10)
4) 문자/문자열 상수 요령
- 문자: 작은따옴표 'A', 이스케이프 '\n', '\x41', '\101'.
- 문자열: 큰따옴표 "ABC" 끝에 널 문자('\0')가 자동 부착.
- 유니코드는 C 표준만으로는 복잡. 기본 ASCII 예제 위주로 시작.
char c1 = 'A'; // 문자 상수
char c2 = '\n'; // 개행
char c3 = '\x41'; // 16진수로 'A' 10진수로 65
char c4 = 0x41;
char s[] = "HELLO"; // ['H','E','L','L','O','\0']
이스케이프 문자
- '\a': 경고음(alarm) 출력
- '\b': 백스페이스(backspace)
- '\f': 폼 피드(form feed, 새 페이지)
- '\n': 줄 바꿈(new line)
- '\r': 캐리지 리턴(carriage return, 행 처음)
- '\t': 수평 탭(horizontal tab)
- '\v': 수직 탭(vertical tab)
- '\\': 백슬래시 자체
- '\'': 작은따옴표 자체
- '\"': 큰따옴표 자체
- '\?': 물음표 자체(삼중문자(trigraph) 회피용, 역사적 이유), 현재는 '?'로 표현하는 것도 유효
- '\0': 널(null) 문자(값 0, 문자열 종료 표식)
- '\nnn': 8진수 코드 값(예: '\101' == 'A')
- '\xhh': 16진수 코드 값(예: '\x41' == 'A', 자릿수 가변)
- '\uFFFF': 유니코드 16비트 코드 포인트(C11, 예: '\u00A9')
- '\UFFFFFFFF': 유니코드 32비트 코드 포인트(C11, 예: '\U0001F600')
5) 변수 선언·초기화 베스트 프랙티스
- 선언과 동시에 초기화: 미초기화 사용 버그 방지.
- 범위(스코프)를 최소화: 사용하는 블록 안쪽에 선언.
- 예상 범위를 넘어갈 수 있으면 더 넓은 타입으로.
#include <stdio.h>
int a = 1; // 전역변수
int main(void) {
int a = 2; // 지역변수, 자료형 변수명;
printf("%d\n", a) // 2 (지역변수 우선 사용)
// char b, c; // 자료형 변수명1, 변수명2;
int sum = 0; // 초기화 필수
for (int i = 1; i <= 10; i++) sum += i;
printf("%d\n", sum); // 55
}
오버플로 예시와 해결
short a = 32767;
short b = a + 1; // C에서 정수 승격 뒤 다시 담김 → 구현에 따라 음수로 wrap. -32767가 됨.
int safe = 32767 + 1; // 더 넓은 형 사용. 32768
6) 열거형(enum) 실전 팁
- 기본적으로 int 범위의 정수 상수 집합.
- 명시 값으로 간격 띄우기 가능(ABI/저장값 의미 명확화).
- 디버깅/로그에서 가독성↑, 스위치 문에 유용.
// 0부터 시작하는 값을 이름으로 부를 수 있음.
enum day { SUN=0, MON, TUE, WED, THU, FRI, SAT };
// 이름에 임의의 값을 지정해서 부를 수 있음.
enum fruit { APPLE=1, PEAR=2, MANGO=10, GRAPE=11 };
int main() {
printf("TUE의 값 = %d\n", TUE); // 2
printf("PEAR의 값 = %d\n", PEAR); // 2
}
7) 선행처리기(Preprocessor)
선행처리 지시어 문장은 '#'으로 시작해서 한 행에 한 개의 문장을 작성(문장 끝에 ';'를 쓰지 않음.)
7.1 헤더 포함
- 표준 헤더: #include <stdio.h>
- 로컬 헤더: #include "my.h"
// my.h
#ifndef MY_H_ // include guard 시작
#define MY_H_
int add(int, int);
#endif // include guard 끝
// my.c
#include "my.h"
int add(int a, int b) { return a + b; }
7.2 매크로 상수/함수
- 상수: 숫자·문자열 매직넘버 제거.
- 함수형 매크로: 반드시 괄호로 방어(부작용/우선순위).
#define PI 3.141592
#define C_AREA(x) ((PI) * (x) * (x)) // 인수/전체에 괄호를 써서 계산오류방지
double a = C_AREA(10); // OK
double b = C_AREA(3+1); // (3+1)*(3+1)로 안전하게 확장
부작용 주의(입력 2번 평가)
#define SQR(x) ((x)*(x))
int i = 2;
int y = SQR(++i); // ++i가 두 번 실행될 수 있음 → 매크로 대신 inline 함수 고려
7.3 조건부 컴파일
- 빌드 옵션/플랫폼별 분기, 로그 토글에 필수.
#define DEBUG 1
#if DEBUG
#define LOG(fmt, ...) printf("[D] " fmt "\n", __VA_ARGS__)
#else
#define LOG(fmt, ...) ((void)0)
#endif
// 사용
LOG("x=%d", x);
- 매크로 존재 여부로 분기: #if defined(WIN32) / #ifdef WIN32
8) 자주 틀리는 포인트
- 타입 크기·범위는 구현 의존: 외우지 말고 sizeof/limits.h/float.h로 확인.
- char의 signed 여부는 구현 정의: 문자→정수 변환 로직에 주의.
- 정수 리터럴 타입 승격: 큰 상수는 접미사로 의도를 명확히(ULL 등).
- 매크로 괄호와 부작용: 함수형 매크로는 괄호 철저, 필요 시 static inline.
- 초기화 누락: 지역 변수는 쓰레기값. 항상 초기화.
9) 짧은 실습(복붙 즉시 실행)
#include <stdio.h>
#include <limits.h>
#include <float.h>
#define PI 3.141592
#define C_AREA(x) ((PI) * (x) * (x))
int main(void) {
// 타입·범위 확인
printf("sizeof(long)=%zu, sizeof(long double)=%zu\n", sizeof(long), sizeof(long double));
printf("int: %d ~ %d\n", INT_MIN, INT_MAX);
printf("double: %e ~ %e\n", DBL_MIN, DBL_MAX);
// 리터럴 접미사
unsigned long long cap = 18446744073709551615ULL; // ULL 접미사
printf("ULL = %llu\n", cap);
// 매크로 안전성
double r1 = 10.0;
double r2 = 3.0 + 1.0;
printf("area(10)=%.3f, area(3+1)=%.3f\n", C_AREA(r1), C_AREA(r2));
// enum 사용
enum day { SUN, MON, TUE, WED, THU, FRI, SAT };
enum day d = WED;
printf("WED=%d\n", d);
}
표준 선행처리 지시어(Preprocessor directives) (C90~C23 공통)
// #include <파일>
#include <stdio.h> // 표준 포함 경로(시스템 헤더)에서 파일을 찾아 포함해라.
// #include "파일"
#include "my.h" // 현재 디렉터리(그다음 include 경로)에서 파일을 찾아 포함해라.
// #define 매크로명 치환식
#define PI 3.141592 // 매크로 정의: 코드에서 PI를 3.141592로 치환해라.
// #define 매크로명(인수들) (수식)
#define SQR(x) ((x)*(x)) // 함수형 매크로: 인수 x를 받아 제곱으로 치환해라.
// #undef 매크로명
#undef PI // 이전에 정의된 매크로 PI를 취소(해제)해라.
// #if 상수식
#if 1 // 조건이 참이면 아래 코드를 컴파일에 포함해라.
/* ... */
#endif // 조건부 컴파일 블록을 끝내라.
// #ifdef 매크로명
#ifdef DEBUG // 매크로 DEBUG가 정의되어 있으면 포함해라.
/* ... */
#endif
// #ifndef 매크로명
#ifndef MY_H_ // 매크로 MY_H_가 아직 정의되지 않았으면 포함해라.
#define MY_H_ // (보호용) include guard 매크로를 정의해라.
/* 헤더 본문 */
#endif // 조건부 블록을 끝내라.
// #elif 상수식 / #else
#if VER == 1
/* ... */
#elif VER == 2 // 위 조건이 거짓이고, 이 조건이 참이면 포함해라.
/* ... */
#else // 위 조건들이 모두 거짓이면 포함해라.
/* ... */
#endif
// #line 라인번호 "파일이름"
#line 100 "virtual.c" // 이후 소스의 파일/라인 정보를 지정한 값으로 바꿔라(경고/에러 위치 표시용).
// #pragma 지시문별_옵션
#pragma once // (비표준이지만 널리 사용) 이 헤더를 한 번만 포함해라.
#pragma STDC FENV_ACCESS ON // 표준 pragma의 예(컴파일러 지원 여부에 따름).
// #error 메시지
#if !defined(__STDC_VERSION__)
#error "C 표준 컴파일러가 필요합니다." // 여기서 컴파일을 중단하고 오류를 내라.
#endif
// (C23) #warning 메시지 — 경고만 띄우고 계속 컴파일
#warning "이 API는 폐기 예정입니다."
팁: 조건식에서 쓰는 defined는 연산자임(지시어 아님).
#if defined(DEBUG) && !defined(NDEBUG)
/* ... */
#endif
매크로 안에서 쓰는 토큰 연산(지시어는 아니지만 필수)
// # 연산자: 문자열화(stringize) — 인수를 "소스 그대로 문자열"로 바꿔라.
#define STR(x) #x
const char *s = STR(Hello World); // → "Hello World"
// ## 연산자: 토큰 결합(token paste) — 두 토큰을 붙여 하나의 식별자로 만들어라.
#define MAKE(name, n) name##n
int var1 = 42;
int x = MAKE(var, 1); // → var1
'C' 카테고리의 다른 글
| 선택 제어문과 반복 제어문 (0) | 2025.11.11 |
|---|---|
| 입.출력 함수와 연산자 (0) | 2025.10.22 |
| C 언어의 개요 (1) | 2025.08.27 |
| 모두를 위한 컴퓨터 과학(하버드CS50 2019)(4) (0) | 2025.04.29 |
| 모두를 위한 컴퓨터 과학(하버드CS50 2019)(3) (1) | 2025.04.29 |