포인터와 정적 배열 (Pointer and Fixed Array) 본문
포인터와 정적 배열
C++에서 포인터와 배열은 밀접한 관련이 있다.
포인터와 정적 배열의 관계를 이해하는 것은 이후 메모리 동적할당, 동적배열을 이해할 때 중요한 기본이 된다.
포인터와 배열이 어떤 관계가 있는지 알아보자.
Similarities between pointers and fixed arrays
지난 포스트에서 우리는 정적 배열을 정의하는 방법을 배웠다.
위의 것은 정수 5개의 배열이지만, 컴파일러에는 배열이 int[5]의 변수다.
각각 array[0], array[1] 및 array[4] 의 값을 가지고 있다.
그러나 배열 자체는 어떤 값을 갖고 있을까?
배열 변수는 마치 포인터인 것 처럼 배열의 첫 번째 요소의 주소를 가지고 있다.
다음 프로그램에서 이를 볼 수 있다.
위에서 만든 array를 출력하면 어떻게 나올까?
이번에는 array의 첫번째 값의 주소를 찍어보자
배열 변수가 보유한 주소는 배열의 첫 번째 요소의 주소다.
이 array 변수는 사실 배열이 아니라 포인터다.
포인터는 주소를 담는다.
이 다섯개의 연이어있는 int값을 저장하는 메모리의 첫 번째 바이트의 주소를 담는다.
그래서 &array[0] 하면 그 주소를 갖고 올 수 있는 것이다.
이번에는 포인터를 이용해서 값을 찍어보자.
array가 포인터라고 말씀드렸는데 그렇다면 de-referencing을 할 수 있을까?
그렇다면 문자열도 되나 테스트해보자
name의 주소가 문자열 "hyoni"에 담겨있는 첫 번째 주소니까 h를 잘 가져왔다.
위에 array에서는 de-referencing을 할 때 int로 가져와서 9를 출력했다.
name에서는 name은 문자형의 포인터니까 char로 가져와서 문자로 출력해주었다.
이렇게 정적 배열도 결국은 포인터라는 것을 알 수 있다.
조금 더 테스트해보자
출력해보면
이때 ptr 자체의 주소를 찍어보면 다르다는 것 주의하자!
Differences between pointers and fixed arrays
그렇다면 포인터와 정적배열이 완벽하게 똑같은 건가 의문이 들것이다.
완벽히 같았다면 둘 중 하나로 통일해서 썼을 것이다.
포인터와 정적배열은 문법상 차이가 조금 있다.
차이점은 원래는 포인터인데 사용자가 정적배열을
이런 식으로 쓸 때 좀 더 편하게 도와주는 편의성 기능이 몇 가지 있다.
나중에는 거의 동적할당 된 포인터를 사용하게 될 것이다.
여기서는 정적배열과 어떻게 차이가 나는지 확인해보자
sizeof() 연산자를 사용할 때 주된 차이가 발생한다.
정적배열에서 sizeof 연산자는 전체 크기를 반환한다.
(배열길이*요소 크기) 포인터에서 사용하면 sizeof 연산자는 메모리 주소의 크기를 반환한다.
일단 sizeof를 찍어보자
여기에 담겨있는 전체의 사이즈가 나올 것이다.
그런데 ptr을 만들고 array를 넣고 ptr의 사이즈를 출력해보면
위에는 4바이트가 5개니까 20 맞고
밑에 나온 4는 포인터 변수의 자체 사이즈가 4바이트 이기 때문이다.
절대 int가 4바이트라서 4가 나온게 아니다. 항상 주의하기
만약 64비트로 바꿔서 빌드해보면
포인터 자체의 사이즈다.
주소가 차지하는 데이터 공간이 4바이트라서 4가 나왔다는 거 잘 기억하자!
Revisiting passing fixed arrays to functions
문제가 될 만한 부분들이 몇 가지 있다.
함수 파라미터로 넘겨줄 때 어떻게 되는지 설명드리겠다
이전 포스팅에서 큰 배열을 복사하는 것은 매우 비쌀 수 있으므로
배열이 함수로 전달될 때 배열을 복사하지 않는다고 배웠다.
함수에 배열로 인수를 전달하면 고정 배열을 포인터로 변환되어 함수에 전달한다.
array가 파라미터로 들어오는 경우를 생각해보자
그리고 array의 사이즈를 함수 안에서 출력해보자
main에 있는 array를 함수에 넣어준 것이다.
출력해보면
바로 넣어줘서 20이 나오지 않을까 생각할 수 있는데 4가 나오는 것을 볼 수 있다.
4가 나오는 이유는
[] 대괄호가 있어서 array처럼 보이지만
이렇게 파라미터로 들어오는 순간 내부적으로는 그냥 포인터다.
위의 예제에서 C++는 배열 구문( [ ] )을 사용해서 매개 변수를 포인터 구문( * )으로 변환한다.
void printArray(int *array) 이렇게 선언하나
void printArray(int array[]) 이렇게 선언하나 사실 둘은 같은 것이다.
시험문제에 나오기 딱 좋은 부분이다.
일부 프로그래머는 [ ] 구문을 선호한다.
함수가 값에 대한 포인터가 아니라 배열이 필요하다는 것을 분명히 하기 때문이다.
그러나 대부분은 포인터가 배열의 크기를 알지 못하기 때문에 배열 크기를 별도의 매개 변수로 전달해야 한다.
포인터 구문을 사용하는 것이 좋다.
매개 변수가 조정 배열이 아닌 포인터로 취급되고 sizeof와 같은 특정 연산이 매개 변수가 포인터인 것처럼 작동한다는 것을 명확히 하기 때문이다.
An intro to pass by address
함수로 전달될 때 배열이 포인터로 변환되므로
함수에서 배열을 변경하면 실제 배열을 변경하는 것과 같다.
한 가지 예제를 보자.
포인터로 받고 그다음 de-referencing을 하면
여기서 중요한 성질을 미리 알려드리자면
de-referencing 한 array에다가 100을 넣고 cout으로 다시 출력해보자.
함수 안에서 100으로 바꿨는데, 함수 밖에서도 100으로 바뀌어있다.
예전에는 C 프로그래밍을 배울 때
포인터를 함수 밖에서도 값이 바뀌게 하려고 파라미터를 포인터로 넣어주는 것을 많이 했다.
C++에서는 이런 용도로는 추후 배울 레퍼런스(참조)를 더 많이 이용한다.
포인터는 더 심오한 목적을 위해서 많이 사용된다.
다음 포스팅할 내용 중 하나 조금 보여드리자면
포인터의 연산이다
이렇게 포인터에다가 어떤 값을 더하고 빼는 것을 포인터 연산 Pointer Arithmetic 이라고 한다.
Arrays in structs and classes don't decay
한 가지 더 설명드리자면
위에서 함수로 array를 보내버리면 포인터로 바뀌어서 sizeof를 했을 때 그냥 포인터의 사이즈가 나왔다.
이번에는 구조체 안에 array를 넣고 구조체를 보내보자.
이렇게 array가 ms 안에 들어있다는 것을 확인할 수 있다.
이번에는 MyStruct를 함수 안에 넣어보자.
sizeof를 ms 넣기 전에 해도 ms.array는 20 정적배열의 사이즈가 나왔고
doSomething 안에도 ms가 들어갔으니까 ms.array 사이즈는 20이 나왔다.
그렇다면 ms를 pointer로 넣으면 혹시 문제가 생길까?
함수에서 ms를 포인터로 넣고 de-referencing 해주고 ( 괄호 잘 쳐줘야 함 )
main에서 doSomething에 ms의 주소를 넣어주었다.
그래도 20이 똑같이 나온다.
array가 class 나 structure 안에 있으면 포인터로 강제 변환이 되지 않는다.
그냥 array 자체가 간다.
그래서 sizeof를 찍었을 때 20 20 그대로 나온 것이다.
이번 포스팅에서는 포인터와 정적배열에 대해서 설명해드렸다.
결과적으로 그 둘은 같다.
그런데 문법상 정적배열을 포인터로 똑같이 쓰기보다는
조금 더 편하게 정적배열답게 쓸 수 있도록 몇가지 도와주는 장치가 들어있는 것이다.
그런데 함수에 파라미터로 정적배열을 집어넣었을 경우에는
그 함수에서 받으면서 포인터로 바꿔버리기 때문에 정적배열로서 쓸 수 없는 것이다.
사실 배열을 포인터로 다 접근할 수 있다.
그 부분에 대해서는 추후 포스팅하겠다.
'💘 C++ > 행렬, 문자열, 포인터, 참조' 카테고리의 다른 글
C 스타일의 문자열 기호적 상수 (C-style string symbolic constants) (0) | 2022.07.11 |
---|---|
포인터의 연산과 배열 인덱싱 (Pointer Arithmetic and Array Indexing) (0) | 2022.07.10 |
널 포인터 (Null pointer) (0) | 2022.07.08 |
포인터 소개 및 기본적인 사용법 Introduction to Pointer (0) | 2022.07.07 |
C언어 스타일의 배열 문자열 C-style strings (0) | 2022.07.06 |