본문 바로가기
Tech/Coding

C언어 고급] 🚀배열과 포인터

by redcubes 2024. 3. 28.

목차

A.  배열과 포인터의 관계

B.  배열을 처리하는 함수

C.  정리 하기

A.  배열과 포인터의 관계

-컴파일러는 첫 번째 배열의 주소를 쉽게 사용하도록 배열명을 컴파일 과정에서 첫 배열 요소 주소로 바꿈.
자료형에 따라 첫 주소에서 크기만큼 건너뛰며 요소에 접근 가능.

____1. 배열명으로 배열 요소 사용하기

- 주소는 정수처럼 보이지만 자료형 정보를 갖고 있는 특별한 값. = 자유로운 연산은 할 수 없고 정해진 연산만 가능

주소 + 정수 -> 주소 + (정수 *  주소를 구한 변수의 크기) 
크기가 4바이트인 int형 변수 a의 주소 100에 1을 더하면 104가 됨.

&a 가100이면 &a + 1은 104(int사이즈가 4일 때)

*(ary + 0) == *(100) == ary[0], *(ary + 1) == *(104) == ary[1],
&ary[2] 는 ary + 2가 연산 과정을 줄일 수 있음.

즉 배열의 대괄호는 포인터 연산의 '간접참조, 괄호, 더하기' 연산기능을 함.  (배열 요소의 대괄호는 연산식이다.)

배열 영역을 벗어나는 포인터 연산은 가능하지만 쓰면 안 된다.

____2. 배열명 역할을 하는 포인터

- 배열명은 주소이므로 포인터에 저장 가능.

int ary[3];

int *pa = ary; // 배열명은 주소다. 첫 번째 요소를 가리킨다.

*pa = 10;*(pa+1) = 20; 은 ary[0]=10; ary[1]=20;과 같다.

____3. 배열명과 포인터의 차이

sizeof 연산의 결과가 다름. 배열명에 사용하면 전체 배열의 크기를 구하고  포인터에 사용하면 포인터 하나의 크기를 구함.

  1. 번수와 상수 차이. 포인터는 가변 변수. 배열명은 상수. 더할 수 있지만 재 저장은 안됨.
  2. pa++는 되지만 arr++는 안 됨.

 - 포인터의 변수적 특징을 활용하는 방법.

 

printf("%d", pa[0]); // pa를 배열명처럼 써서 첫 요소 출력 

printf("%d", *(pa + 0)); // pa[0]를 그대로 포인터 연산식으로 바꾸는 방법

printf("%d", *pa); // 의미 없는 0과 괄호 제거하는 방법.

pa = pa + 1;이나 pa++;로 연속 엑세스 가능.

혼자공부하는 C -한빛

주의사항!!

포인터의 값이 변할 수 있어 값이 유효한지 확인 필요

포인테어 증가연산자와 간접 참조 연산자를 함께 쓸 때 전위 표현 사용하면 안됨. 첫 요소를 못 읽고 마지막에 쓰레기값 하나 읽음.

++(*pa)라고 쓰면 첫 번째 요소의 값이 1씩 증가.

____4. 포인터의 뺄셈과 관계 연산

- 포인터 빼기 포인터는 ? (값의 차 / 가리키는 자료형의 크기)
- 관계 연산자로 대소 비교도 가능.

#include <stdio.h>

int main(void)
{
	int ary[5] = { 10, 20, 30, 40, 50 };
	int* pa = ary;                         // 첫 번째 배열 요소 주소
	int* pb = pa + 3;                      // 네 번째 배열 요소 주소

	printf("pa : %u\n", pa);
	printf("pb : %u\n", pb);
	pa++;                                  // pa를 다음 배열 요소로 이동
	printf("pb - pa : %u\n", pb - pa);     // 두 포인터의 뺄셈

	printf("앞에 있는 배열 요소의 값 출력 : ");
	if (pa < pb) printf("%d\n", *pa);      // pa가 배열의 앞에 있으면 *pa 출력
	else printf("%d\n", *pb);              // pb가 배열의 앞에 있으면 *pb 출력

	return 0; 
}

printf("pb - pa : %u\n", pb - pa);     // 두 포인터의 뺄셈

pb - pa → (40 - 32) / sizeof(int) → 8 / 4 → 2

관계 연산자로 순서 확인 가능.

 

B.배열을 처리하는 함수

- 함수로 배열을 처리하려면 포인터가 필요. 포인터로 첫 요소의 주소를 주면 함수가 모든 배열 요소 계산 가능. 
(예전에 c++로 주변 환경 정보를 스캔해서 배열로 다루는 자율주행차를 만들 때 배열을 넘겨주는 방법을 알아보니 포인터로 넘겨야 한다고 해서 왜 이러는지 이해가 안 갔는데 이제는 이해가 간다.)

____1. 배열의 값을 출력하는 함수

- 배열의 값 확인을 위해 수시로 출력해야 할 때 모든 배열 내용을 인자로 넘기기보다 시작 주소를 주면 포인터 연산으로  해결 가능. 함수를 호출할 때 배열명을 주고 매개변수로포인터 선언. (함수 안에서 포인터를 배열명처럼 사용하면 된다.)

____2. 배열 요소의 개수가 다른 배열도 출력하는 함수

- 요소 수가 가변적일 때.

#include <stdio.h>

void print_ary(int* pa, int size);   // 함수 선언, 매개변수 2개

int main(void)
{
	int ary1[5] = { 10, 20, 30, 40, 50 };           // 배열 요소의 개수가 5개인 배열
	int ary2[7] = { 10, 20, 30, 40, 50, 60, 70 };   // 요소의 개수가 7개인 배열

	print_ary(ary1, 5);              // ary1 배열 출력, 배열 요소의 개수 전달
	printf("\n");
	print_ary(ary2, 7);              // ary2 배열 출력, 배열 요소의 개수 전달

	return 0;
}

void print_ary(int* pa, int size)    // 배열명과 배열 요소의 개수를 받는 매개변수 선언
{
	int i;
	
	for (i = 0; i < size; i++)       // size의 값에 따라 반복 횟수 결정
	{
		printf("%d ", pa[i]);
	}
}

함수에서 인자를 전달할 때 배열의 사이즈를 전달하거나 sizeof로 계산해서 전달한다.

함수 안에서는 sizeof로 배열의 크기를 알 수 없음. 함수 밖에서 정의된 배열명을 함수 안에서 쓸 수 없는데 포인터에 sizeof연산을 하면 배열의 크기가 아니라 포인터의 크기만 알려줌.

결론: 포인터를 배열명처럼 쓰고 배열 크기를 전달해야 한다.

____3. 배열에 값을 입력하는 함수

#include <stdio.h>

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

int main(void)
{
	double ary[5];
	double max;                               // 최댓값을 저장할 변수
	int size = sizeof(ary) / sizeof(ary[0]);  // 배열 요소의 개수 계산

	input_ary(ary, size);                     // 배열에 값 입력
	max = find_max(ary, size);                // 배열의 최댓값 반환
	printf("배열의 최댓값 : %.1lf\n", max);

	return 0;
}

void input_ary(double* pa, int size)          // double 포인터를 매개변수로 선언
{
	int i;

	printf("%d개의 실수값 입력 : ", size);
	for (i = 0; i < size; i++)                // size의 값에 따라 반복 횟수 결정
	{
		scanf("%lf", pa + i);                 // &pa[i]도 가능, 입력할 배열 요소의 주소를 전달
	}
}

double find_max(double* pa, int size)
{
	double max;
	int i;

	max = pa[0];                              // 첫 번째 배열 요소의 값을 최댓값으로 설정
	for (i = 1; i < size; i++)                // 두 번째 배열 요소부터 max와 비교
	{
		if (pa[i] > max) max = pa[i];         // 새로운 배열 요소의 값이 max보다 크면 대입
	}

	return max;                               // 최댓값 반환
}

입력 함수에는 저장할 배열의 위치가 필요해서 포인터를 함수 안에서 직접 사용.

 

____4. 함수의 매개변수 자리에 배열을 선언하는 경우

- 배열의 저장 공간이 할당되지 않으며 컴파일 과정에서 첫 요소를 가리키는 포인터로 바뀜.
= 즉, 배열의 개수는 의미가 없다.

void func(int pa[5]) { ... } void func(int *pa) { ... }
void func(int pa[10]) { ... } void func(int *pa) { ... }
void func(int pa[]) { ... } void func(int *pa) { ... }
void func(double pa[5]) { ... } void func(double *pa) { ... }
// 배열 선언과 함수 호출
int ary[5] = { 1, 2, 3, 4, 5 };
print_ary(ary); // 함수 호출

// 함수 정의
void print_ary(int pa[5]) // 메모리 주소에 ary 배열과 같은 값을 갖는 배열 선언
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", pa[i]); // *(pa + i) 형식으로 배열 요소의 값 출력
    }
}

 

 

C. 정리하기

____1. 요약

- 배열명은 첫 요소의 주소.
-포인터에 배열명을 저장하면 포인터를 배열명처럼 쓸 수 있음.
-배열명의 정수 덧셈은 자료형 크기를 곱해서 더함.
-포인터의 뺄셈은 빼서 자료형 크기로 나눈다.(요소 간 간격(인덱스 차)의미.)

- 배열을 인자로 받는 함수에는 배열명(첫 요소의 주소)
- 배열의 길이를 미리 알 수 없다면 요소의 개수도 인수로 전달해야 한다.

____2 . 활용

  1. ary는 배열의 시작 주소인 100입니다.
  2. *(ary + 1)는 ary의 주소에서 double 타입의 크기만큼 한 번 더해진 주소의 값을 찾습니다. double이 8바이트라고 가정하면, ary + 1은 주소 100에 8을 더한 108이 됩니다. 따라서 *(ary + 1)의 값은 주소 108에 있는 3.5입니다.
  3. pa + 2는 포인터 pa가 가리키는 주소에서 두 double 크기만큼 떨어진 주소를 나타냅니다. pa는 ary와 같은 주소 100을 가리키므로, pa + 2는 주소 100에 16을 더한 116을 가리키게 됩니다.
  4. pa[3]는 pa로부터 세 요소 뒤를 가리킵니다. pa가 100을 가리킨다면, pa[3]은 주소 100에 3 * 8인 24를 더한 124에 있는 값을 가리키며, 그 값은 0.5입니다.
  5. *pb는 pb 포인터가 가리키는 값을 참조합니다. pb는 ary의 시작 주소 100에 2 * 8인 16을 더한 116을 가리키므로, *pb의 값은 7.4입니다.
  6. pb - pa는 포인터 간의 차이를 나타냅니다. pa가 100을 가리키고 pb가 116을 가리키므로, pb - pa는 16 / 8이 되어 결과는 2가 됩니다. (여기서 8은 double의 크기입니다.)
#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) {
    // 로또 번호를 사용자로부터 입력받는 함수
    printf("Enter 6 lotto numbers: ");
    for (int i = 0; i < 6; i++) {
        scanf("%d", &lotto_nums[i]);
    }
}

void print_nums(int *lotto_nums) {
    // 로또 번호를 출력하는 함수
    printf("Lotto numbers are: ");
    for (int i = 0; i < 6; i++) {
        printf("%d ", lotto_nums[i]);
    }
    printf("\n");
}

 

 

____3. 궁금한 점

-