C 언어 -포인터와 배열

컴퓨터/C

728x90
반응형

포인터와 배열

포인터와 배열의 개념을 이해를 했다면 이 둘을 조합하여 조금 더 프로그래밍적 기법들을 사용할 수 있습니다. 

우선 이 둘에 대하여 다시 정리해 보도록 하겠습니다.

  • 포인터
    • 포인터는 메모리 주소를 저장할수 있다.
    • 저장된 메모리 주소에 접근하여 메모리 주소 안의 값을 변경할 수 있다.
  • 배열 
    • 동일한 자료형의 변수의 묶음이다. 
    • 배열은 대괄호 [ ] 안에 인덱스 번호를 참조하여 각각의 요소에 접근할 수 있다.
    • 배열의 이름은 배열이 시작하는 가장 처음의 메모리 주소이다.

 

배열의 시각화 

아래오 같은 배열을 선언했다고 하면 메모리에서 배열은 어떻게  존재할까요?

int arr[2] = {0,};

편의상 메모리 주소는 1byte씩 10진수로 표기했습니다. 요약하자면 아래와 같습니다.

  • int형 배열 arr[2]를 선언하면 위와 같이 메모리에 올라가게 된다.
  • arr [0]은 int형 자료를 저장하기 위해 메모리 주소 1~4까지를 사용하여 arr [0]을 저장한다. 
  • arr [0]의 메모리 시작 주소는 1이다
  • arr [1]의 메모리 시작 주소는 5이다
  • 전체 배열 arr의 시작 주소는 1이다.

배열의 접근 

위의 그림을 보면 배열은 선언한 자료형의 크기만큼 순서대로 메모리 위에 올라가 있는 것을 알 수 있습니다.

즉 배열이 시작되는 가장 첫 번째 메모리 주소와 자료형을 알고 있다면 각각의 배열의 인덱스에 접근할 수 있습니다. 

 

실제로 배열에서 각각의 인덱스에 접근하기 위해 대괄호 [ ]를 사용하여 원하는 위치의 인덱스에 접근하게 됩니다.

말로 풀자면 다음과 같을 것입니다.

 

arr [0]  

arr의 주소에서 자료형의 크기만큼 0번 지난 후 자료형의 크기만큼 읽어줘

 

이해가 어려울 수 있으니 다시 한번 선언한 배열 arr을 이해해 보도록 하겠습니다.

  • 배열 arr의 자료형은 int(4byte)이다.
  • 배열 arr은 arr [2]로 선언되었다 - int(4byte) * 2개이다.
  • arr [0]은 arr로부터 0번 4byte를 지난 후 4byte의 메모리 값에 접근한다.
  • arr [1]은 arr로부터 1번 4byte를 지난 후 4byte의 메모리 값에 접근한다.

이런 식으로 배열의 접근법을 풀면 위의 설명과 같습니다. 

그럼 포인터는 메모리의 주소를 저장한다고 했습니다. 만약 포인터 변수에 배열의 시작 주소를 저장한다면 배열처럼 제어가 가능할까요?

 

포인터 변수에 메모리 시작주소를 넣는다면?

int arr[2] = {0,};
int* p = &arr;

이제 포인터 변수 p안에 들어있는 메모리 주소와 배열 arr의 메모리 주소는 동일합니다. 

그리고 포인터 변수를 통해 arr [0]만을 제어할 수 있습니다. 

여기서 배열처럼 포인터 변수도 n번째 자료형 크기 뒤의 정보를 읽어오게 할수 있습니다. 이것을 C언어에서는 주소 연산이라고 합니다.

 

메모리 주소 연산

int arr[2] = {0,};
int* p = &arr;
p+1; // 메모리 주소연산

int*형 포인터 변수를 선언했기 때문에 포인터 변수에 저장된 주소부터 4byte를 읽는 포인터 변수입니다. 

이 포인터 변수에 +1을 더한다는 것은 자료형의 크기만큼 메모리 주소를 더한다는 의미입니다.

즉 +1은 +int(4byte)의 개념이 되는 것입니다. 

실제로 배열에 접근이 되었는지 확인해보도록 하겠습니다.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int arr[2] = { 0,1 };
    int* p = &arr;
    printf("%d\n", *p);
    printf("%d\n", *(p + 1));
    return 0;
}

  • 포인터를 사용하여 *p 출력
    • 포인터 변수 p에 저장된 메모리 주소 안의 데이터 값 출력(arr [0])
  • 포인터 주소 연산을 사용하여 *(p+1) 출력
    • 포인터 변수 p에 +int크기만큼 메모리 주소를 더한 메모리 주소 안의 데이터 값 출력(arr [1])

 

배열과 포인터는 개념이 같기 때문에 바꿔서 표현할 수 있다.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int arr[2] = { 0,1 };
    int* p = &arr;
    printf("%d\n", p[0]);
    printf("%d\n", p[1]);
    printf("%d\n", *(arr + 0));
    printf("%d\n", *(arr + 1));
    return 0;
}

C언어를 통해 프로그래밍을 배우고 있다면 결국 데이터들은 메모리 안에서 어떻게 정의되어 사용하는지에 대하여 이해를 어느 정도 했을 것입니다.

그리고 포인터와 배열을 이해하고 사용했다면, 표현의 방법의 차이가 있을 뿐이지, 결국은 메모리 공간을 어떻게 정의하고 사용할지에 대한 개념일 뿐입니다.  

이런 내용들을 이해하고 있다면 아래와 같은 방법으로 표기를 해도 사용 가능할 수 있다는 것을 예상할 수 있습니다.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int arr[2] = { 0,1 };
    int* p = &arr;
    printf("%d\n", p[0]);
    printf("%d\n", p[1]);
    printf("%d\n", *(arr + 0));
    printf("%d\n", *(arr + 1));

    return 0;
}

 

결국 개념적인 부분에서 배열과 = 포인터는 같다. 

따라서 표현 방법 또한 혼용이 가능하다.

 

포인터와 배열 그리고 주소 연산의 필요성

결국 이런 개념들이 발전하고 사용하는 데는 다 이유가 있습니다. 

그럼 오늘 배운 것들을 한번 이용해서 사용하면 장점이 무엇인지 알아보도록 하겠습니다.

간단한 예시로 배열의 값을 함수에서 제어하는 소스코드를 만들어 보도록 하겠습니다.

 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void arrset(int* arr , int size)
{
    for (int i = 0; i < 10; i++)
    {
        *(arr + i) = i;
    }
}

int main()
{
    int arr[10] = { 0 };
    for (int i = 0; i < 10; i++)
    {
        printf("함수 실행 전 arr[%d] : %d\n", i, arr[i]);
    }
    arrset(&arr, 10);
    for (int i = 0; i < 10; i++)
    {
        printf("함수 실행 후 arr[%d] : %d\n", i, arr[i]);
    }
    return 0;
}

위의 소스처럼 배열의 인덱스는 1부터 1씩 증가하면서 초기화하는 함수를 만들었습니다. 

물론 main함수 안에서 구현을 할 수 있지만, 만약 100개의 배열을 초기화하기 위해서는 main함수에서 구현하기란 상당히 어려운 일입니다. 또한, 포인터와 주소 연산의 개념을 모른다면 배열을 한 번에 제어하는 함수를 만들기는 쉽지 않을 것입니다. 이런 한계들을 극복하여 좀 더 효율적인 소스코드를 만들 수 있습니다.

728x90
반응형

'컴퓨터 > C' 카테고리의 다른 글

C 언어 - typedef 키워드  (0) 2021.06.22
C 언어 - struct(구조체)  (0) 2021.06.22
C 언어 - const 키워드  (0) 2021.06.18
C 언어 - 형변환(Casting)  (0) 2021.06.17
C 언어 - 오버플로우와 언더플로우  (0) 2021.06.16

Commnet

G91개발일지

Gon91(지구일)

91년생 공학엔지니어의 개발일지

TODAY :

YESTER DAY :

TOTAL :