배열과 포인터 문서의 구현 코드다.
/* 배열과 포인터: 칸의 주소 = 시작 주소 + i x 칸 크기, 문자 배열과 문자 포인터, strcpy·strcmp 두 판.
*
* 문서: 05.배열과 포인터 (예시, 주소 식, 문자 배열과 문자 포인터, 문자열 함수 두 판, 카드)
* 실행: cc -std=c11 -Wall -Wextra 05_array-pointer_impl.c -o array_pointer && ./array_pointer
*
* 함수 이름을 strcpy, strcmp로 쓰면 표준 라이브러리에 이미 있는 것과 겹친다.
* 여기서는 copy_a, copy_b, compare_a, compare_b로 이름을 바꿨다.
* 무작위 문자열은 정해 둔 씨앗에서 뽑으므로 돌릴 때마다 같은 문자열이 나온다.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static int fails = 0;
#define CHECK(cond, msg) \
do { \
if (!(cond)) { \
printf("[FAIL] %s (%d번째 줄)\n", (msg), __LINE__); \
fails++; \
} \
} while (0)
struct pair {
int key;
double value;
};
/* 레코드(구조체): 칸마다 시작에서 떨어진 거리가 정해져 있다 */
struct date {
int year;
int month;
int day;
};
/* A: 색인으로 쓴 복사 */
static void copy_a(char *s, const char *t)
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
/* B: 포인터로 쓴 복사 */
static void copy_b(char *s, const char *t)
{
while ((*s = *t) != '\0') {
s++;
t++;
}
}
/* 비교 1: 색인으로 쓴 비교 */
static int compare_a(const char *s, const char *t)
{
int i;
for (i = 0; s[i] == t[i]; i++)
if (s[i] == '\0')
return 0;
return s[i] - t[i];
}
/* 비교 2: 포인터로 쓴 비교 */
static int compare_b(const char *s, const char *t)
{
for (; *s == *t; s++, t++)
if (*s == '\0')
return 0;
return *s - *t;
}
/* 복사 B가 일을 몇 번 하는지 세는 판 (카드용) */
static void copy_b_counted(char *s, const char *t, int *assigns, int *moves)
{
*assigns = 0;
*moves = 0;
while (((*assigns)++, (*s = *t)) != '\0') {
s++;
t++;
(*moves)++;
}
}
static int sign(int v)
{
return (v > 0) - (v < 0);
}
/* 정해 둔 씨앗에서 수를 차례로 뽑는다 */
static uint32_t seed = 20261012u;
static uint32_t next_random(void)
{
seed = seed * 1664525u + 1013904223u;
return seed >> 8;
}
static void random_string(char *buf, int max_len)
{
int len = (int)(next_random() % (uint32_t)(max_len + 1));
int i;
for (i = 0; i < len; i++)
buf[i] = (char)('a' + next_random() % 3u); /* 글자 종류를 줄여 앞부분이 같은 쌍이 자주 나오게 */
buf[len] = '\0';
}
static void address_formula(void)
{
char c[8];
int a[8];
double d[8];
struct pair s[8];
int i;
for (i = 0; i < 8; i++) {
c[i] = (char)('a' + i);
a[i] = 10 * i;
d[i] = 0.5 * i;
s[i].key = i;
s[i].value = i;
}
for (i = 0; i < 8; i++) {
/* 칸의 주소 = 시작 주소 + i x 칸 크기 */
CHECK((uintptr_t)&c[i] - (uintptr_t)&c[0] == (uintptr_t)i * sizeof c[0], "char 배열의 주소 차");
CHECK((uintptr_t)&a[i] - (uintptr_t)&a[0] == (uintptr_t)i * sizeof a[0], "int 배열의 주소 차");
CHECK((uintptr_t)&d[i] - (uintptr_t)&d[0] == (uintptr_t)i * sizeof d[0], "double 배열의 주소 차");
CHECK((uintptr_t)&s[i] - (uintptr_t)&s[0] == (uintptr_t)i * sizeof s[0], "구조체 배열의 주소 차");
/* A + x 는 &A[x], *(A + x) 는 A[x] */
CHECK(a + i == &a[i] && *(a + i) == a[i], "a + i == &a[i], *(a + i) == a[i]");
CHECK(d + i == &d[i] && *(d + i) == d[i], "d + i == &d[i], *(d + i) == d[i]");
CHECK(c + i == &c[i] && *(c + i) == c[i], "c + i == &c[i], *(c + i) == c[i]");
}
CHECK(a == &a[0], "배열 이름은 첫 칸의 주소로 쓰인다");
{
struct date today = {2026, 10, 12};
CHECK(offsetof(struct date, year) == 0 && offsetof(struct date, month) == 4 && offsetof(struct date, day) == 8,
"레코드의 칸은 시작에서 0, 4, 8바이트 떨어져 있다");
CHECK((uintptr_t)&today.day - (uintptr_t)&today == 8 && today.day == 12, "day의 주소 = 시작 + 8");
CHECK(100 + 2 * 4 == 108 && 200 + 8 == 208, "01번 문서의 예: 100번지 배열의 2번 칸은 108번지, 200번지 레코드의 day는 208번지");
}
CHECK(sizeof(int) == 4 && 100 + 3 * sizeof(int) == 112, "문서 예: arr[0]이 100번지면 arr[3]은 112번지");
CHECK(sizeof(double) == 8 && 100 + 3 * sizeof(double) == 124, "double 배열이면 124번지");
CHECK(&a[5] - &a[2] == 3, "포인터끼리 빼면 칸 수가 나온다");
CHECK((uintptr_t)&a[5] - (uintptr_t)&a[2] == 12, "바이트로는 12");
printf("[OK] 주소 식: char %zu, int %zu, double %zu, 구조체 %zu바이트 칸에서 &x[i] - &x[0] = i x 칸 크기\n",
sizeof c[0], sizeof a[0], sizeof d[0], sizeof s[0]);
}
static void array_name_and_pointer(void)
{
int arr[8] = {0, 10, 20, 30, 40, 50, 60, 70};
int *pa = arr; /* int *pa = &arr[0]; 과 같다 */
CHECK(pa == &arr[0] && *pa == 0, "pa는 0번 칸을 가리킨다");
CHECK(arr[7] == 70, "마지막 칸은 arr[7]이다. arr[8]은 배열 밖이다");
CHECK(sizeof arr == 8 * sizeof(int), "sizeof arr 는 배열 전체의 크기");
CHECK(sizeof pa == sizeof(int *), "sizeof pa 는 포인터 하나의 크기");
CHECK(sizeof arr / sizeof arr[0] == 8, "칸 수 = 전체 크기 / 한 칸 크기");
pa++; /* 포인터는 변수라서 옮길 수 있다 */
CHECK(pa == &arr[1] && *pa == 10, "pa++ 뒤에는 1번 칸");
pa += 3;
CHECK(pa == &arr[4] && *pa == 40 && pa[-1] == 30 && pa[2] == 60, "pa += 3 뒤에는 4번 칸. pa[-1], pa[2]도 쓸 수 있다");
pa = &arr[7];
CHECK(*pa == 70 && pa - arr == 7, "pa - arr 은 칸 번호");
printf("[OK] 배열 이름과 포인터: sizeof arr = %zu, sizeof pa = %zu\n", sizeof arr, sizeof pa);
}
static void char_array_and_char_pointer(void)
{
char amessage[] = "now is the time"; /* 배열: 글자 15개와 '\0'을 담는 16칸 */
const char *pmessage = "now is the time"; /* 포인터: 문자열 상수의 첫 글자를 가리킨다 */
CHECK(sizeof amessage == 16 && strlen(amessage) == 15, "amessage는 16바이트, 글자는 15개");
CHECK(sizeof pmessage == sizeof(char *), "pmessage는 포인터 하나 크기");
CHECK(strlen(pmessage) == 15 && strcmp(amessage, pmessage) == 0, "둘 다 같은 글자를 읽는다");
CHECK(amessage[15] == '\0' && pmessage[15] == '\0', "둘 다 끝에 '\\0'이 있다");
CHECK((const char *)amessage != pmessage, "글자가 놓인 자리는 서로 다르다");
amessage[0] = 'N'; /* 배열의 칸은 고칠 수 있다 */
CHECK(strcmp(amessage, "Now is the time") == 0, "amessage[0] = 'N'");
CHECK(pmessage[0] == 'n', "문자열 상수는 그대로다");
pmessage = "later"; /* 포인터는 다른 곳을 가리키게 바꿀 수 있다 */
CHECK(strcmp(pmessage, "later") == 0, "pmessage = \"later\"");
pmessage = amessage;
CHECK(pmessage[0] == 'N', "pmessage가 amessage를 가리키게 할 수도 있다");
printf("[OK] 문자 배열 %zu바이트(고칠 수 있다), 문자 포인터 %zu바이트(다른 곳을 가리키게 할 수 있다)\n",
sizeof amessage, sizeof pmessage);
}
static void string_functions(void)
{
char src[16], other[16], out_a[16], out_b[16], out_std[16];
int trial, same = 0, less = 0, greater = 0;
int assigns, moves;
/* 손으로 확인하는 값 */
copy_a(out_a, "hi");
copy_b(out_b, "hi");
CHECK(strcmp(out_a, "hi") == 0 && strcmp(out_b, "hi") == 0, "두 복사 모두 \"hi\"");
copy_a(out_a, "");
copy_b(out_b, "");
CHECK(out_a[0] == '\0' && out_b[0] == '\0', "빈 문자열을 복사하면 '\\0' 하나");
CHECK(compare_a("abc", "abc") == 0 && compare_b("abc", "abc") == 0, "같으면 0");
CHECK(compare_a("abc", "abd") == -1 && compare_b("abc", "abd") == -1, "\"abc\"와 \"abd\": 'c' - 'd' = -1");
CHECK(compare_a("abd", "abc") == 1 && compare_b("abd", "abc") == 1, "\"abd\"와 \"abc\": 1");
CHECK(compare_a("ab", "abc") == -'c' && compare_b("ab", "abc") == -'c', "\"ab\"와 \"abc\": '\\0' - 'c' = -99");
CHECK(compare_a("b", "abc") == 1 && compare_b("b", "abc") == 1, "\"b\"와 \"abc\": 'b' - 'a' = 1");
CHECK(compare_a("", "") == 0 && compare_b("", "") == 0, "빈 문자열끼리는 0");
/* 무작위 1,000쌍: 두 판과 표준 함수 */
for (trial = 0; trial < 1000; trial++) {
int ra, rb, rs;
random_string(src, 12);
random_string(other, 12);
memset(out_a, '#', sizeof out_a);
memset(out_b, '#', sizeof out_b);
memset(out_std, '#', sizeof out_std);
copy_a(out_a, src);
copy_b(out_b, src);
strcpy(out_std, src);
/* 끝의 '\0'까지만 쓰고 그 뒤는 건드리지 않아야 한다: 16바이트를 통째로 비교한다 */
CHECK(memcmp(out_a, out_std, sizeof out_std) == 0, "copy_a == strcpy");
CHECK(memcmp(out_b, out_std, sizeof out_std) == 0, "copy_b == strcpy");
ra = compare_a(src, other);
rb = compare_b(src, other);
rs = strcmp(src, other);
CHECK(ra == rb, "두 비교 판의 값이 같다");
CHECK(sign(ra) == sign(rs), "표준 strcmp와 부호가 같다");
if (rs == 0) same++;
else if (rs < 0) less++;
else greater++;
}
CHECK(same > 0 && less > 0 && greater > 0, "같다·앞이다·뒤다 세 경우가 모두 나왔다");
printf("[OK] 무작위 1,000쌍: 복사 두 판 = strcpy, 비교 두 판의 부호 = strcmp (같음 %d, 앞 %d, 뒤 %d)\n",
same, less, greater);
/* 카드: 복사 B가 "hi"를 옮길 때 */
copy_b_counted(out_b, "hi", &assigns, &moves);
CHECK(strcmp(out_b, "hi") == 0 && assigns == 3 && moves == 2, "\"hi\" 복사: 대입 3번, 포인터 옮기기 2번");
printf("[OK] 카드: 복사 B는 \"hi\"에서 대입 %d번, 옮기기 %d번\n", assigns, moves);
}
static void cards(void)
{
int a[6] = {5, 6, 7, 8, 9, 10};
int *p = a + 1;
CHECK(*(p + 2) == a[3] && *(p + 2) == 8, "*(p + 2)는 a[3] = 8");
CHECK(p[-1] == 5 && &p[3] == &a[4], "p[-1]은 a[0], &p[3]은 &a[4]");
CHECK(100 + 3 * 4 == 112 && 100 + 3 * 8 == 124 && 100 + 3 * 1 == 103, "100번지에서 3번 칸: int 112, double 124, char 103");
printf("[OK] 카드: 주소 계산과 포인터 식\n");
}
int main(void)
{
address_formula();
array_name_and_pointer();
char_array_and_char_pointer();
string_functions();
cards();
if (fails) {
printf("%d개 확인 실패\n", fails);
return 1;
}
printf("ALL CHECKS PASSED\n");
return 0;
}