본문 바로가기
Programming/C, C++

LV2 C++ 포인터(Pointer) 사용

by Dev_카페인 2022. 9. 8.
반응형

 

[lv2/C++] 포인터(Pointer) 사용

 

포인터가 어떤 동작을 하는지 이해가 되었다는 가정하에 글을 작성하겠다.

포인터를 처음 접하는 사람이라면 이전 글을 읽어본 후 이 글을 읽길 바란다.

[lv2/C++] 포인터 Pointer

 

우리는 변수가 메모리상 어딘가에 저장되고 주소를 가지고 있다는 것을 알았다. 컴퓨터마다, 실행할 때 마다 주소가 바뀌지만 우리는 포인터를 이용해 주소에 접근하고 그 공간에 있는 값을 사용할 수 있다. 그렇다면 메모리 안에서 변수들이 갖는 크기는 얼마나 될까? 데이터 형식(자료형)에 따라 크기를 할당 받는 것을 배우고 sizeof()를 통해 알아볼 수도 있지만 실제 메모리 상 주소 간격에 따른 차이를 확인해 보자.

 

자료형에 따른 주소 간격 출력

#include <iostream>
using namespace std;

int main()
{
	char char_1byte = '1';
	short short_2byte = 2;
	int int_4byte = 4;
	float float_4byte = 4.0;
	double double_8byte = 8.0;

	char *cPtr = &char_1byte;
	short *sPtr = &short_2byte;
	int *iPtr = &int_4byte;
	float *fPtr = &float_4byte;
	double *dPtr = &double_8byte;

	// char*은 C++의 std::strings 이전에 C의 문자열을 나타내는데 사용된 특수한 유형이다.
	// cout << "char Addr size = " << cPtr << ", Next Addr = " << cPtr + 1 << endl; 처럼 한다면 '儆'문자나 이상한 문자들이 출력되는 것을 볼 수 있을 것이다.
	printf("char Addr = %p, Next Addr = %p \n", cPtr, cPtr+1); 
	cout << "short Addr size = " << sPtr << ", Next Addr = " << sPtr+1 << endl;
	cout << "int Addr size = " << iPtr << ", Next Addr = " << iPtr + 1 << endl;
	cout << "float Addr size = " << fPtr << ", Next Addr = " << fPtr + 1 << endl;
	cout << "double Addr size = " << dPtr << ", Next Addr = " << dPtr + 1 << endl;

	return 0;
}

결과창

집중 포인트

1. 포인터 연산 (포인터 변수 + 1)

2. 포인터 배열 (char*)

 

포인터 연산

포인터 변수는 연산이 가능하다. 연산이 가능하다고 해서 일반적인 사칙연산이 적용되는 것은 아니고 연산 대상이 메모리 주소이므로 정수를 더하거나 빼는 것만 가능하다. 물론 곱하거나 나누는 것, 실수를 연산하는 것은 불가능하다. 

위 프로그램에서는 포인터 변수에 1을 더했다. 데이터 형식마다 증가하는 간격이 다른 것을 볼 수 있는데 이 것은 포인터 변수 타입을 어떤 자료형으로 하느냐에 따라 달라진다. 포인터의 데이터 타입이 char 이라면 1byte씩, short라면 2byte씩, int나 float타입이라면 4byte씩, double타입이라면 8byte씩 주소가 더해지게 된다. 쉽게 말해 포인터에 +1을 한다는 것은 pointer + (1 * byte)인 셈이다. 추가적으로 데이터 형식(자료형)에 따른 용량 차이를 알고 싶다면 이전에 게시해둔 자료형 정리를 보거나 사이즈를 확인하는 프로그램을 만들어서 확인해 보자.

[lv1/C++] 데이터 형식(자료형) 종류와 범위

#include <iostream>
using namespace std;

int main()
{
	cout << "char size = " << sizeof(char) << endl;
	cout << "short size = " << sizeof(short) << endl;
	cout << "int size = " << sizeof(int) << endl;
	cout << "float size = " << sizeof(float) << endl;
	cout << "double size = " << sizeof(double) << endl;

	return 0;
}

포인터와 배열

배열은 동일한 자료형으로 된 여러개의 요소들을 순차적으로 저장할 수 있다. 메모리상에서도 배열은 순차적인 구조를 가지고 있는데 포인터를 이용해서 접근 한다면 좀더 간단한 처리가 가능하다. 포인터와 배열 사이의 관계에서 배열명은 배열의 시작 주소를 가지고 있다는 것을 꼭 기억해두길 바란다.

 

포인터가 문자열을 가리킨다면 널문자를 만날 때까지 읽는다. 배열명은 시작 주소를 가지고 있으므로 주소 연산자('&')는 생략해주도록 한다. 문자열 중간에 '\0'를 넣는다면("EndPoint\0 is null) EndPoint 까지만 출력될 것이다.

#include <iostream>
using namespace std;

int main()
{
	char str[] = "EndPoint is null";
	char* strPtr = str;		// 배열명은 시작 주소를 가지고 있으므로 주소연산자('&')는 생략한다.
	
	cout << "Address = " << &strPtr << ", result = " << strPtr << endl;

	return 0;
}

위에서 봤던 문자열이 아닌 문자를 포인터로 가리키게 되면 자신이 넣은 문자 뒤에 이상한 글자들이 출력되는 것도 널문자를 만나지 못해서 정의되지 않은 동작이 발생한 것이다.

#include <iostream>
using namespace std;

int main()
{
	char s = 'A';
	char *sPtr = &s;
	
	cout << sPtr << endl;

	return 0;
}

정의되지 않은 동작

위 프로그램과 결과를 보면 문자 변수('s')에는 A가 들어가 있고 포인터 변수('sPtr')은 문자 변수('s')를 가리키고 있다. char이 아닌 다른 데이터 형식일 때 포인터 변수를 그대로 출력한다면 주소값이 출력되지만 char*는 C++의 std::strings 이전에 C의 문자열을 나타내는데 사용된 특수한 유형이여서 예외적으로 처리된다. 컴파일러 입장에서 그 문자 뒤에 어떤 값이 있는지 명시적으로 표시된 것이 아니며 문자인지 문자열인지 구분할 수 있는 방법이 없으므로 포인터변수(sPtr)가 문자를 가리킨다면 대상 문자와 함께 다음 주소에 있는 값도 읽어들이게 되는 현상이 일어나게 된다. 

 

포인터의 문자열처리는 아래와 같이도 가능하다. 포인터의 문자열 처리시 "널 문자를 만날 때 까지 읽는다", "문자열의 뒤에는 항상 null문자('\0')가 붙는다.", "char의 크기 1byte", "포인터연산"를 기억하고 있다면 이해하는데 어렵지 않다.

#include <iostream>
using namespace std;

int main()
{
	const char *str = "HelloWorld";	// 문자열의 뒤에는 항상 숨어있는 null문자('\0')가 붙는다.

	for (int i = 0; i < 10 ; i++) {
		cout << str + i << endl;
	}

	return 0;
}

다음은 정수형 배열을 포인터를 사용해 출력한 것이다.

#include <iostream>
using namespace std;

int main()
{
	int arr[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
	int* iPtr = arr;
	
	for (int i = 0; i < 10; i++) {
		cout << "arr[" << i << "] = " << *(iPtr + i) << ", Address = " << iPtr + i << ", Original = " << arr[i] << endl;
	}

	return 0;
}

포인터의 연산과 주소값이 어떻게 달라지는지를 중점적으로 확인하면 좋다.

 

 

 

반응형