카테고리 없음

[C] 포인터(1)

root_go 2022. 10. 6. 09:07

포인터의 개념

포인터(pointer)는 주소(address)를 저장하는 변수이다.

메모리에는 각각의 바이트를 구분하기 위한 주소(번지)가 있다.

- 주소의 크기도 플랫폼에 따라 다르다.

- 32비트 플랫폼에서는 주소가 4바이트이고, 64비트 플랫폼에서는 주소가 8바이트이다.

 

포인터를 사용할 때는 주소 값이 아니라 포인터가 어떤 변수를 가리키는지가 중요하다.

포인터는 다른 변수를 가리키는 변수이다.

- 포인터는 주소를 이용해서 다른 변수에 접근할 수 있도록 도와준다.

 

포인터의 선언

- 포인터를 선언할 때 지정하는 데이터형은 포인터가 가리키는 변수의 데이터형이다.

 

포인터의 의미

 

포인터의 크기

포인터의 데이터형이 다르더라도 포인터의 크기는 항상 같다.

포인터의 크기는 플랫폼에 의해서 결정된다.

 

포인터의 바이트 크기 구하기

#include<stdio.h>

int main(void) 
{    
	int *pi;       // *는 변수명 쪽으로 붙여준다.
	double *pd;
	char *pc;

	printf("sizeof(pi) = %zd\n", sizeof(pi)); // 4바이트 (32비트 플랫폼)
	printf("sizeof(pi) = %zd\n", sizeof(pd)); // 4바이트
	printf("sizeof(pi) = %zd\n", sizeof(pc)); // 4바이트

	printf("sizeof(int*) = %zd\n", sizeof(int*));          // 4바이트
	printf("sizeof(double*) = %zd\n", sizeof(double*));   // 4바이트
	printf("sizeof(char*) = %zd\n", sizeof(char*));       // 4바이트

	return 0;
}
 
 

포인터의 초기화

  • 포인터에 직접 절대 주소를 대입해서는 안된다.
int *p2 = (int*)0x12345678; // 메모리 주소를 직접 사용하면 실행 에러가 발생한다.
 
  • 변수인 주소를 구할 때 주소 구하기 연산자인 &를 이용한다.
int a = 10;
int *p3 = &a;    // int 변수 a의 주소를 구해서 int 포인터인 p3를 초기화한다.
 
  • 어떤 변수의 주소로 초기화할지 알 수 없으면 0으로 초기화한다.
int *p4 = 0;      // 어떤 변수의 주소로 초기화할지 알 수 없으면 0으로 초기화한다.
 
int *p5 = NULL;   // 0 대신 NULL 사용해도 된다.
 
 

포인터의 선언 및 초기화

#include<stdio.h>

int main(void) {
	//int *p1 = 0x12345678;        // 컴파일 에러
	int *p2 = (int*)0x12345678;    // 실행 에러가 발생할 수 있다.

	int a = 10;
	int *p3 = &a; // a의 주소를 구해서 p를 초기화한다.

	int *p4 = 0;  // 어떤 주소로 초기화할지 알 수 없으면 0으로 초기화한다.
	int *p5 = NULL; // 0 대신 NULL을 사용해도 된다.

	printf("p2 = %p\n", p2); // 주소를 출력할 때는 %p 형식 문자열을 이용한다.
	printf("p3 = %p\n", p3); // 같은 주소값이 출력된다.
    printf("a = %p\n", &a);  // 같은 주소값이 출력된다.
	printf("p4 = %p\n", p4);
	printf("p4 = %p\n", p5);

	return 0;
}
// p2 = 12345678
// p3 = 0135FD80
// a = 0135FD80
// p4 = 00000000
// p4 = 00000000
 
 

포인터의 사용

  • 주소 구하기 연산자 &

- 변수(I-value)에만 사용할 수 있다.

int x = 10;
int *p = &x;
 

- 상수나 수식에는 사용할 수 없다.

p = &123;             // ERROR
P = &(X + 1);         // ERROR
P = &printf("hello"); // ERROR
 
#include<stdio.h>

int main(void) {
	int x = 10;
	int *p = &x;  // p는 x의 주소로 초기화한다.

	printf(" x = %d\n", x);
	printf("&x = %p\n", &x);  // &x는 주소 값이므로 %p로 출력

	printf(" p = %p\n", p);
	printf("*p = %d\n", *p);  // *p는 int형 변수이므로 %d로 출력
	printf("&p = %p\n", &p);  // 포인터도 변수이므로 주소가 있다.

	*p = 20;                  // x =20;으로 수행된다.
	printf("*p = %d\n", *p);  // printf("*p = %d\n", x);로 수행된다.
	printf(" x = %d\n", x);
	return 0;
}
// x = 10
// &x = 00EFF8B8
// p = 00EFF8B8
// *p = 10
// &p = 00EFF8AC
// *p = 20
// x = 20
 
 

역참조 연산의 의미

 

포인터가 필요한 경우

#include<stdio.h>
void test1(int x)
{
	x = 20;
}
void test2(int *p)
{
	*p = 20;
}
int main(void) 
{
	int x = 10;
	test1(x);
	printf("test1 호출 후 x = %d\n", x);

	test2(&x);
	printf("test2 호출 후 x = %d\n", x);

	return 0;
}
// test1 호출 후 x = 10
// test2 호출 후 x = 20
 
 

예제 문제

(C/C++프로그래밍 과제_4) 포인터 변수 이해

- 길이가 5인 int형 배열 arr을 선언하고 이를 1,2,3,4,5로 초기화한 다음, 이 배열의 마지막 요소를 가리키는 포인터 변수 ptr을 선언한다. 그 다음 변수 ptr에 저장된 값을 감소시키는 형태의 연산을 기반으로 모든 배열 요소에 접근하여, 배열에 저장된 모든 정수를 더하여 그 결과를 출력하는 프로그램을 작성한다.

#include <iostream>
int main(void)
{
	int i = 0; 
	int sum = 0; // 변수선언과 초기화
	int arr[5] = { 1,2,3,4,5 }; // 배열변수 선언과 초기화
	int* ptr = &arr[4]; // 포인터 변수선언과 초기화

	for (i = 4; i >= 0; i--) {
		int* ptr = &arr[i];
		sum += *ptr;
	}

	printf("배열의 합 : %d", sum);
}
// 배열의 합 : 15
 

const 포인터

 

const 포인터의 의미

#include<stdio.h>

int main() {
	int a = 10, b = 20;

	const int* p1 = &a;         // p1는 a에 읽기 전용으로 접근
	int* const p2 = &a;         // p2는 a 전용 포인터
	const int* const p3 = &a;   // p3는 읽기 전용 + a 전용 포인터

	printf("*p1 = %d\n", *p1); // p1으로 a를 읽어 온다.
	// *p1 = 100;               // *p1은 const 변수로 간주되므로 컴파일 에러 
	p1 = &b;                   // p1이 다른 변수를 가리킬 수는 있다. 이제 p1은 b를 가리킨다.
	printf("*p1 = %d\n", *p1); // p1으로 b를 읽어 온다.

	// p2 = &b;                 // p2가 다른 변수를 가리키게 할 수 없으므로 컴파일 에러
	*p2 = 100;                 // p2가 가리키는 변수의 값을 변경할 수 있다.  
	printf("*p2 = %d\n", *p2);

	// *p3 = 100;               // 컴파일 에러
	// p3 = &b;                 // 컴파일 에러
	printf("*p3 = %d\n", *p3); // p3이 가리키는 변수의 값을 읽어 온다.

	return 0;
}
//*p1 = 10
//*p1 = 20
//*p2 = 100
//*p3 = 100
 

'포인터+정수' 연산의 결과

#include<iostream>

int main() {
	int* p = (int*)0x100; // 포인터 연산을 확인하기 위해 절대 주소를 대입한다.
	double* q = (double*)0x100;
	char* r = (char*)0x100;

	printf("int* : %p, %p, %p\n", p, p + 1, p + 2);    // 4바이트씩 차이
	printf("double* : %p, %p, %p\n", q, q + 1, q + 2); // 8바이트씩 차이
	printf("char* : %p, %p, %p\n", r, r + 1, r + 2);   // 1바이트씩 차이

	return 0;
}
// int* : 00000100, 00000104, 00000108
// double* : 00000100, 00000108, 00000110
// char* : 00000100, 00000101, 00000102

포인터와 +, - 연산

'포인터+정수' 연산은 포인터가 가리키는 주소에 마치 배열이 있는 것처럼 메모리에 접근한다.

p + i == &arr[i]
*(p + i) == arr[i]

배열의 0번 원소를 가리키는 포인터의 +, - 연산

#include<iostream>

int main() {
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* p = &arr[0];  // arr[0]은 int형 변수이므로 arr[0]의 주소를 p에 저장할 수 있다.
	int i;

	for (i = 0; i < 5; i++)
	{
		printf("p + %d = %p, ", i, p + i); // arr[i]의 주소를 출력
		printf("*(p + %d) = %d\n", i, *(p + i)); // arr[i]를 출력
	}
	printf("%p , %d, ", p, *p);
	return 0;
}
// p + 0 = 010FF994, *(p + 0) = 1
// p + 1 = 010FF998, *(p + 1) = 2
// p + 2 = 010FF99C, *(p + 2) = 3
// p + 3 = 010FF9A0, *(p + 3) = 4
// p + 4 = 010FF9A4, *(p + 4) = 5
// 010FF994 , 1,

배열처럼 사용되는 포인터

  • 배열 원소를 가리키는 포인터

· type형의 포인터는 항상 type형의 변수 또는 type형 배열의 원소를 가리킬 수 있다.

  • 배열 원소를 가리키는 포인터는 배열 이름인 것처럼 사용할 수 있다.
  • 배열의 원소를 가리키는 포인터는 배열의 어떤 원소든지 가리킬 수 있다.
 

포인터를 배열인 것처럼 사용하는 경우

#include<stdio.h>

int main(void) {
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* p = arr; // 배열의 이름, 배열의 시작 주소, &arr[0]은 모두 같다.
	int i;

	for (i = 0; i < 5; i++)
		printf("p[%d] = %d\n", i, p[i]); // p를 배열 이름인 것처럼 사용한다.()
                                  // p[i] 와 arr[i]는 같다.
	return 0;
}
// p[0] = 1
// p[1] = 2
// p[2] = 3
// p[3] = 4
// p[4] = 5
 

포인터처럼 사용되는 배열

  • 배열의 이름은 배열의 시작 주소를 의미한다.

· 배열 이름을 포인터인 것처럼 사용할 수 있다.

int arr[5] = {1, 2, 3, 4, 5};
for (i=0; i<5; i++)
    printf("%d ", *(arr+i)); // arr을 포인터인 것처럼 사용할 수 있다.
 
arr[i] == *(arr + i) // arr는 배열의 이름
&arr[i] == arr + i

배열 VS 포인터

  • 배열 이름은 특정 변수 전용 포인터인 것처럼 사용할 수 있다.
  • 배열의 시작 주소는 변경할 수 없다.
int x[5] ={1, 2, 3, 4, 5};
int y[5];
v = x; // 컴파일 에러
x++; // 컴파일 에러
// 배열의 시작 주소는 변경할 수 없다.
 
  • 포인터는 값을 변경할 수 있으므로 포인터에 보관된 주소는 변경할 수 있다.
int x[5] = {1, 2, 3, 4, 5};
int y[5];
int p = x;
p = y; // OK
x++;   // OK
// 포인터에 저장된 주소는 다른 주소로 변경할 수 있다.

배열과 포인터의 차이점

#include <iostream>

int main() {

	int x[5] = { 1, 2, 3, 4, 5 };
	int y[5];
	int* p = x; // p는 x[0]을 가리킨다.
	int i;

	for (i = 0; i < 5; i++)
		printf("%d ", p[i]);
	printf("\n");

	p = y; // p는 이제 y[0]을 가리킨다.
	for (i = 0; i < 5; i++)
		p[i] = x[i]; // p가 가리키는 y 배열에 x 배열을 복사한다.

	for (i = 0; i < 5; i++, p++)
		printf("%d ", *p);
	printf("\n");
	return 0;
}
// 1 2 3 4 5
// 1 2 3 4 5

여러 가지 포인터 선언

  • 포인터 배열 : 주소를 저장하는 배열
  • 배열에 대한 포인터 : 배열 전체를 가리키는 포인터
  • 이중 포인터 : 포인터의 주소를 저장하는 포인터

 

포인터 배열

  • 포인터 배열의 각 원소가 다른 변수를 가리키는 포인터이다.
int a, b, c, d, e;
int *arr[5] = {&a, &b, &c, &d, &e};

for(i=0; i<5; i++)
    *arr[i] = i; // arr[i]가 가리키는 int형 변수

포인터 배열의 선언 및 사용

#include <iostream>

int main(void) {

	int a, b, c, d, e;
	int* arr[5] = { &a, &b, &c, &d, &e }; // int의 주소로 초기화된 포인터 배열
	int i;

	for (i = 0; i < 5; i++)
	{
		*arr[i] = i;
		printf("%d ", *arr[i]); // arr[i]는 포인터이다.
	}
	printf("\n");

	return 0;
}
// 0 1 2 3 4

배열에 대한 포인터

  • 배열에 대한 포인터는 2차원 배열과 함께 사용된다.

· 열 크기만큼 만들어진 묶음을 가리킬 때 배열에 대한 포인터를 사용한다.

· 배열에 대한 포인터를 사용 할 때는 2차원 배열인 것처럼 사용한다.

 

  • 배열 원소에 대한 포인터
int *p;
p + 1; // 배열 원소 크기(int)
 
  • 배열 전체에 대한 포인터
int (*p)[5];
p + 1; // 배열 전체 크기(int [5])