/C
📌 포인터의 개념
- 주소값의 이해: 주소값 = 데이터가 저장된 메모리의 시작 주소
- 포인터란 메모리의 주소값을 저장하는 변수이다.
int n = 100;
int *ptr = &n;
위의 예시에서 n이라는 변수를 선언하고, *ptr을 통해 포인터를 선언하였다.
- 포인터 연산자에는 주소연산자와 참조연산자가 있다.
- 주소연산자(&)는 변수 이름 앞에 기호를 붙여서 사용한다. 해당 변수의 주소값을 반환하여 번지연산자라고도 불리며, 기호 &은 앰퍼샌드라고 읽는다.
- 참조연산자(*)는 포인터 이름이나 주소 앞에 붙여서 사용한다. 포인터가 가리키는 주소에 저장된 값을 반환한다.
- 포인터를 선언하는 방식은 아래와 같다.
타입 * 포인터이름 = &변수이름;
타입 * 포인터이름 = 주소값;
- 포인터를 참조할 때는 참조연산자를 활용한다.
int x = 7;
int * ptr = &x;
int * pptr = &ptr;
#include <stdio.h>
int main(void)
{
int num01 = 1234;
double num02 = 3.14;
int* ptr_num01 = &num01;
double* ptr_num02 = &num02;
printf("포인터의 크기는 %d입니다.\n", sizeof(ptr_num01));
printf("포인터 ptr_num01이 가리키고 있는 주소값은 %#x입니다.\n", ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소값은 %#x입니다.\n", ptr_num02);
printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %f입니다.\n", *ptr_num02);
return 0;
}
//포인터의 크기는 8입니다.
//포인터 ptr_num01이 가리키고 있는 주소값은 0xb06343dc입니다.
//포인터 ptr_num02가 가리키고 있는 주소값은 0xb06343e0입니다.
//포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 1234입니다.
//포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 3.140000입니다.
📌 포인터 연산
- 포인터 연산의 규칙 네 가지
- 포인터끼리의 덧셈, 곱셈, 나눗셈은 의미가 없다.
- 포인터끼리의 뺄셈은 두 포인터의 상대적 거리를 의미한다.
- 포인터에 정수를 더하거나 빼는 것이 가능하다. 다만 실수와의 연산은 허용되지 않는다.
- 포인터끼리의 대입/비교가 가능하다.
- 예시 코드를 살펴보자.
#include <stdio.h>
int main(void)
{
char* ptr_char = 0;
int* ptr_int = NULL;
double* ptr_double = 0x00;
printf("포인터 ptr_char가 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_char);
printf("포인터 ptr_int가 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_int);
printf("포인터 ptr_double이 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_double);
printf("포인터 ptr_char가 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_char);
printf("포인터 ptr_int가 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_int);
printf("포인터 ptr_double이 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_double);
return 0;
}
main 함수 내에 있는 포인터는 전부 0을 가리키는 포인터이다. 1 증가 연산 이후 포인터가 가리키는 주소는 각각의 타입에 따라 달라진다. 증가 폭이 포인터가 가리키는 변수의 타입 크기와 동일하다는 것을 알 수 있다.
예를 들어, int형 포인터라면 증가폭은 int형 타입의 크기인 4바이트만큼 증가하게 된다. (뺄셈에도 동일하게 적용된다)
- 포인터의 비교/대입 연산의 예시 코드는 아래와 같다.
#include <stdio.h>
int main(void)
{
int num01 = 10;
int num02 = 20;
int *ptr_num01 = &num01;
int *ptr_num02 = &num02;
if (ptr_num01 != ptr_num02) // 포인터끼리의 비교 연산
{
printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num02);
printf("포인터 ptr_num01과 ptr_num02는 현재 다른 주소를 가리키고 있습니다.\n\n");
ptr_num02 = ptr_num01; // 포인터끼리의 대입 연산
}
printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num02);
if (ptr_num01 == ptr_num02) // 포인터끼리의 비교 연산
{
printf("포인터 ptr_num01과 ptr_num02는 현재 같은 주소를 가리키고 있습니다.\n");
}
return 0;
}
📌 인수전달방법
- 함수 호출 시에 필요한 데이터를 인수(argument)로 전달 가능하다. 전달 방법에는 값에 의한 전달과 참조에 의한 전달이 있다.
- 값에 의한 전달: 인수로 전달되는 변수의 값을 함수 내의 매개변수로 복사하는 방법이다. 여기서 변수와 매개변수는 완전히 다른 별개의 값이 되므로 함수 내에서 매개변수를 조작해도 변수에 영향이 가지 않는다.
#include <stdio.h>
void local(int);
int main(void)
{
int var = 10;
printf("변수 var의 초기값은 %d입니다.\n", var);
local(var);
printf("local() 함수 호출 후 변수 var의 값은 %d입니다.\n", var);
return 0;
}
void local(int num)
{
num += 10;
}
//변수 var의 초기값은 10입니다.
//local() 함수 호출 후 변수 var의 값은 10입니다.
- 참조에 의한 전달: 변수의 주소값을 인수로 전달한다. 따라서 함수 내에서 변수의 값을 변경할 수 있다.
#include <stdio.h>
void local(int*);
int main(void)
{
int var = 10;
printf("변수 var의 초기값은 %d입니다.\n", var);
local(&var);
printf("local() 함수 호출 후 변수 var의 값은 %d입니다.\n", var);
return 0;
}
void local(int* num)
{
*num += 10;
}
//변수 var의 초기값은 10입니다.
//local() 함수 호출 후 변수 var의 값은 20입니다.
📌 다양한 포인터
- 포인터의 포인터: 포인터 변수를 가리키는 포인터를 의미한다. 참조연산자(*)를 두 번 사용하여 표현하여 이중포인터라고도 부른다.
- void포인터: 대상이 되는 데이터의 타입을 명시하지 않은 포인터를 의미한다. 따라서 어떤 값도 가리킬 수 있지만, 포인터 연산이나 메모리 참조는 불가능하다. 사용할 때마다 사용하고자 하는 타입으로 명시적 타입 변환을 하는 과정 또한 필요하다.
- 함수 포인터: 함수는 프로그램 실행 시에 모두 메인 메모리에 올라가게 된다. 함수의 이름은 메모리에 올라간 함수의 시작 주소를 가리키는 포인터 상수가 되는데, 이 포인터 상수가 함수 포인터이다. 함수 포인터 타입은 반환값과 매개변수로 정해진다.
- 널 포인터: 0, NULL타입을 대입하여 초기화한 포인터를 의미한다. 즉, 아무것도 가리키지 않는 포인터이다.
void func(int, int); //이런 함수 원형이 있다면
void (*ptr_func) (int, int); //함수 포인터는 이렇게 선언