본문으로 바로가기
반응형

포인터 초기화

포인터 변수의 초기화는 포인터 변수가 가리키는 메모리를 지정하는 것이다.

int i;
int *iPtr;
*iPtr = 21;

이 경우, 포인터 변수 iPtr은 아무 메모리의 주소도 가지고 있지 않은 상태(가리키는 메모리 공간이 없는 상태)에서, 대상 메모리에 대한 값을 부여받았다.

이것은 포인터 변수를 초기화하지 않고 잘못 사용한 경우이며, 만약에 iPtr이 아주 중요한 메모리 공간을 가리키고 있다면, 시스템 전체에 심각한 문제가 발생할 수도 있는 일이다.

 

char* string;
scanf("%s", string);

이 경우, 포인터 변수 string의 경우도 초기화되지 않았기 때문에, scanf가 읽은 문자열이 어디에 기록될지 알 수 없다.

 

아래의 예제를 작성해보자.

#include <stdio.h>

int main() {

	int iNum = 0x12345678;

	//포인터 변수 타입을 통해 데이터를 어떻게 볼것인지 정할 수 있다.
	int* p1 = &iNum;
	char* p2 = &iNum;  // int*형이나 char*이나 크기는 4바이트이다.
	
	printf("%08x\n", *p1);
	printf("%08x\n\n", *p2);
	// 00000078 이 출력되는데, 원래 char형이 1바이트를 가지므로 iNum주소의 1바이트만 출력되는데, 출력을 %08x로 했으므로 00000078이 출력된다.

	*p2 = 0xAB;
	printf("%08x\n", iNum); // char*으로 메모리의 1바이트의 값만을 바꿀 수 있다.
    }

 

printf 출력에서 *p1은 int*형인 p1이 가리키는 주소값이기때문에 iNum값이 그대로 출력된다.

마찬가지로 *p2는 char*형인 p2가 가리키는 주소값이기때문에 iNum값이 출력되는데, char형이기때문에 1바이트만 출력된다.

 

이때, *p2의 값을 0xAB로 바꿔주고 다시 출력을하면 78부분인 1바이트 자리가 AB로 바뀐것을 확인할 수 있다.

 

 

 

포인터의 연산

포인터 변수가 가리키는 객체에 대한 연산은 일반변수와 같이 사용할 수 있다.

포인터 변수의 연산은 메모리 주소에 대한 연산이며, 덧셈과 뺄셈만 허용된다.

즉, 포인터 변수 p에 대한 연산은 

p = p + 3;

과 같이 가감만 가능하다.

위의 예에서, 

iptr++;

정수형을 가리키는 포인터 변수 iptr의 값을 0x2011에서 4증가시킨 0x2015가 된다. 이것은 포인터 변수의 증감은 그 포인터 변수가 가리키는 대상자료형의 크기만큼 증감시키기 때문이다.

포인터 변수의 연산자는 주소연산이 우선이다. int형 포인터 변수 iptr이 5를 가지고있는 변수 num을 가리킨다고 할때, *iptr의 값은 5가 된다.

x = *iptr++;

x = (*iptr++);

와 동일한 명령문으로 x는 *iptr의 값인 5가 부가되고, iptr값은 대상자료형 크기인 int형 크기만큼(4바이트) 주소값이 증가된다.

향후 iptr은 num을 가리키던 포인터를 상실하게 된다.

num의 값을 증가시킬 경우는 

x = (*iptr)++;

와 같이 괄호를 사용하여 연산자 우선 순위를 바꾸어 주어야 한다.

 

 

포인터와 증감연산자 예제

#include <stdio.h>
void main() {
	int n = 10;
	int* p1 = &n;
	int* p2 = p1;

	printf("%d\n", (*p1)++);
	printf("%d\n", (*p2)++);
	printf("%d\n", n);
}

 첫번째 printf문에서 (*p1)를 먼저 실행하고 후치연산으로 ++을 실행하기때문에 printf에는 10이 출력되고, n이 11이 되어있다.

 두번째 printf문에서도 (*p2)를 먼저 실행하고 후치연산으로 ++을 실행해서 printf에는 11이 출력되고 n은 12가 된다.

 세번째 printf문에는 n을 그대로 출력하면 되므로 12가 출력된다.

 

 

 

 

 

일반 연산과 주소 연산의 차이점을 예제를 통해 확인해보자.

#include <stdio.h>
int main()
{
	int iNum = 0x12345678;
	int* p = &iNum;
	printf("%p\n", &iNum);
	printf("%p\n\n", p);

	printf("%p\n", &iNum+1);  // 주소 + 1을 통해 4바이트 이동 => 4가 +됨
	printf("%p\n", p+1);
}

 

printf문에서 p에서 p+1을 했을때 차이점을 관찰하는 예제이다.

p는 int*형이므로 (주소 + 1)이 됬으므로 4바이트 만큼의 주소 자리가 옮겨지게된다.(이사갔다고 생각하면 편하다)

그래서 D8에서 DC로 +4가 된 것이다.

 

그렇다면 char*형에서는 어떻게 출력될까?

#include <stdio.h>
int main()
{
	char cNum = 0x12345678;
	char* p = &cNum;
	printf("%p\n", &cNum);
	printf("%p\n\n", p);

	printf("%p\n", &cNum + 1);  // 주소 + 1을 통해 1바이트 이동 => 1이 +됨
	printf("%p\n", p + 1);
}

 

char*형에서도 (주소 + 1)을 했을때는 int*형과는 다르게 char의 형이 1바이트이므로, 주소가 1만큼 상승한 것을 확인할 수 있다.

 

따라서, 주소값 + 1은 주소 연산을 의미하고, 단순히 값인 숫자 + 1과 주소 + 1 의 의미는 다르다는 것을 알 수 있다.

 

포인터형 변수는 4바이트의 공간을 차지하는데, char*형으로 출력문을 작성하면 1바이트씩 출력된다. 그렇다면 1바이트씩 출력하면서 모든 숫자를 출력하는 방법이 있다. p + 1은 단순히 주소를 옮기는 것으로만 생각했는데, 이 식에서 *를 붙여서 관찰해보자.

#include <stdio.h>
int main(){
	int iNum = 0x12345678;
	char* p = &iNum;
	printf("%02x\n\n", *p);

	printf("%02x\n", *(p + 0));    // 주소 + 1을 먼저 연산해서 저장된 메모리의 값들을 1바이트씩 출력
	printf("%02x\n", *(p + 1));
	printf("%02x\n", *(p + 2));
	printf("%02x\n\n", *(p + 3));
}

 

처음 출력문은 78만 출력되지만, 주소의 값을 1칸씩 더해주면서 출력하면 12 34 56 78이 전부 출력된 것을 확인할 수 있다.

비슷한 패턴이 반복되므로 반복문으로도 묶어줄 수 있다.

#include <stdio.h>
int main(){
	int iNum = 0x12345678;
	char* p = &iNum;
	printf("%02x\n\n", *p);

	for(int i = 0; i<4; i++)
	{
		printf("%02x\n", *(p++));
	}
}

결과는 똑같이 출력된다

 

char*, short*, int* 형으로 예제를 작성해보자.

#include <stdio.h>
int main(){

	int iNum = 0x12345678;
	char* cp = (char*)&iNum;
	short* sp = (short*)&iNum;
	int* ip = &iNum;

	printf("%08x\n", *cp);
	printf("%08x\n", *sp);
	printf("%08x\n\n", *ip);


	//3456 출력

	sp = (short*)((char*)cp + 1);
	printf("%04x\n", *sp); 

	sp = (short*)((char*)&iNum + 1);
	printf("%04x\n", *sp);

	sp = (short*)((char*)ip + 1);
	printf("%04x\n", *sp);
}

char*, short*, int*는 각각 1바이트, 2바이트, 4바이트를 출력하는 것을 확인할 수 있다.

위 코드는 short형을 이용해서 3456을 출력하는 예제이다.

printf문에서 (short*)((char*)를 통해 형변환을 해주는 것을 볼 수 있는데, 포인터 변수인건 똑같지만 컴파일러는 포인터 변수의 타입이 다르다고 인식해서 보여주기때문에 혹시 있을 오류를 없애주기 위해 형변환을 통해 출력하였다.

 

 

 

다음 포스팅은 실수를 포인터로 저장해서 메모리에 어떻게 변환되는지 알아보도록 하자.

 

 

(지난번꺼 간략히 복습)

int iNum = 100;

int *p = 0;

(*는 주소, p는 포인터 변수라고 부른다)

 

p = &iNum;

(p는 int* &iNum은 int*, &는 주소, iNum은 int)

 

*p = 200;

(*p는 int*형이며, 주소를 찾아가라 => 200을 p가 가리키는 곳에 대입해라 => iNum에 200을 대입해라)

반응형