배열 이름은 왜 주소처럼 보일까?
앞 장에서 우리는 포인터를 배웠습니다. 포인터는 주소를 저장하는 변수였고, &는 변수의 주소를 얻는 연산자였으며, *는 포인터가 가리키는 곳의 값을 읽거나 쓰는 연산자였습니다.
이제 그 포인터를 배열과 연결해 보겠습니다. 사실 C에서 배열을 깊이 이해하려면 포인터 감각이 꼭 필요합니다. 배열은 메모리에 연속해서 놓이고, 포인터는 메모리 주소를 다룹니다. 둘은 자연스럽게 만날 수밖에 없습니다.
앞에서 이런 코드를 보았습니다.
int scores[5] = {90, 85, 72, 100, 64};이 배열은 int 값 다섯 개를 메모리에 나란히 저장합니다.
scores[0] scores[1] scores[2] scores[3] scores[4]
90 85 72 100 64그런데 C에서 배열 이름 scores는 많은 상황에서 배열의 첫 번째 원소 주소처럼 동작합니다.
printf("%pn", (void *)scores);
printf("%pn", (void *)&scores[0]);두 줄은 보통 같은 주소를 출력합니다. scores가 배열의 시작 위치, 즉 scores[0]의 주소를 가리키는 것처럼 사용되기 때문입니다.
여기서 중요한 질문이 생깁니다.
"그렇다면 배열은 포인터와 같은 것일까?"
답은 조심스럽게 말해야 합니다. 배열과 포인터는 같지 않습니다. 다만 배열 이름은 많은 표현식에서 첫 번째 원소를 가리키는 포인터로 변환됩니다. 이 차이를 이해하는 것이 이번 장의 핵심입니다.

배열은 연속된 메모리다
배열을 다시 메모리 관점으로 보겠습니다.
int numbers[4] = {10, 20, 30, 40};int 하나가 4바이트인 환경이라고 가정하면, 배열은 대략 다음처럼 놓입니다. 실제 int 크기는 환경에 따라 달라질 수 있으므로 정확한 크기는 sizeof(int)로 확인해야 합니다.
주소 값
0x1000 numbers[0] = 10
0x1004 numbers[1] = 20
0x1008 numbers[2] = 30
0x100C numbers[3] = 40첫 번째 원소와 두 번째 원소 사이가 1바이트가 아니라 sizeof(int)만큼 떨어져 있다는 점이 중요합니다. 배열의 원소들은 같은 자료형이므로, 컴퓨터는 시작 주소와 원소 크기만 알면 원하는 인덱스의 위치를 계산할 수 있습니다.
numbers[i]의 위치 = 배열 시작 주소 + sizeof(int) × i그래서 배열 인덱스는 0부터 시작했습니다. numbers[0]은 시작 주소에서 0칸 떨어진 곳이고, numbers[1]은 시작 주소에서 1칸 떨어진 곳입니다.
이 구조를 코드로 확인해 봅시다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
for (int i = 0; i < 4; i++) {
printf("&numbers[%d] = %pn", i, (void *)&numbers[i]);
}
return 0;
}출력되는 주소 값은 실행 환경마다 다르지만, 각 주소가 일정한 간격으로 증가하는 것을 볼 수 있습니다. 그 간격은 보통 sizeof(int)와 같습니다.
배열 이름과 첫 번째 원소의 주소
배열 이름은 많은 상황에서 첫 번째 원소의 주소로 변환됩니다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
printf("numbers = %pn", (void *)numbers);
printf("&numbers[0] = %pn", (void *)&numbers[0]);
return 0;
}대부분의 환경에서 두 주소는 같습니다.
numbers = 0x...
&numbers[0] = 0x...따라서 다음과 같이 포인터에 배열 이름을 저장할 수 있습니다.
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;이 코드는 다음과 같은 의미입니다.
int *p = &numbers[0];p는 numbers[0]을 가리킵니다.
p ─────→ numbers[0] numbers[1] numbers[2] numbers[3]
10 20 30 40그래서 *p는 첫 번째 원소의 값입니다.
printf("%dn", *p); // 10여기까지 보면 배열 이름과 포인터가 완전히 같은 것처럼 보일 수 있습니다. 하지만 곧 차이가 드러납니다.
배열과 포인터는 같지 않다
다음 코드를 봅시다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;
printf("sizeof(numbers) = %zun", sizeof(numbers));
printf("sizeof(p) = %zun", sizeof(p));
return 0;
}sizeof(numbers)는 배열 전체의 크기입니다. int가 4바이트라면 4 × 4 = 16바이트가 나올 수 있습니다.
반면 sizeof(p)는 포인터 변수 하나의 크기입니다. 64비트 환경에서는 보통 8바이트, 32비트 환경에서는 보통 4바이트일 수 있습니다.
즉, numbers는 실제 배열이고, p는 그 배열의 첫 번째 원소 주소를 저장한 포인터 변수입니다. 둘은 메모리에서 차지하는 의미가 다릅니다.
또 하나의 차이도 있습니다.
int numbers[4] = {10, 20, 30, 40};
int other[4] = {1, 2, 3, 4};
int *p = numbers;
p = other; // 가능: p가 다른 배열의 첫 원소를 가리키게 함
// numbers = other; // 오류: 배열 이름에는 대입할 수 없음포인터 변수 p에는 다른 주소를 다시 저장할 수 있습니다. 하지만 배열 이름 numbers는 배열 전체를 대표하는 이름이지, 값을 바꿔 넣을 수 있는 포인터 변수가 아닙니다.
정리하면 이렇게 볼 수 있습니다.
배열은 실제 원소들이 연속해서 놓인 공간입니다.
포인터는 주소를 저장하는 변수입니다.
배열 이름은 많은 표현식에서 첫 번째 원소의 주소처럼 변환됩니다.
하지만 배열 자체와 포인터 변수는 같은 것이 아닙니다.
이 차이를 기억해 두면 sizeof, 함수 매개변수, 문자열 처리에서 헷갈림이 줄어듭니다.
포인터 연산: 한 바이트가 아니라 한 원소씩 이동한다
포인터에는 덧셈과 뺄셈을 할 수 있습니다. 이것을 포인터 연산이라고 합니다.
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;이때 p는 numbers[0]을 가리킵니다.
printf("%dn", *p); // 10
printf("%dn", *(p + 1)); // 20
printf("%dn", *(p + 2)); // 30여기서 p + 1은 주소 값을 숫자 1만큼 더한다는 뜻이 아닙니다. p가 int *이므로, p + 1은 int 한 칸 뒤로 이동합니다. 만약 int가 4바이트라면 실제 주소는 4바이트 뒤로 이동합니다.
p → numbers[0]
p + 1 → numbers[1]
p + 2 → numbers[2]C는 포인터의 자료형을 보고 몇 바이트씩 이동해야 하는지 압니다.
char chars[4] = {'A', 'B', 'C', 'D'};
char *cp = chars;char는 보통 1바이트이므로 cp + 1은 보통 1바이트 뒤를 가리킵니다. 반면 double * 포인터라면 p + 1은 double 한 칸, 보통 8바이트 뒤를 가리킬 수 있습니다.
이것이 포인터 선언에서 자료형이 중요한 이유입니다.
int *p;이 선언은 단순히 "p는 주소를 저장한다"만 말하지 않습니다. "p가 가리키는 곳에는 int가 있고, p를 한 칸 이동하면 int 크기만큼 이동한다"는 정보도 담고 있습니다.
arr[i]와 *(arr + i)
C에서 배열 인덱스 표현은 포인터 연산과 깊이 연결되어 있습니다.
numbers[i]이 표현은 개념적으로 다음과 비슷합니다.
*(numbers + i)배열 이름 numbers가 첫 번째 원소 주소처럼 사용되고, 거기에 i칸 이동한 뒤, *로 그 위치의 값을 읽는 것입니다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
for (int i = 0; i < 4; i++) {
printf("numbers[%d] = %d, *(numbers + %d) = %dn",
i, numbers[i], i, *(numbers + i));
}
return 0;
}출력해 보면 numbers[i]와 *(numbers + i)가 같은 값을 가리킨다는 것을 확인할 수 있습니다.
그렇다고 평소에 *(numbers + i)를 더 많이 쓰라는 뜻은 아닙니다. 배열을 다룰 때는 numbers[i]가 훨씬 읽기 쉽습니다. 다만 내부적으로 이런 관계가 있다는 사실을 알면 포인터와 배열이 왜 자주 함께 등장하는지 이해할 수 있습니다.
포인터 변수도 배열처럼 사용할 수 있습니다.
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;
printf("%dn", p[0]); // 10
printf("%dn", p[1]); // 20
printf("%dn", p[2]); // 30p[1]은 *(p + 1)과 같은 의미입니다. 그래서 동적 메모리 할당으로 얻은 포인터도 배열처럼 사용할 수 있었습니다.
int *scores = malloc(sizeof(int) * count);
scores[0] = 90;
scores[1] = 85;scores는 배열 이름은 아니지만, 연속된 int 공간의 시작 주소를 가리키는 포인터이므로 scores[i]처럼 사용할 수 있습니다.
포인터로 배열 순회하기
배열은 보통 인덱스로 순회합니다.
int numbers[4] = {10, 20, 30, 40};
for (int i = 0; i < 4; i++) {
printf("%dn", numbers[i]);
}포인터로도 순회할 수 있습니다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;
for (int i = 0; i < 4; i++) {
printf("%dn", *(p + i));
}
return 0;
}또는 포인터 자체를 이동시킬 수도 있습니다.
#include <stdio.h>
int main(void)
{
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;
int *end = numbers + 4;
while (p < end) {
printf("%dn", *p);
p++;
}
return 0;
}end는 마지막 원소 바로 다음 위치를 가리킵니다. 이 위치를 역참조하면 안 됩니다. 하지만 반복의 끝을 표시하는 경계로는 사용할 수 있습니다.
numbers → numbers[0]
numbers + 4 → 마지막 원소 다음 위치이런 방식은 C 라이브러리나 시스템 코드에서 자주 볼 수 있습니다. 다만 초보 단계에서는 인덱스 방식이 더 읽기 쉬운 경우가 많습니다. 포인터 순회는 배열이 연속된 메모리라는 사실을 이해하기 위한 도구로 먼저 받아들이면 됩니다.
배열을 함수에 전달하면 무슨 일이 생길까?
배열을 함수에 전달하는 코드를 다시 보겠습니다.
int sum_array(int arr[], int count)
{
int sum = 0;
for (int i = 0; i < count; i++) {
sum += arr[i];
}
return sum;
}매개변수에 int arr[]라고 적혀 있으니 배열 전체가 함수로 복사되는 것처럼 보일 수 있습니다. 하지만 그렇지 않습니다. C에서 배열을 함수에 전달하면 배열 전체가 복사되지 않습니다. 함수에는 배열의 첫 번째 원소 주소가 전달됩니다.
다음 두 선언은 함수 매개변수에서는 사실상 같은 의미입니다.
int sum_array(int arr[], int count);
int sum_array(int *arr, int count);둘 다 arr은 int를 가리키는 포인터 매개변수입니다. 그래서 함수 안에서 sizeof(arr)를 사용하면 배열 전체 크기가 아니라 포인터 크기가 나옵니다.
#include <stdio.h>
void print_size(int arr[])
{
printf("sizeof(arr) in function = %zun", sizeof(arr));
}
int main(void)
{
int numbers[5] = {1, 2, 3, 4, 5};
printf("sizeof(numbers) in main = %zun", sizeof(numbers));
print_size(numbers);
return 0;
}main 안에서 sizeof(numbers)는 배열 전체 크기입니다. 하지만 print_size 안에서 sizeof(arr)는 포인터 크기입니다.
그래서 배열을 함수에 넘길 때는 원소 개수도 함께 넘겨야 합니다.
void print_array(int arr[], int count)
{
for (int i = 0; i < count; i++) {
printf("%dn", arr[i]);
}
}함수 안의 arr만 보고는 원래 배열에 원소가 몇 개인지 알 수 없습니다. C는 배열의 크기를 자동으로 함께 전달하지 않습니다. 이것이 C 배열을 사용할 때 중요한 책임 중 하나입니다.
함수에서 배열 원소 바꾸기
배열을 함수에 전달하면 첫 번째 원소 주소가 전달됩니다. 따라서 함수 안에서 배열 원소를 바꾸면 원본 배열도 바뀝니다.
#include <stdio.h>
void set_zero(int arr[], int count)
{
for (int i = 0; i < count; i++) {
arr[i] = 0;
}
}
int main(void)
{
int numbers[5] = {1, 2, 3, 4, 5};
set_zero(numbers, 5);
for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
printf("n");
return 0;
}출력은 다음과 같습니다.
0 0 0 0 0set_zero 함수 안에서 바뀐 것은 복사본 배열이 아닙니다. 함수는 원본 배열의 시작 주소를 받아 그 주소를 통해 원소에 접근합니다. 그래서 원본 배열의 내용이 바뀝니다.
이 점은 함수의 일반 변수 전달과 다릅니다.
void change(int n)
{
n = 100;
}일반 int 값은 복사되어 전달되므로 함수 안에서 바꿔도 원본이 바뀌지 않습니다. 하지만 배열은 함수에 전달될 때 첫 원소 주소로 전달되므로, 원소를 수정하면 원본 배열에 영향을 줍니다.
원본을 바꾸지 않겠다는 의도를 표현하고 싶다면 const를 사용할 수 있습니다.
void print_array(const int arr[], int count)
{
for (int i = 0; i < count; i++) {
printf("%dn", arr[i]);
}
}const int arr[]는 함수 안에서 배열 원소를 바꾸지 않겠다는 약속입니다. 다음 코드는 컴파일 오류가 됩니다.
void print_array(const int arr[], int count)
{
arr[0] = 10; // 오류
}읽기만 하는 함수라면 const를 붙이는 습관이 좋습니다. C에서는 포인터를 통해 원본 값을 바꿀 수 있으므로, 바꾸지 않는다는 약속을 코드에 적어 주는 것이 중요합니다.
문자열과 포인터
C 문자열은 널 문자 0으로 끝나는 char 배열이라고 배웠습니다.
char name[] = "Mina";메모리에는 다음처럼 저장됩니다.
'M' 'i' 'n' 'a' '0'배열 이름 name은 많은 상황에서 첫 번째 문자 주소처럼 사용됩니다.
char *p = name;이제 p는 문자열의 첫 문자를 가리킵니다.
printf("%cn", *p); // M
printf("%cn", *(p + 1)); // i
printf("%sn", p); // Mina%s는 포인터가 가리키는 위치에서 시작해 0을 만날 때까지 문자를 출력합니다. 그래서 p를 넘겨도 문자열이 출력됩니다.
#include <stdio.h>
int main(void)
{
char word[] = "Hello";
char *p = word;
printf("%sn", p);
printf("%sn", p + 1);
printf("%sn", p + 2);
return 0;
}출력은 다음과 같습니다.
Hello
ello
llop + 1은 두 번째 문자 e의 주소입니다. %s는 그 위치부터 0까지 출력하므로 ello가 나옵니다.
이 예제는 문자열 함수들이 왜 주소를 받는지 보여 줍니다. C에서 문자열은 내용 전체가 함수로 복사되어 전달되는 값이 아니라, 시작 주소와 0이라는 끝 표시로 다루는 데이터입니다.
문자 배열과 문자열 리터럴 포인터
다음 두 코드는 비슷해 보이지만 차이가 있습니다.
char a[] = "Hello";
char *p = "Hello";a는 문자 배열입니다. 배열 안에는 H, e, l, l, o, 0이 복사되어 들어갑니다. 따라서 배열 원소를 바꿀 수 있습니다.
char a[] = "Hello";
a[0] = 'Y';
printf("%sn", a); // Yello반면 p는 문자열 리터럴의 시작 주소를 가리키는 포인터입니다.
char *p = "Hello";문자열 리터럴은 수정하면 안 됩니다. 다음 코드는 위험하며 정의되지 않은 동작입니다.
char *p = "Hello";
p[0] = 'Y'; // 잘못된 코드그래서 문자열 리터럴을 가리킬 때는 보통 const char *를 사용하는 편이 좋습니다.
const char *p = "Hello";이제 p[0] = 'Y'; 같은 실수를 컴파일러가 막아 줄 수 있습니다.
문자열을 수정해야 한다면 배열을 사용하세요.
char text[20] = "Hello";
text[0] = 'Y';문자열을 읽기만 한다면 const char *를 사용하세요.
void print_message(const char *message)
{
printf("%sn", message);
}이 함수는 문자열 내용을 바꾸지 않겠다는 약속을 합니다. 문자열 리터럴도, 문자 배열도 안전하게 넘길 수 있습니다.
문자열 비교에서 ==가 위험한 이유
문자열 장에서 우리는 문자열 비교에 strcmp를 써야 한다고 배웠습니다. 이제 그 이유를 포인터 관점에서 볼 수 있습니다.
char a[] = "cat";
char b[] = "cat";
if (a == b) {
printf("같습니다.n");
}이 코드는 문자열 내용이 같은지 비교하지 않습니다. a와 b는 각각 다른 배열의 시작 주소처럼 사용됩니다. 따라서 a == b는 두 배열의 시작 주소가 같은지 비교합니다. 두 배열은 내용이 같아도 서로 다른 메모리 공간에 있으므로 보통 같지 않습니다.
문자열 내용 비교는 다음처럼 해야 합니다.
#include <string.h>
if (strcmp(a, b) == 0) {
printf("내용이 같습니다.n");
}strcmp는 두 문자열의 시작 주소를 받아서, 각 위치의 문자를 하나씩 비교합니다. 둘 다 0에 도달할 때까지 모든 문자가 같으면 0을 반환합니다.
이제 strcmp가 왜 포인터를 받는지도 이해할 수 있습니다. 문자열은 배열 전체가 통째로 전달되는 것이 아니라, 첫 문자의 주소를 통해 다루어지기 때문입니다.
이중 포인터 맛보기
포인터를 배우다 보면 int **pp 같은 표현도 만납니다. 이것을 이중 포인터라고 부릅니다. 말 그대로 포인터를 가리키는 포인터입니다.
처음에는 어렵게 느껴지지만 원리는 같습니다.
int value = 10;
int *p = &value;
int **pp = &p;그림으로 보면 다음과 같습니다.
pp ─────→ p ─────→ value
[주소] [10]p는 value의 주소를 저장합니다. pp는 포인터 변수 p 자신의 주소를 저장합니다.
printf("%dn", value); // 10
printf("%dn", *p); // 10
printf("%dn", pp); // 10*pp는 pp가 가리키는 곳, 즉 p입니다. pp는 p가 가리키는 곳, 즉 value입니다.
이중 포인터는 언제 필요할까요? 대표적으로 함수 안에서 포인터 변수 자체를 바꾸고 싶을 때 필요합니다.
#include <stdio.h>
#include <stdlib.h>
int allocate_int(int **out)
{
int *p = malloc(sizeof(int));
if (p == NULL) {
return 0;
}
*p = 100;
*out = p;
return 1;
}
int main(void)
{
int *value = NULL;
if (allocate_int(&value)) {
printf("%dn", *value);
free(value);
}
return 0;
}allocate_int 함수는 main의 포인터 변수 value에 새 주소를 넣어야 합니다. 일반 변수 값을 바꾸려면 그 변수의 주소를 넘겼듯이, 포인터 변수 값을 바꾸려면 포인터 변수의 주소를 넘깁니다.
allocate_int(&value);value의 자료형은 int *입니다. 따라서 &value의 자료형은 int **입니다.
지금은 이중 포인터를 완벽하게 익힐 필요는 없습니다. 다만 "포인터 자체도 변수이고, 그 변수의 주소도 가질 수 있다"는 사실을 기억해 두면 됩니다. 동적 배열을 함수 안에서 확장하거나, 연결 리스트의 머리 포인터를 바꿀 때 이 개념이 다시 등장합니다.
포인터 실수 모음
포인터와 배열이 만나는 지점에서는 실수가 자주 나옵니다. 대표적인 패턴을 정리해 봅시다.
첫 번째는 배열 범위를 넘는 포인터 연산입니다.
int arr[3] = {1, 2, 3};
int *p = arr + 3;
printf("%dn", *p); // 잘못된 접근arr + 3은 마지막 원소 다음 위치입니다. 반복문의 경계로 사용할 수는 있지만, 역참조하면 안 됩니다.
두 번째는 함수 안에서 sizeof로 배열 크기를 구하려는 실수입니다.
void print_array(int arr[])
{
size_t count = sizeof(arr) / sizeof(arr[0]); // 잘못된 생각
}함수 매개변수 arr은 포인터입니다. 원래 배열 전체 크기를 알 수 없습니다. 배열 크기는 별도 인자로 받아야 합니다.
void print_array(int arr[], int count)
{
for (int i = 0; i < count; i++) {
printf("%dn", arr[i]);
}
}세 번째는 문자열 리터럴을 수정하려는 실수입니다.
char *p = "Hello";
p[0] = 'Y'; // 위험수정할 문자열은 배열로 만들어야 합니다.
char text[] = "Hello";
text[0] = 'Y';읽기 전용으로 사용할 문자열 리터럴은 const char *로 받는 편이 좋습니다.
const char *message = "Hello";네 번째는 포인터가 가리키는 대상의 수명을 잊는 것입니다.
int *bad(void)
{
int local = 10;
return &local; // 위험
}local은 함수가 끝나면 사라지는 지역 변수입니다. 그 주소를 밖으로 반환하면 더 이상 유효하지 않은 주소를 들고 있게 됩니다.
다섯 번째는 포인터와 포인터가 가리키는 값을 혼동하는 것입니다.
p = &arr[0]; // p에 주소를 저장
*p = 10; // p가 가리키는 곳의 값을 변경p는 주소이고, *p는 그 주소에 있는 값입니다. 이 둘을 계속 구분해야 합니다.
배열과 포인터를 읽는 질문
배열과 포인터 코드를 읽을 때는 다음 질문을 차례대로 던져 보세요.
이 이름은 실제 배열인가, 포인터 변수인가?
이 포인터는 어떤 자료형의 원소를 가리키는가?
p + 1은 몇 바이트가 아니라 몇 원소 이동하는가?이 포인터를 역참조해도 유효한 범위 안인가?
함수에 배열을 넘길 때 원소 개수도 함께 넘겼는가?
이 문자열은 수정 가능한 배열인가, 읽기 전용 문자열 리터럴인가?
이 질문들은 조금 귀찮아 보입니다. 하지만 C에서 메모리 오류를 줄이는 가장 좋은 방법은 질문을 습관으로 만드는 것입니다. 포인터는 "주소를 따라가는 도구"입니다. 주소를 따라가기 전에 그 주소가 어디를 가리키는지 확인해야 합니다.
이번 장에서 기억할 것
이번 장에서는 포인터와 배열의 관계를 살펴보았습니다. 배열은 같은 자료형의 값이 연속된 메모리에 놓인 구조이고, 배열 이름은 많은 상황에서 첫 번째 원소의 주소처럼 사용됩니다.
핵심을 정리해 봅시다.
배열 이름은 많은 표현식에서
&배열[0]처럼 첫 번째 원소 주소로 변환됩니다.배열 자체와 포인터 변수는 같지 않습니다.
sizeof와 대입 가능 여부에서 차이가 드러납니다.포인터 연산에서
p + 1은 한 바이트가 아니라p가 가리키는 자료형 한 칸만큼 이동합니다.arr[i]는 개념적으로*(arr + i)와 연결됩니다.배열을 함수에 전달하면 배열 전체가 복사되지 않고 첫 번째 원소 주소가 전달됩니다.
함수 안에서는 원래 배열 크기를 알 수 없으므로 원소 개수를 함께 전달해야 합니다.
문자열은
char배열이며,%s와 문자열 함수들은 시작 주소에서0까지 읽습니다.문자열 리터럴은 수정하지 말고, 읽기 전용 문자열은
const char *로 다루는 습관이 좋습니다.포인터가 가리키는 범위와 대상의 수명을 항상 확인해야 합니다.
포인터와 배열은 C에서 가장 많이 헷갈리는 주제 중 하나입니다. 하지만 둘을 메모리 그림으로 보면 훨씬 단순해집니다. 배열은 값들이 줄 서 있는 공간이고, 포인터는 그 줄의 어느 칸을 가리키는 주소입니다.
다음 장에서는 이 감각을 더 넓혀 프로그램 전체의 메모리 구조를 살펴봅니다. 코드 영역, 데이터 영역, 스택, 힙이라는 큰 그림 속에서 변수, 배열, 포인터, 동적 메모리가 각각 어디에 놓이는지 확인해 보겠습니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.
