C 기본 문법 – 배열 – 3 – 포인터와 배열

C 기본 문법 – 배열 – 3편: 포인터와 배열 완전 정복

안녕하세요, 코딩 동지 여러분! 😄
오늘은 C 언어 학습의 본격적인 핵심 구간,
바로 **”포인터와 배열의 관계”**에 대해 정리해드릴게요.

🧠 C 언어에서 **포인터(pointer)**와 **배열(array)**은 뗄래야 뗄 수 없는 사이입니다.
둘은 서로 다른 개념이지만, 실제 사용 방식에서는 서로 긴밀하게 연결되어 있고, 종종 비슷하게 동작하죠.

“포인터는 주소를 저장하는 변수”
“배열의 이름은 포인터처럼 동작한다”
이런 말들, 헷갈리셨죠?
오늘 싹 정리해드릴게요! 🔍


1. 배열 이름 = 시작 주소?

int arr[5] = {10, 20, 30, 40, 50};

위에서 arr은 단순한 배열 이름이지만,
실제로는 arr 자체가 배열의 시작 주소를 가리키는 포인터처럼 동작합니다.

printf("%p
", arr);      // 배열의 시작 주소
printf("%d
", *arr);     // arr[0]과 같음 → 10

💡 arr == &arr[0]
배열의 이름은 배열 첫 번째 요소의 주소를 의미해요!


2. 포인터로 배열 순회하기

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // == &arr[0]

for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i));  // 포인터 연산으로 접근
}

비교: 배열 방식 vs 포인터 방식

배열 접근 방식 포인터 방식
arr[i] *(arr + i)
arr + i 주소 연산
&arr[i] arr + i

🎯 배열과 포인터는 인덱스 접근과 포인터 연산이 거의 동일하게 작동합니다!


3. 배열 함수에 전달 = 포인터 전달

예제:

void printArr(int *p, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", *(p + i));
    }
}
int main() {
    int nums[5] = {10, 20, 30, 40, 50};
    printArr(nums, 5);  // 배열 전달 == 포인터 전달
}

✅ 배열을 함수에 전달하면 자동으로 포인터로 변환됩니다!


4. 포인터를 이용한 배열 값 변경

void doubleValues(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        *(arr + i) *= 2;
    }
}

int main() {
    int nums[4] = {1, 2, 3, 4};
    doubleValues(nums, 4);

    for (int i = 0; i < 4; i++) {
        printf("%d ", nums[i]);
    }
}

🎯 포인터를 통해 함수 안에서 배열 요소를 직접 수정할 수 있어요!


5. 2차원 배열과 포인터

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

int *p = &matrix[0][0];
  • 2차원 배열은 실제로는 연속된 메모리 공간
  • 따라서 *p, *(p+1)… 로 순차적으로 접근 가능

예:

for (int i = 0; i < 6; i++) {
    printf("%d ", *(p + i));
}

6. 포인터 배열 vs 배열 포인터

이 둘은 다릅니다!

(1) 포인터 배열

char *strs[3] = {"apple", "banana", "grape"};
  • 문자열 포인터 3개가 들어있는 배열
  • 각 요소는 문자열을 가리키는 포인터

(2) 배열 포인터

int (*p)[3];  // 3개의 정수를 갖는 배열을 가리키는 포인터
  • 복잡해 보이지만, 포인터가 배열 전체를 가리킴

🧠 포인터 배열은 “많은 포인터를 담은 배열”
배열 포인터는 “하나의 배열을 가리키는 포인터”


7. 문자열과 포인터

char str1[] = "Hello";   // 배열
char *str2 = "World";    // 포인터
  • str1은 배열 이름 = 주소
  • str2는 문자열 리터럴을 가리키는 포인터

str1[0] == *str1
str2[0] == *str2
완전 같은 방식으로 사용 가능!


✅ 배열 vs 포인터 요약 비교

항목 배열 포인터
선언 int arr[5]; int *ptr;
메모리 고정 크기 연속 공간 동적으로 변경 가능
크기 sizeof(arr)는 전체 크기 sizeof(ptr)는 주소 크기
함수 전달 시 포인터로 자동 변환 직접 포인터 전달
접근 방법 arr[i] *(ptr + i)
메모리 할당 정적 동적 할당 가능 (malloc)

⚠️ 포인터와 배열 사용 시 주의할 점

주의사항 설명
배열 이름은 상수 arr++처럼 배열 이름에 연산 불가
포인터는 초기화 필수 선언 후 주소를 반드시 지정해야 함
인덱스 범위 초과 *(p + i) 사용 시 메모리 초과 주의
함수 인자 크기 정보 없음 포인터 전달 시 배열 크기 따로 전달해야 함
문자열 포인터 수정 금지 char *s = "text";는 읽기 전용 리터럴을 가리킴

마무리하며 💬

오늘은 배열과 포인터의 관계를 낱낱이 파헤쳐 봤어요!

포인터를 제대로 이해하면 배열을 유연하게 다룰 수 있고,
배열을 잘 이해하면 포인터 개념이 더욱 쉽게 와닿습니다.

함수와 배열의 관계, 메모리 주소와 연산, 문자열 처리까지
실전 개발에서 굉장히 중요한 내용들이에요.

🎯 “배열을 마스터하려면 포인터를 이해해야 한다”
이 말, 오늘로서 실감하셨죠? 😊

다음에는 포인터를 활용한 **동적 메모리 할당(malloc, free)**에 대해서도 다뤄볼게요!

끝까지 읽어주셔서 감사합니다!
포인터와 배열, 이젠 자신 있으시죠? 💻🔥
계속해서 함께 성장해봐요!

답글 남기기