Search
Duplicate

포인터와 배열 (remind)

간단소개
포인터와 배열에 대해서 한번 더 짚고 넘어가보면 좋겠다고 생각한 부분들을 정리해보았습니다.
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C
Scrap
태그
포인터
더블포인터
9 more properties

1. 포인터의 형

포인터는 메모리의 위치를 담는 변수를 말한다.
포인터 변수를 선언할때는 포인터의 형(type)을 알아야 하고 이를 통해서 컴파일러는 메모리 공간을 참조하는 기준을 알 수 있게된다.

2. 포인터 변수를 선언할때 주의점(feat. NULL)

포인터를 통해서 변수나 함수가 저장되어있는 위치를 참조해서 들어갈 수 있고, 값의 조작이 용이해진다.
이러한 특징 때문에 가끔은 포인터를 선언만하고 초기화를 하지 않아서 치명적인 문제를 발생시킬 수 있다.
int *ptr;
C
복사
위와 같이 선언만 한다면, ptr 이라는 포인터 변수에는 쓰레기 값이 들어갈 수 있다.
이 경우 잘못된 위치를 참조하게 되어서 ptr를 참조해서 값을 변경할 때나 값을 참조해서 가져올 때 중요한 시스템 변수를 변경시킬 수도 아니면 이상한 값을 가져올 수도 있다.(물론 컴파일러가 시스템변수와 같은 중요한 메모리는 접근 못하도록 막아주는 것으로 알고있다) 그러므로 만약 포인터 변수를 사용할것이지만, 나중에 유효한 주소값을 채워 넣으려면 0또는 NULL 로 채워줘야 한다.
int *ptr = 0 // int *ptr = NULL;
C
복사

3. 포인터대상의 증감연산의 작동

포인터의 형의 크기만큼 증가와 감소가 이루어진다.
int num = 3; //num이라는 int형 변수의 값은 3이며, 메모리 위치는 0x0001이라고 가정하자. int *ptr = # printf("%p",ptr++); //0x0005( sizeof(int)*1만큼 증가된 위치이다)
C
복사
여기서의 포인터 변수가 가리키고 있는 값은 int형이므로 int형의 크기만큼 커진다.
int arr[3] = {11, 22, 33}; int *ptr = arr; printf("%d %d %d", *ptr, *(ptr+1), *(ptr+2)); //11 22 33
C
복사
여기서의 포인터 변수가 가리키고 있는 값은 int 형이므로 int 형의 크기만큼 커진다.

4. arr[i] == *(arr+i) 이다.

너무 당연한 말이지만, 한번 더 짚어보았다.
당연하기 때문에 이차원 배열에서의 다음의 값들도 같은 표현이다.
int arr[2][3]; arr[2][1] = 4; (*(arr+2))[1] = 4; *(arr[2]+1) = 4; *(*(arr+2)+1) = 4;
C
복사

5. 문자열의 선언

char str1[] = "First expression"; char *str2 = "Second expression";
C
복사
str1과 str2는 문자열을 선언할 수 있다는 점에서 공통적이지만, 차이점이 존재한다.

char str1[] = "First";

컴파일러는 배열의 길이를 자동으로 계산해서 문자열을 할당한다.
이때 문자열의 시작 주소는 고정되어있다.(문자열의 시작주소 = 배열의 이름)
== 배열의 이름이 상수 형태로 선언된다.
요소들의 변경은 가능하다.
변수 형태의 문자열 선언이라고 한다.

char *str2 = "Second";

문자열은 별도의 메모리 공간에 저장이 되며 메모리 주소값이 반환된다.
str2는 포인터 변수로 반환된 주소값이 들어간다.
따라서 str2는 '변수 형태의 포인터'이며, 들어가는 주소를 다른것을 넣어서 변경이 가능하다.
char *str2 = "Second";//S의 시작주소가 저장된다. str3 = "Third"; //T의 시작 주소가 저장된다.
C
복사
그러나 참조된 값을 찾아가서 값을 변경할 수는 없다.
char *str2 = "Second"; str2[5] = 'i'; //에러
C
복사
이것을 상수 형태의 문자열 선언이라고 한다.

위의 두가지 표현은 엄연히 다르지만, 함수에서 인자로 전달할때는 동일한 것으로 사용이 가능하다.

int sumArr(int *param, int len) { int sum = 0; for (int i=0; i<len; i++) sum+=param[i]; } int sumArr(int param[], int len) { int sum = 0; for (int i=0; i<len; i++) sum+=param[i]; }
C
복사

6. 배열의 선언과 동시에 초기화하기

배열의 길이 전달해서 선언하기

배열을 선언함과 동시에 초기화를 할때는 '초기화 리스트'를 사용한다.
{}를 사용해서 표현한 것을 초기화리스트라고 한다. (""도 초기화 리스트의 역할을 한다.)
int arr[5] = {1,2,3,4,5}; // {} 를 사용해서 표현한 것을 초기화 리스트라고 한다.
C
복사
char str[5]="cake"; // c a k e /0
C
복사

배열의 길이 전달하지 않고 선언하기

초기화 리스트가 없이 선언을 하더라도 컴파일러는 초기화 리스트의 요소 수를 참조 후 길이 정보를 자동으로 채워줄 수 있다.
int arr[] = {1,2,3,4,5,6,7};
C
복사
char str[] = "cake";
C
복사

이차원 배열의 경우, 선언시 배열의 세로 길이만 생략가능하다.

참고 : "8. 이차원 배열의 메모리"
이유 : 컴파일러가 가로길이, 세로길이를 둘다 모르면 계산해서 넣을수 없기 때문이다.

7. Const의 역할

A. 심볼릭 상수(이름을 지니는 상수)의 선언

상수(값의 변경이 불가능) 를 선언할 때 사용한다.
주의 : 심볼릭상수는 선언과 동시에 꼭 초기화를 해줘야 한다.
const int MAX = 100;
C
복사

B. 포인터 변수의 Const 선언

특정 포인터 변수를 참조한 값을 변경하고 싶지 않을때 사용한다.
const int *ptr = &num; *ptr = 30; //에러발생
C
복사
⇒ 이경우, ptr을 참조해서 num의 값을 변경할 수 없다. (출력은 가능하다)
이것이 num이 상수화 되는 것을 의미하는 것은 아니다.

C. 포인터 변수의 상수화

포인터 변수 자체가 갖고 있는 값을 변경할 수 없도록 만든다.
int * const ptr = &num; ptr = &num2;// 에러 발생
C
복사
⇒ ptr이 한번 값을 가리키면 다시 변경할수 없다.

8. 이차원 배열의 메모리

이차원 배열은 행의 순서대로 저장된다.
int arr[3][2]; /* arr[0][0] : 0x0001 (1) arr[0][1] : 0x0005 (4) arr[0][2] : 0x0009 (9) arr[1][0] : 0x000D (13) arr[1][1] : 0x0011 (17) arr[1][2] : 0x0015 (21) arr[2][0] : 0x0019 (25) arr[2][1] : 0x002D (29) arr[2][2] : 0x0031 (33) */
C
복사
이런 순서로 저장된다는 것을 기억하면, 다음과 같이 초기화가 가능한것을 쉽게 이해할 수 있다.
int arr[3][3] = { {1}, {4,5}, {7,8,9} } //빈자리는 0으로 채워짐
C
복사
int arr[3][3] = { 1, 0, 0, 4, 5, 0, 7, 8, 9 }
C
복사
선언시에 배열의 세로 길이만 생략가능한 것은 메모리가 가로가 먼저 채워지기 때문이다.

9. 이차원 배열의 포인터형

int arr[3][3]; 이 있을때, arr 과 arr[0]은 다르다

arr
배열의 첫번째 요소를 가리킨다.
printf("%p\n", arr); //0x7ffee7567900 printf("%p\n", arr[0]); //0x7ffee7567900 printF("%p\n",&arr[0][0]); //0x7ffee7567900
C
복사
배열의 전체를 가리키기도 한다.
printf("%lu",sizeof(arr)/sizeof(int)); //9
C
복사
arr[0]
배열의 첫번째 요소를 가리킨다.
(위의 예시 보기)
배열의 한 행을 가리킨다.
printf("%lu", sizeof(arr[0])/sizeof(int)); //3
C
복사

이차원 배열의 포인터형은 더블 포인터가 아니다.

이차원 배열에서 arr과 arr[0]은 다르다.
arr의 배열의 포인터형은 그냥 더블 포인터라고 하면 안된다.
int arr[3][2]; printf("arr + 0 %p \n",arr); //0x0001 printf("arr + 1 %p \n",arr+1); //0x0009 (8증가) printf("arr + 2 %p \n",arr+2); //0x0011 (8증가) int arr2[2][3]; printf("arr2 + 0 %p \n",arr2); //0x00C0 printf("arr2 + 1 %p \n",arr2+1); //0x00CC (12증가) printf("arr2 + 2 %p \n",arr2+2); //0x00D8 (12증가)
C
복사
포인터 포인터 형이 같다면 포인터 대상의 증감연산의 결과값이 같지만, 둘의 연산결과는 다르다.
이차원 배열의 포인터 형은 가로의 길이에 영향을 받는다.

int (*ptr) [2]

이런 표현은 int arr[3][2]와 같은 이차원 배열의 포인터를 담을 수 있다.
int : 포인터가 참조하고 있는 요소의 자료형이 int 이며,
(*ptr) : 포인터 변수이며
[2] : 가로의 길이가 2임을 알려준다.

int *ptrArray[4]; int (*ptr2d) [4];는 다르다

int *ptrArray[4]는 "포인터 배열"을 의미한다.

즉, 배열의 요소가 포인터 값임을 말한다.
인자로 받을 경우에는 int **ptr; 를 통해서 받을 수 있다.
int* 와 *ptr를 끊어서 생각해보면 쉽게 알 수 있다.
포인터 형이 참조하는 곳으로 갔을때 담고 있는 값이 int *이며, 포인터 변수이므로 *ptr이다.

int (*ptr2d) [4]는 "이차원 배열의 포인터"이다.

이것은 가로 길이가 4인 이차원 배열의 포인터를 가리킨다.
인자로 넘겨줄 경우에는 int (*ptr2d) [4] 이렇게 넘길 수 있다.
===동일한 표현으로는 int arr[][4]도 가능하다(인자에 한해서만 동일한 표현이다)

이차원 배열의 세로길이 계산하기

sizeof(arr)은 전체 배열의 크기를 알려준다.
sizeof(arr[0])은 배열의 가로의 크기를 알려준다.
⇒ 따라서 전체 배열의 크기 / 배열의 가로 크기 == 배열의 세로 크기 이다.
sizeof(arr)/ sizeof(arr[0]))
C
복사