출시·고도화 중
C 안내서 · 3/6
C의 타입은 값이 메모리에서 몇 바이트를 차지하고 어떻게 해석되는지를 정합니다. 이 장에서는 정수와 실수 타입, 배열, 문자 배열로 다루는 문자열, 그리고 여러 값을 묶는 구조체, 공용체, 열거형과 typedef를 살펴봅니다.
C 표준은 정수 타입의 정확한 크기를 정하지 않고 최솟값만 보장합니다. 실제 크기는 플랫폼마다 다릅니다.
| 타입 | 표준이 보장하는 최소 폭 | 64비트 Linux · macOS | 64비트 Windows |
|---|---|---|---|
char | 8비트 | 8비트 | 8비트 |
short | 16비트 | 16비트 | 16비트 |
int | 16비트 | 32비트 | 32비트 |
long | 32비트 | 64비트 | 32비트 |
long long | 64비트 | 64비트 | 64비트 |
long의 크기가 운영체제에 따라 다르다는 점에 주의합니다. 정확한 폭이 필요하면 <stdint.h>의 int8_t, int32_t, uint64_t 같은 고정 폭 타입을 씁니다. 객체 크기와 배열 인덱스에는 <stddef.h>의 size_t(부호 없는 타입)를 씁니다.
정수 타입에는 부호 있는(signed) 것과 부호 없는(unsigned) 것이 있습니다. 부호 없는 정수는 범위를 넘으면 0부터 다시 도는 것이 정의되어 있지만, 부호 있는 정수의 오버플로는 정의되지 않은 동작입니다. 실수 타입은 float, double, long double이 있고 보통 double을 기본으로 씁니다.
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <limits.h>
int main(void) {
printf("int: %zu바이트, 최댓값 %d\n", sizeof(int), INT_MAX);
printf("long: %zu바이트\n", sizeof(long));
uint8_t small = 255;
small++; /* 부호 없는 정수는 0으로 돌아감 */
printf("small = %u\n", (unsigned)small);
int64_t big = INT64_C(9000000000);
printf("big = %" PRId64 "\n", big); /* 고정 폭 타입용 지정자 */
double avg = 10 / 4; /* 2.0: 정수끼리 먼저 나눈 뒤 변환 */
double ok = 10 / 4.0; /* 2.5 */
printf("%.1f %.1f\n", avg, ok);
return 0;
}배열은 같은 타입의 값을 메모리에 연속으로 놓은 것입니다. 크기는 선언할 때 정해지고 나중에 바꿀 수 없습니다. 초기값을 일부만 주면 나머지는 0으로 채워집니다.
int scores[5] = {90, 85, 77}; /* 나머지 두 칸은 0 */
size_t n = sizeof scores / sizeof scores[0]; /* 요소 개수 5 */
for (size_t i = 0; i < n; i++) {
printf("%zu: %d\n", i, scores[i]);
}
int grid[2][3] = {{1, 2, 3}, {4, 5, 6}}; /* 2차원 배열 */
printf("%d\n", grid[1][2]); /* 6 */C는 배열 범위를 검사하지 않습니다. scores[5]처럼 범위를 벗어난 접근은 컴파일도 되고 실행도 될 수 있지만 정의되지 않은 동작입니다. 또 배열을 함수에 넘기면 첫 요소를 가리키는 포인터로 바뀌므로(배열의 붕괴), 함수 안에서는 sizeof로 요소 개수를 구할 수 없습니다. 그래서 배열과 함께 길이를 따로 넘기는 것이 관례입니다.
C에는 별도의 문자열 타입이 없습니다. 문자열은 끝에 널 문자 '\0'이 붙은 char 배열입니다. "hi"는 h, i, \0 세 바이트를 차지합니다.
#include <stdio.h>
#include <string.h>
int main(void) {
char name[16] = "Kim"; /* 수정 가능한 배열 */
const char *lang = "C"; /* 문자열 리터럴: 수정하면 안 됨 */
printf("%zu\n", strlen(name)); /* 3: '\0'은 세지 않음 */
printf("%zu\n", sizeof name); /* 16: 배열 전체 크기 */
char buf[32];
snprintf(buf, sizeof buf, "%s loves %s", name, lang); /* 크기 안에서만 씀 */
puts(buf);
if (strcmp(name, "Kim") == 0) { /* ==가 아니라 strcmp로 비교 */
puts("같은 문자열");
}
return 0;
}strcpy, strcat은 대상 버퍼 크기를 확인하지 않아 넘쳐 쓰기 쉽습니다. 크기를 함께 받는 snprintf를 쓰는 습관을 들이면 안전합니다. ==로 문자열을 비교하면 내용이 아니라 주소를 비교하게 되므로 strcmp를 씁니다.
구조체는 서로 다른 타입의 값을 하나로 묶습니다. 필드는 .으로, 구조체를 가리키는 포인터에서는 ->로 접근합니다. C99의 지정 초기화(.x = 1)를 쓰면 필드 순서와 상관없이 읽기 쉽게 초기화할 수 있습니다.
열거형(enum)은 이름 붙은 정수 상수 묶음이고, 공용체(union)는 모든 멤버가 같은 메모리를 공유해 한 번에 하나의 값만 담습니다. 공용체는 보통 어떤 멤버가 유효한지 알려 주는 태그와 함께 씁니다. typedef는 타입에 새 이름을 붙입니다.
#include <stdio.h>
typedef enum { SHAPE_CIRCLE, SHAPE_RECT } ShapeKind;
typedef struct {
double x, y;
} Point;
typedef struct {
ShapeKind kind; /* 어느 멤버가 유효한지 알려 주는 태그 */
Point origin;
union {
double radius; /* SHAPE_CIRCLE일 때 */
struct { double w, h; } size; /* SHAPE_RECT일 때 */
} u;
} Shape;
double area(const Shape *s) {
switch (s->kind) {
case SHAPE_CIRCLE: return 3.14159265 * s->u.radius * s->u.radius;
case SHAPE_RECT: return s->u.size.w * s->u.size.h;
}
return 0.0;
}
int main(void) {
Shape c = { .kind = SHAPE_CIRCLE, .origin = { .x = 0, .y = 0 }, .u.radius = 2.0 };
Shape r = { .kind = SHAPE_RECT, .u.size = { .w = 3.0, .h = 4.0 } };
printf("%.2f %.2f\n", area(&c), area(&r));
printf("sizeof(Shape) = %zu\n", sizeof(Shape));
return 0;
}구조체를 함수에 값으로 넘기면 통째로 복사되므로, 큰 구조체는 포인터로 넘기고 읽기만 할 때는 const를 붙입니다. 구조체 크기는 필드 정렬을 위한 패딩 때문에 필드 크기의 합보다 클 수 있습니다.
<stdint.h>, 크기와 인덱스에는 size_t를 씁니다.'\0'으로 끝나는 char 배열이며 snprintf, strcmp로 안전하게 다룹니다.enum, 긴 타입 이름은 typedef로 정리합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.