elevne's Study Note

C 공부 (5: 포인터(2)) 본문

Backend/C

C 공부 (5: 포인터(2))

elevne 2023. 2. 3. 18:50

C 에서 배열의 이름포인터이다. 단, 그 값을 바꿀 수 없는 상수 형태의 포인터라고 한다. 이에 대한 의미는 아래 코드를 통해서 확인해볼 수 있다.

 

 

 

#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    printf("배열의 이름: %p \n", arr);
    printf("FIRST: %p \n", &arr[0]);
    printf("SECOND: %p \n", &arr[1]);
    printf("THIRD: %p \n", &arr[2]);
    return 0;
}

 

 

result

 

 

 

위 코드의 출력결과를 통해 int 형 배열요소간 주소 값의 차이는 4 바이트라는 것을 확인할 수 있다. 이는 모든 배열 요소가 메모리 공간에 나란히 할당된다는 것을 보여준다. 또, 배열의 첫 번째 바이트 주소값과 배열의 이름을 출력한 결과도 같다는 것을 확인할 수 있다. 즉, 배열의 이름은 배열의 시작 주소 값을 의미하는 것이며, 그 형태는 값의 저장이 불가능한 상수라는 것을 알 수 있다. 배열의 이름은 상수 형태의 포인터인 것이다 (그래서 배열의 이름을 가리켜 포인터상수라고도 부른다) .  1차원 배열 이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정된다.

 

 

 

#include <stdio.h>

int main() {
    int arr1[3] = {1, 2, 3};
    double arr2[3] = {1.1, 2.2, 3.3};

    printf("%d  %g  \n", *arr1, *arr2);
    *arr1 += 100;
    *arr2 += 120.5;
    printf("%d  %g  \n", arr1[0], arr2[0]);
    return 0;
}

 

 

 

result

 

 

 

위 함수에서 arr1 arr2 가 가리키는 대상을 우선 출력한다. arr1 int 형 포인터이므로 4바이트 크기의 메모리를 정수의 형태로 참조, arr2 double 형 포인터이므로 8 바이트 크기의 메모리를 실수의 형태로 참조하게 된다. 따라서 arr1 arr2 가 가리키는 배열의 첫 번째 요소가 출력된다. 그 다음으로는 arr1, arr2 이 각각 가리키는 대상의 값을 증가시킨다. 그리고 이를 확인해보면 첫 번째 요소의 값이 그대로 변한 것을 확인할 수 있다.

 

 

 

포인터를 대상으로 증감 연산을 진행할 수도 있다. (int 형 포인터를 대상으로 1을 증가시키면 4 가 증가하고 double 형 포인터를 대상으로 1을 증가시키면 8이 증가한다)

 

 

 

#include <stdio.h>

int main() {
    int * ptr1 = (int *) 0x0010;
    double * ptr2 = (double *) 0x0010;

    printf("%p %p %p \n", ptr1, ptr1+1, ptr1+2);
    printf("%p %p %p \n", ptr2, ptr2+1, ptr2+2);
    return 0;
}

 

 

 

result

 

 

 

이러한 포인터의 연산특성으로 인해 아래와 같은 코드에서 보이는 형태의 배열접근이 가능하다.

 

 

 

#include <stdio.h>

int main() {
    int arr[3] = {111, 222, 333};
    int *ptr = arr;
    printf("%d %d %d \n", *ptr, *ptr+1, *ptr+2);

    printf("%d ", *ptr); ptr++;
    printf("%d ", *ptr); ptr++;
    printf("%d ", *ptr); ptr--;
    printf("%d ", *ptr); ptr--;
    printf("%d ", *ptr);
    printf("\n");
    return 0;
}

 

 

 

result

 

 

 

따라서 배열을 사용할 때 아래의 식이 성립함을 알 수 있다.

 

 

 

arr[i] == *(arr+i)

 

 

 

마지막에 널 문자가 삽입되는 문자열의 선언방식에는 두 가지가 있다.

 

 

 

#include <stdio.h>

int main() {
    char str1[] = "String1";
    char *str2 = "String2";
}

 

 

 

첫 번째는 배열을 기반으로 하는 변수 형태의 문자열선언 방식이다. 변수라고 부르는 이유는 문자열의 일부를 변경 가능하기 때문이다. 두 번째 문장처럼 포인터를 기반으로 문자열을 선언하는 것도 가능하다.

 

 

 

str1 은 그 자체로 문자열 전체를 저장하는 배열이고, str2 는 메모리상에 자동으로 저장된 문자열의 첫 번째 문자를 단순히 가리키고만 있는 포인터변수이다. (배열이름 str1이 의미하는 것도 실제로는 문자 S의 주소 값이기 때문에  str1str2도 문자열의 주소 값을 담고 있다는 측면에서는 동일하다) 배열이름 str1은 계속해서 문자 S 가 저장된 위치를 가리키는 상태이어야 하지만, 포인터 변수 str2 는 다른 위치를 가리킬 수 있다.

 

 

 

배열을 대상으로는 값의 변경이 가능하기 때문에 첫 번째 문장처럼 선언된 것을 변수형태의 문자열이라고 한다. 반면에 str2는 상수 형태의 문자열이라고 불리고 그 내용의 변경은 불가능하다.

 

 

 

 

포인터변수로 이루어진, 그래서 주소 값의 저장이 가능한 배열을 가리켜 포인터 배열 이라고 한다. 이러한 배열은 아래와 같이 선언해줄 수 있다.

 

 

 

#include <stdio.h>

int main() {
    int *arr1[20];
    double *arr2[30];
}

 

 

 

이 또한 기본 자료형 배열의 선언방식과 동일하다. 아래와 같이 선언과 활용을 해볼 수 있다.

 

 

 

#include <stdio.h>

int main() {
    int num1=10, num2=20, num3=30;
    int* arr[3] = {&num1, &num2, &num3};

    printf("%d %d %d \n", *arr[0], *arr[1], *arr[2]);
    return 0;
}

 

 

 

result

 

 

 

문자열에 대해서도 적용될 수 있다. 아래와 같은 코드를 작성해볼 수 있다.

 

 

 

#include <stdio.h>

int main() {
    char* strArr[3] = {"One", "Two", "Three"};
    printf("%s \n", strArr[0]);
    printf("%s \n", strArr[1]);
    printf("%s \n", strArr[2]);
    return 0;
}

 

 

 

result

 

 

 

큰 따옴표로 묶여서 표현되는 문자열은, 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다. 즉 위 문장이 실행되면 초기화 리스트에 선언된 문자열들은 메모리 공간에 저장되고, 그 위치에 저장된 문자열의 주소 값이 반환되는 것이다.

 

 

 

 

 

Reference:

윤성우의 열혈 c 프로그래밍

'Backend > C' 카테고리의 다른 글

C 공부 (6: Call by reference)  (1) 2023.03.07
C 공부 (4: 포인터(1))  (0) 2023.01.29
C 공부 (3: 배열)  (1) 2023.01.28
C 공부 (2: 반복문, static)  (0) 2023.01.27
C 공부 (1: ~ Casting)  (0) 2023.01.26