포인터 문서의 구현 코드다.
/* 포인터: 주소 연산자 &, 따라가기 연산자 *, 포인터의 포인터, 포인터 형 변환을 값으로 확인한다.
*
* 문서: 03.포인터 (예시, 식과 타입 표, 추적 표, 스스로 설명해 보기, 오해, 카드)
* 실행: cc -std=c11 -Wall -Wextra 03_c-pointer_impl.c -o c_pointer && ./c_pointer
*
* 주소 값 자체(예: 0x16b...)는 실행할 때마다 달라진다. 그래서 주소의 숫자가 아니라
* "누가 누구를 가리키는가"와 "따라가면 무슨 값이 나오는가"만 비교한다.
*/
#include <math.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)
/* 1. 변수와 포인터: 문서 예시의 그림 */
static void basics(void)
{
int a = 100;
int *p = &a; /* p에는 a의 주소가 들어 있다 */
CHECK(p == &a, "p에 든 값은 a의 주소다");
CHECK(*p == 100, "*p는 p가 가리키는 칸의 값이다");
CHECK(*&a == a, "주소를 구했다가 따라가면 원래 값이다");
CHECK(&*p == p, "따라갔다가 주소를 구하면 원래 포인터다");
CHECK(p[0] == *p, "*p와 p[0]은 같은 칸이다");
*p = 7; /* p가 가리키는 칸(= a)에 7을 넣는다 */
CHECK(a == 7, "*p에 넣으면 a가 바뀐다");
a = 8;
CHECK(*p == 8, "a를 바꾸면 *p로 읽은 값도 바뀐다");
printf("[OK] 변수와 포인터: p == &a, *p로 읽고 쓰면 a를 읽고 쓰는 것이다\n");
}
/* 2. 문서의 예: 101을 세 번 찍는 코드 */
static void slide_example(void)
{
int V = 101;
int *P = &V; /* P는 int V를 가리킨다 */
int **Q = &P; /* Q는 int 포인터 P를 가리킨다 */
char out[64];
snprintf(out, sizeof out, "%d %d %d", V, *P, **Q);
CHECK(strcmp(out, "101 101 101") == 0, "V, *P, **Q는 모두 101");
CHECK(*Q == P && *Q == &V, "*Q는 P에 든 값, 곧 V의 주소다");
printf("[OK] 101을 세 번: V, *P, **Q -> %s\n", out);
}
/* 3. 문서의 추적 표 */
static void trace_table(void)
{
int V = 101, W = 55;
int *P = &V;
int **Q = &P;
CHECK(V == 101 && W == 55 && P == &V && *Q == &V, "처음 상태");
*P = 7; /* 1줄: P를 따라간 칸 V에 7 */
CHECK(V == 7 && W == 55 && P == &V, "*P = 7 뒤: V만 7");
**Q = 9; /* 2줄: Q를 따라가면 P, P를 따라가면 V */
CHECK(V == 9 && W == 55 && P == &V, "**Q = 9 뒤: V만 9");
*Q = &W; /* 3줄: Q를 한 번만 따라간 칸은 P. P가 W를 가리키게 된다 */
CHECK(V == 9 && W == 55 && P == &W && *P == 55, "*Q = &W 뒤: P가 W를 가리킨다");
**Q = 1; /* 4줄: 이제 두 번 따라가면 W */
CHECK(V == 9 && W == 1 && P == &W, "**Q = 1 뒤: W만 1");
printf("[OK] 추적 표: V 101->7->9->9->9, W 55->55->55->55->1, P는 3번째 줄에서 W로 옮겨 간다\n");
}
/* 4. 오해: p = q 와 *p = *q 는 다르다 */
static void copy_pointer_or_value(void)
{
int a = 1, b = 2;
int *p = &a, *q = &b;
*p = *q; /* 내용물을 베낀다: a에 b의 값 */
CHECK(a == 2 && b == 2 && p == &a && p != q, "*p = *q: a가 2가 되고 p는 여전히 a를 가리킨다");
*p = 10;
CHECK(a == 10 && b == 2, "그 뒤 *p = 10은 a만 바꾼다");
p = q; /* 쪽지를 베낀다: p도 b를 가리킨다 */
CHECK(p == &b && p == q && a == 10 && b == 2, "p = q: 값은 그대로이고 p가 b를 가리킨다");
*p = 5;
CHECK(a == 10 && b == 5 && *q == 5, "그 뒤 *p = 5는 b를 바꾼다. q로 봐도 5다");
printf("[OK] p = q 는 가리키는 곳을 바꾸고, *p = *q 는 가리키는 칸의 값을 바꾼다\n");
}
/* 5. 선언: 별표는 변수 이름마다 붙는다. 포인터의 크기는 가리키는 타입과 상관없다 */
static void declarations(void)
{
int* x, y; /* x는 int *, y는 그냥 int */
int *u, *v; /* 둘 다 int * */
int n = 3;
int *np = NULL; /* 아무것도 가리키지 않는다 */
x = &n; y = n; u = &n; v = &n;
CHECK(sizeof y == sizeof(int), "int* x, y; 에서 y는 int다");
CHECK(sizeof x == sizeof(int *) && sizeof u == sizeof v, "x, u, v는 포인터다");
CHECK(*x == 3 && y == 3 && *u == 3 && *v == 3, "값 확인");
CHECK(np == NULL, "NULL은 어느 칸의 주소와도 다른 값이다");
CHECK(sizeof(char *) == sizeof(int *) && sizeof(int *) == sizeof(double *) &&
sizeof(double *) == sizeof(int **) && sizeof(int **) == sizeof(void *),
"포인터는 무엇을 가리키든 크기가 같다");
CHECK(sizeof(char) == 1 && sizeof(int) == 4 && sizeof(double) == 8, "이 기계의 char, int, double 크기");
printf("[OK] 크기: int %zu바이트, double %zu바이트, 포인터 %zu바이트(가리키는 타입과 상관없다)\n",
sizeof(int), sizeof(double), sizeof(void *));
}
/* 6. 포인터 형 변환
* 같은 칸을 다른 타입으로 읽으면 같은 비트가 다른 값으로 읽힌다.
* int의 자리를 float * 로 직접 읽는 것은 C 표준이 결과를 정해 두지 않았다.
* 그래서 여기서는 표준이 허락하는 두 방법으로 같은 일을 한다:
* unsigned char * 로 바이트를 하나씩 보기, memcpy로 비트를 그대로 옮기기. */
static void casting(void)
{
int V = 101;
unsigned char *bytes = (unsigned char *)&V; /* 어떤 칸이든 바이트로는 볼 수 있다 */
unsigned int probe = 1;
int little = *(unsigned char *)&probe == 1; /* 낮은 자리 바이트가 앞에 오는 기계인가 */
float as_bits, as_value;
unsigned int rebuilt = 0;
size_t i;
for (i = 0; i < sizeof V; i++) {
size_t k = little ? i : sizeof V - 1 - i;
rebuilt |= (unsigned int)bytes[i] << (8 * k);
}
CHECK(rebuilt == 101u, "바이트 네 개를 다시 모으면 101");
if (little)
CHECK(bytes[0] == 101 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0, "101의 바이트는 65 00 00 00");
memcpy(&as_bits, &V, sizeof as_bits); /* 비트를 그대로 float 칸으로 */
as_value = (float)V; /* 값을 float로 바꾼다 */
CHECK(sizeof(float) == sizeof(int), "이 기계에서 float와 int는 둘 다 4바이트");
CHECK(as_value == 101.0f, "(float)V 는 값을 바꾼다: 101.0");
CHECK(as_bits != 101.0f, "같은 비트를 float로 읽으면 101이 아니다");
CHECK(as_bits == ldexpf(101.0f, -149), "같은 비트를 float로 읽으면 101 x 2^-149");
CHECK((double)as_bits > 1.41e-43 && (double)as_bits < 1.42e-43, "약 1.4 x 10^-43");
printf("[OK] 형 변환: (float)V = %.1f, V의 비트를 float로 읽으면 %g\n", (double)as_value, (double)as_bits);
}
/* 7. 카드의 답 */
static void cards(void)
{
/* C1 */
int x = 3, y = 8;
int *p = &x;
int **pp = &p;
*p = *p + 1; /* x = 4 */
*pp = &y; /* p가 y를 가리킨다 */
**pp = **pp * 2; /* y = 16 */
CHECK(x == 4 && y == 16 && *p == 16 && p == &y, "C1: x = 4, y = 16, *p = 16");
/* C2 */
{
int a = 1, b = 2;
int *s = &a, *t = &b;
s = t;
*s = 9;
CHECK(a == 1 && b == 9, "C2 (가): s = t; *s = 9; -> a = 1, b = 9");
}
{
int a = 1, b = 2;
int *s = &a, *t = &b;
*s = *t;
*s = 9;
CHECK(a == 9 && b == 2, "C2 (나): *s = *t; *s = 9; -> a = 9, b = 2");
}
/* C4: 그림을 코드로 */
{
int n = 5;
int *r = &n;
int **q = &r;
CHECK(**q == 5 && *q == &n && q == &r, "C4: q -> r -> n(5)");
}
printf("[OK] 카드 C1, C2, C4의 답\n");
}
int main(void)
{
basics();
slide_example();
trace_table();
copy_pointer_or_value();
declarations();
casting();
cards();
if (fails) {
printf("%d개 확인 실패\n", fails);
return 1;
}
printf("ALL CHECKS PASSED\n");
return 0;
}