elevne's Study Note
C 공부 (5: 포인터(2)) 본문
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;
}
위 코드의 출력결과를 통해 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;
}
위 함수에서 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;
}
이러한 포인터의 연산특성으로 인해 아래와 같은 코드에서 보이는 형태의 배열접근이 가능하다.
#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;
}
따라서 배열을 사용할 때 아래의 식이 성립함을 알 수 있다.
arr[i] == *(arr+i)
마지막에 널 문자가 삽입되는 문자열의 선언방식에는 두 가지가 있다.
#include <stdio.h>
int main() {
char str1[] = "String1";
char *str2 = "String2";
}
첫 번째는 배열을 기반으로 하는 ‘변수 형태의 문자열’ 선언 방식이다. 변수라고 부르는 이유는 문자열의 일부를 변경 가능하기 때문이다. 두 번째 문장처럼 포인터를 기반으로 문자열을 선언하는 것도 가능하다.
str1 은 그 자체로 문자열 전체를 저장하는 배열이고, str2 는 메모리상에 자동으로 저장된 문자열의 첫 번째 문자를 단순히 가리키고만 있는 포인터변수이다. (배열이름 str1이 의미하는 것도 실제로는 문자 S의 주소 값이기 때문에 str1도 str2도 문자열의 주소 값을 담고 있다는 측면에서는 동일하다) 배열이름 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;
}
문자열에 대해서도 적용될 수 있다. 아래와 같은 코드를 작성해볼 수 있다.
#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;
}
큰 따옴표로 묶여서 표현되는 문자열은, 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다. 즉 위 문장이 실행되면 초기화 리스트에 선언된 문자열들은 메모리 공간에 저장되고, 그 위치에 저장된 문자열의 주소 값이 반환되는 것이다.
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 |