[★C] 배열과 포인터

방법이있지·2025년 6월 8일
post-thumbnail

배열명은 컴파일 과정에서 해당 배열의 첫 번째 요소의 주소로 해석되며, 포인터 연산을 통해 나머지 요소에도 접근할 수 있습니다.

배열명

배열명은 첫 번째 요소의 주소

  • 배열은 자료형이 같은 변수를 메모리에 연속으로 할당
    • e.g., int arr[3]이 메모리 100번지부터 할당되면, 각 요소의 주소는 100, 104, 108번지
  • 컴파일 과정에서 배열명은 첫번째 배열 요소의 주소로 해석
    • 이를 배열의 시작 주소라고도 함
    • e.g., arr은 첫 배열 요소의 주소 100으로 해석됨

포인터 연산 (주소의 정수 덧셈)

  • 주소에 정수를 더하면 아래 식대로 연산이 이루어지며, 연산 결과도 주소임
    • 주소 + 정수 -> 주소 + (정수 * 주소에 위치한 변수의 크기)
    • e.g., int형 변수 a의 주소 100번지에 1을 더하면
    • &a + 1 -> 100 + 1 * 4 -> 104
  • 포인터에 정수를 더해도 마찬가지
    • 포인터 + 정수 -> 포인터가 가리키는 주소 + (정수 * 포인터가 가리키는 자료형의 크기)`
int arr[3];

*(arr + 0) = 5;                  // arr[0] = 5
*(arr + 1) = *(arr + 0) + 5;     // arr[1] = arr[0] + 5
*(arr + 2) = 15;                // scanf("%d", &arr[2])

// arr[0], arr[1], arr[2] 출력
for (int i = 0; i < 3; i++){
    printf("%3d", *(arr + i));
}

// [출력]   5 10 15

  • arr이 메모리 100번지부터 할당되었다고 가정하면
    • int는 4바이트, 각 요소는 100, 104, 108번지에 할당됨
  • 배열명 arr의 값은 100 (첫번째 요소의 주소)
    • int는 4바이트므로, 1을 더할 때마다 주소가 4만큼 증가
    • *(arr + 0) -> *100 -> 첫번째 요소 arr[0]
    • *(arr + 1) -> *104 -> 두번째 요소 arr[1]
    • *(arr + 2) -> *108 -> 세번째 요소 arr[2]
  • arr[i]*(arr + i)와 동일한 표현
  • 배열 범위를 벗어난 포인터 연산도 가능하나, 함부로 쓰지 말 것

배열명처럼 포인터 사용하기

  • 배열명은 첫 번째 요소의 주소이므로, 포인터에 저장할 수 있음
int arr[3];
int *pa = arr;          // 포인터 pa에 배열명 arr 저장
*pa = 5;                // arr[0]에 5 대입
*(pa + 1) = 10;         // arr[1]에 10 대입
pa[2] = pa[0] + pa[1];  // arr[2]에 5 + 10 = 15 대입

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

// [출력]  5 10 15

  • int*형 포인터 pa에 배열명 arr 저장
    • pa는 배열 arr의 첫 요소를 가리킴
  • 포인터 pa에도 주소 연산, 대괄호 인덱싱 사용 가능
    • e.g., pa[1], *(pa + 1), (arr + 1), arr[1]은 모두 arr의 두번째 요소를 나타냄

배열명 vs 포인터

  • sizeof 연산
    • sizeof(배열명): 배열 전체의 크기
    • sizeof(포인터): 포인터 하나의 크기 (8바이트 고정)
int arr[3];
int* pa = arr;
printf("%d\n", sizeof(arr));    // 12 (4바이트 * 3)
printf("%d\n", sizeof(pa));     // 8 (포인터는 8바이트)
  • 배열명은 주소 (상수): 값을 바꿀 수 없음
    • arr++ 같은 연산 불가능
  • 포인터는 변수: 값, 즉 가리키는 위치를 바꿀 수 있음
int arr[3] = {42, 13, 9};
int* pa = arr;              // arr의 첫 요소 가리키게 초기화

for (int i = 0; i < 3; i++){
    printf("%4d", *pa);     // 현재 가리키는 값 출력
    pa++;                   // 실제로는 다음 배열 요소를 가리키도록 4 증가
}

// [출력]  42  13   9
  • 포인터는 변수이기 때문에, 값을 변경하며 위와 같은 코드를 실행할 수 있음
  • *pa: pa가 가리키는 값
    • paarr의 첫번째 요소를 가리키도록 초기화
    • pa++: 1을 증가시킬 때마다 주소가 4 증가하여 다음 요소를 가리킴

포인터 간 뺄셈과 관계 연산

  • 두 포인터가 가리키는 자료형이 같으면, 포인터 간 뺄셈 / 관계 연산 가능
  • 뺄셈: 주소의 차 / 가리키는 자료형의 크기
  • 관계 연산: 주소의 대소 비교
int arr[5] = {11, 22, 33, 44, 55};
int* pa = arr;      // arr[0]을 가리킴
int* pb = arr + 3;  // arr[3]을 가리킴
printf("pb - pa: %d\n", pb - pa);
// [출력] pb - pa = 3

printf("앞의 값: %d", pa < pb ? *pa : *pb);
// [출력] 앞의 값: 11

  • 뺄셈은 배열 요소 간 인덱스 차이를 계산한다고 볼 수 있음
    • arr[3]을 가리키는 pb에서 arr[0]을 가리키는 pa를 빼면 (112 - 100) // 4 = 3

배열을 처리하는 함수

  • 함수로 배열을 처리하기 위해선 배열명을 인수로 전달해야 하며, 이를 위해 포인터 매개변수가 필요함
  • 배열명으로 배열의 첫 요소의 주소만 알면, 나머지 요소는 포인터 연산으로 접근할 수 있음

예제

  • 배열의 값을 입력받고, 최댓값을 출력하는 함수
#include <stdio.h>

void input_array(double *pa, int size);
double find_max(double *pa, int size);

int main(void){
    double arr[4];
    double max;
    int length = sizeof(arr) / sizeof(arr[0]);
    input_array(arr, length);       // 값 입력
    max = find_max(arr, length);    // 최댓값 찾고 저장
    printf("최댓값: %.1lf\n", max);

    return 0;
}

// double* 포인터를 매개변수로 선언 -> 배열명을 받기 위해
void input_array(double *pa, int length){
    printf("%d개의 실수 값 입력: ", length);
    for (int i = 0; i < length; i++){
        scanf("%lf", pa + i);       // 입력할 배열 요소의 주소 전달
    }
}

// double* 포인터를 매개변수로 선언 -> 배열명을 받기 위해
double find_max(double *pa, int length){
    double max;
    max = pa[0];

    for (int i = 1; i < length; i++){
        if (pa[i] > max) max = pa[i];
    }

    return max;
}

// [입력] 5개의 실수값 입력: 3.4 6.7 2.5 7.3 4.5
// [출력] 배열의 최댓값: 7.3
  • 배열명 arr는 배열의 첫번째 요소의 주소
    • 함수에서 double *형 포인터 매개변수로 받음
  • pa[i]로 배열 요소에 접근 - *(pa + i)와 같은 의미
  • scanf에서 입력받을 때는 주소가 필요하므로, 배열 요소의 주소를 pa + i로 넘겨줌
    • pa + i 자체가 주소이므로, &는 붙이지 않아도 됨
    • &pa[i]로 넘겨줘도 결과는 같음

🤔 input_array, find_max가 아니라 main에서 배열의 길이를 계산한 이유가 있나요?

  • 함수 밖 sizeof(arr)은 배열의 크기를 반환하지만,
  • 함수 안 sizeof(pa)는 포인터의 크기인 8바이트만을 반환하므로 배열의 길이를 계산할 수 없습니다.

연습문제

1~45 중에 6개의 서로 다른 수를 배열에 입력하고 출력합니다. 입력한 수가 이미 저장된 수와 같으면 에러 메시지를 출력하고 다시 입력합니다.

  • input_nums: int *lotto_nums 포인터를 사용해서, 주소 lotto_nums + i에 값을 입력
    • 기본적으로 flag은 1. 1~45 사이 값이 아니거나 중복값인 경우, flag를 0으로 설정
    • 이후 조건문에서 flag == 0인 경우 i가 1 줄어듦
  • print_nums: int *lotto_nums 포인터를 사용해서, lotto_nums[i]로 값을 출력
#include <stdio.h>

void input_nums(int *lotto_nums);
void print_nums(int *lotto_nums);

int main(void){
    int lotto_nums[6];
    input_nums(lotto_nums);
    print_nums(lotto_nums);
    return 0;
}

void input_nums(int* lotto_nums){
    for (int i = 0; i < 6; i++){
        int flag;

        flag = 1;   // 올바른 입력이 아닐 시 0
        printf("번호 입력: ");
        scanf("%d", lotto_nums + i);

        if (lotto_nums[i] < 1 || 45 < lotto_nums[i]){
            flag = 0;
            printf("수는 1~45 사이 값이여야 합니다!\n");
        }
        for (int j = 0; j < i; j++){
            if (lotto_nums[i] == lotto_nums[j]){
                flag = 0;
                printf("같은 번호가 있습니다!\n");
                break;
            }
        }

        // 올바른 입력이 아닐 때만 실행됨
        if (flag) i--;
    }
}

void print_nums(int* lotto_nums){
    printf("로또 번호: ");
    for (int i = 0; i < 6; i++){
        printf("%-4d", lotto_nums[i]);
    }
}

// [실행결과]
// 번호 입력: 3
// 번호 입력: 7
// 번호 입력: 15
// 번호 입력: 3
// 같은 번호가 있습니다!
// 번호 입력: 22
// 번호 입력: 35
// 번호 입력: -5
// 수는 1~45 사이 값이여야 합니다!
// 번호 입력: 100
// 수는 1~45 사이 값이여야 합니다!
// 번호 입력: 40
// 로또 번호: 3   7   15  22  35  40
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글