언어마다 다른 것 · 34
포인터
값이 있는 자리(주소)를 들고 다니며 부른 쪽 변수를 바꿔요.
이 단원은 C++ · C에서 배워요.
Python·JavaScript·TypeScript·Java·크레이콘에는 주소를 직접 쥐는 포인터가 없고, 객체에는 참조로 닿아요(함수 단원의 목록 넘기기).
C#은 unsafe 코드 안에서만 포인터를 쓸 수 있어요.
포인터는 값 대신 그 값이 들어 있는 메모리 주소를 담는 변수예요.
아래 예제는 주소를 넘겨 두 변수를 맞바꾸고, 배열을 포인터로 받아 합과 가장 큰 칸을 찾아요.
#include <iostream> void swapPtr(int* a, int* b) { int t = *a; *a = *b; *b = t;} int sum(const int* p, int n) { // 배열은 첫 칸의 포인터로 넘어와요 int s = 0; for (const int* end = p + n; p < end; ++p) s += *p; return s;} int* findMax(int* p, int n) { if (n == 0) return nullptr; int* best = p; for (int i = 1; i < n; ++i) if (p[i] > *best) best = p + i; return best;} int main() { int a, b, n, arr[100]; std::cin >> a >> b; swapPtr(&a, &b); // 실제 코드라면 std::swap(a, b) std::cout << a << ' ' << b << '\n'; std::cin >> n; for (int i = 0; i < n; ++i) std::cin >> arr[i]; std::cout << sum(arr, n) << '\n'; int* m = findMax(arr, n); if (m != nullptr) std::cout << *m << ' ' << (m - arr) << '\n';}#include <stdio.h> void swap(int *a, int *b) { int t = *a; *a = *b; *b = t;} int sum(const int *p, int n) { /* 배열은 첫 칸의 포인터로 넘어와요 */ int s = 0; for (const int *end = p + n; p < end; p++) s += *p; return s;} int *find_max(int *p, int n) { if (n == 0) return NULL; int *best = p; for (int i = 1; i < n; i++) if (p[i] > *best) best = p + i; return best;} int main(void) { int a, b, n, arr[100]; scanf("%d %d", &a, &b); swap(&a, &b); printf("%d %d\n", a, b); scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", arr + i); printf("%d\n", sum(arr, n)); int *m = find_max(arr, n); if (m != NULL) printf("%d %td\n", *m, m - arr); return 0;}3 8 4 5 1 7 2
8 3 15 7 2
핵심
&x는 x의 주소,*p(역참조)는 p가 가리키는 자리의 값이에요.int *p = &x;다음의*p = 5;는 곧x = 5;예요.scanf에&n을 넘길 때부터 이미 주소를 써 왔어요.- 수를 넘기면 값이 복사돼서, 함수 안에서 바꿔도 부른 쪽은 그대로예요.
부른 쪽 변수를 바꾸려면 주소를 넘겨요:swap(&a, &b). - 배열은 함수에 넘기거나 식에서 쓰면 첫 칸을 가리키는 포인터로 바뀌어요(decay).
그래서p[i]는*(p + i)와 똑같아요. - 포인터 산술:
p + 1은 1바이트가 아니라 한 칸(sizeof(int), 보통 4바이트) 뒤예요.
같은 배열 안의 두 포인터를 빼면(m - arr) 칸 수, 곧 인덱스가 나와요. - 배열 끝 바로 다음(
p + n)까지는 가리켜도 되지만, 그 자리를 읽으면 안 돼요. - 아무것도 가리키지 않는 포인터는 NULL이에요.
예제의find_max는 칸이 0개면 NULL을 돌려주고, 부른 쪽은 확인한 다음에 읽어요.
언어별로 쓰는 법
| C++ | 포인터는 C와 같고 빈 포인터는 nullptr(C++11부터) — 부른 쪽을 바꿀 때는 참조 void swap(int& a, int& b)가 더 간단하고, 실제로는 std::swap(a, b).참조는 null이 될 수 없고 다른 대상으로 바꿀 수도 없는 별명이라, 포인터는 꼭 필요할 때만 써요 |
|---|---|
| C | int *p = &x;, *p로 읽고 쓰기 — 빈 포인터는 NULL.부른 쪽을 바꾸는 함수는 void swap(int *a, int *b), 배열을 받을 때는 int sum(const int *p, int n)처럼 길이를 따로 받아요 |
자주 하는 실수
NULL을 역참조했어요
int *m = find_max(arr, 0);
printf("%d\n", *m);→ 정의되지 않은 동작이라 보통 Segmentation fault로 죽어요. Java의 NullPointerException처럼 어디서 틀렸는지 알려 주지 않아요
if (m != NULL)로 먼저 확인한 다음에 *m을 읽어요.
지역 변수의 주소를 돌려줬어요
int *f(void) {
int x = 3;
return &x;
}→ x는 함수가 끝나면 사라져서, 받은 포인터는 없는 자리를 가리켜요(허상 포인터). GCC는 function returns address of local variable 경고를 내고, 실행하면 엉뚱한 값이 나오거나 죽어요
결과를 담을 자리는 부른 쪽이 마련한 배열이나, 다음 단원의 동적 메모리로 해요.
매개변수 배열의 길이를 sizeof로 쟀어요
int count(int arr[]) {
return sizeof(arr) / sizeof(arr[0]);
}→ 64비트 컴퓨터에서 8 / 4 = 2예요. 매개변수 int arr[]는 사실 int *arr라서 길이 정보가 사라져요(main 안의 int arr[5]에서는 5)
C는 길이 n을 따로 받고, C++는 길이를 함께 들고 다니는 std::vector나 std::span(C++20)을 넘겨요.
언어마다 다른 점
포인터를 역참조하기 전에 지금 살아 있는 무언가를 가리키는지 꼭 따져 보세요.
NULL, 이미 끝난 함수의 지역 변수, 배열 범위 밖을 가리키는 포인터를 역참조하면 C·C++는 오류 메시지 없이 엉뚱한 값을 내거나 죽어요.
포인터를 역참조하기 전에 지금 살아 있는 무언가를 가리키는지 꼭 따져 보세요.
NULL, 이미 끝난 함수의 지역 변수, 배열 범위 밖을 가리키는 포인터를 역참조하면 C·C++는 오류 메시지 없이 엉뚱한 값을 내거나 죽어요.