✨ C언어 포인터
C언어의 포인터는 메모리 주소를 저장하는 변수이다.
(변수나 배열의 값을 간접적으로 접근하고 조작하는 방식)
💡도대체 포인터는 왜 필요할까 ?
포인터를 사용하는 이유는 다음과 같다.
1. 포인터는 특정 변수나 데이터의 메모리 주소를 가리켜서 직접적으로 메모리 접근이 가능하다.
2. 포인터를 사용하면 함수를 호출할 때 값을 복사하지 않고 주소를 전달하여, 메모리 낭비를 줄이고 직접 수정이 가능하다.
3. 동적으로 메모리를 할당하여 프로그램의 메모리사용을 효율적으로 관리한다.
4. 복잡한 자료구조 (연결 리스트, 트리, 그래프 등 ) 동적 자료 구조 구현에 필수적이다.
💡그렇다면 왜 !! 이중 포인터가 필요할까 ?
1. 다차원 배열을 처리할때, 이중 포인터로 각 행을 동적으로 할당하거나 관리 할 수 있다.
2. 함수에서 포인터 자체를 변경해야 할 때 이중 포인터를 사용한다.
3. 이중 포인터는 2차원 동적 배열을 할당하고 해제하여 유연한 배열 구조를 만들 수 있다
🔖 포인터 선언
int *p;
정수를 가리키는 포인터를 선언
& 연산자는 주소를 p에 저장한다.
* 연산자는 포인터가 가리키는 주소의 값을 참조할 수 있다.
#include <stdio.h>
int main()
{
int a = 10 ;
int *p = &a ;
printf("a의 값 : %d \n",a);
printf("a값의 주소 : %p \n", &a);
printf("p의 값 : %p \n",p);
printf("p가 가르키는 값 : %d \n", *p);
return 0;
}
💡포인터에 타입이 있는 이유 ?
포인터가 가리키는 데이터의 크기와 형식을 알아야한다.
1. 포인터 연산 ( p + 1 등)을 할 때, 가리키는 데이터의 크기만큼 이동해야함
ex) int *은 p+1 이 4 바이트 이동하지만, char *는 1바이트만 이동
2. 포인터의 타입은 해당 메모리 주소에 접근할 때 데이터 형식을 결정한다.
🔖 상수 포인터 (const)
1. 상수를 가리키는포인터
#include <stdio.h>
int main()
{
int a,b;
const int* ptr = &a;
//값 변경 불가능
// *ptr = 10 ;
// 주소 변경 가능
ptr = &b;
return 0;
}
포인터가 가리키는 값을 변경할 수 없지만,
포인터의 주소를 변경하여 다른 변수를 가리키도록 할 수 있다.
2.주소를 변경할 수 없는 상수 포인터
#include <stdio.h>
int main()
{
int a,b;
int* const ptr = &a;
//값 변경 가능
*ptr = 10 ;
// 주소 변경 불불가능
// ptr = &b;
return 0;
}
포인터가 지정하는 주소를 바꾸지 못하지만,
포인터가 가리키는 값은 변경 가능하다.
3. 값, 주소 모두 변경 할수 없는 상수 포인터
#include <stdio.h>
int main()
{
int a,b;
const int* const ptr = &a;
//값 변경 가능
// *ptr = 10 ;
// 주소 변경 불불가능
// ptr = &b;
return 0;
}
포인터가 가르키는 값,주소 모두 변경 불가능
💡상수 포인터를 사용하는 이유는 ?
1. 코드 안정성 (실수로변경되는 것 방지)
2. 가독성 향상
3. 최적화 (컴파일러가 상수로 인식하여 메모리 최적화)
최적화 방법 : 불필요한 메모리 접근 제거
→ const로 선언된 값은 변경되지 않아 메모리에 접근하지 않고 캐시 또는 레지스터에 상수를 저장하여 참조 속도를 높힌다.
🔖포인터의 덧셈
#include <stdio.h>
int main()
{
int a ;
int* pa ;
pa = &a;
printf("%p\n",pa);
printf("%p\n",pa+1);
return 0;
}
포인터에 덧셈을 하면 해당 데이터 타입만큼 더해진다.
#include <stdio.h>
int main()
{
int a;
char b;
double c;
int *pa = &a;
char *pb = &b;
double *pc = &c;
printf("(int)pa 값 : %p\n",pa);
printf("(int)pa +1 값 : %p\n\n",pa+1);
printf("(char)pb 값 : %p\n",pb);
printf("(char)pb +1 의 값 : %p\n\n",pb+1);
printf("(double)pc 값 : %p\n",pc);
printf("(double)pc +1 값 : %p\n\n",pc+1);
return 0;
}
int는 4 바이트, char은 1 바이트, double은 8바이트 식 더해졌다. (16진수 더하기)
🔖 배열과 포인터
배열들의 각 원소는 메모리 상에 연속되게 놓여있다.
#include <stdio.h>
int main()
{
int arr[5] = {5,6,7,8,9};
int *parr = &arr[0] ;
for(int i=0; i<4;i++){
printf("arr[%d] : %d | parr+%d : %d\n",i,arr[i],i,*(parr+i));
}
return 0;
}
그래도 배열은 배열이고 포인터는 포인터이다... !!
🔖 이중 포인터
🔖 2차원 배열 구조
#include <stdio.h>
int main() {
int arr[2][2];
printf("arr[0] : %p \n", arr[0]);
printf("&arr[0][0] : %p \n\n", &arr[0][0]);
printf("arr[1] : %p \n", arr[1]);
printf("&arr[1][0] : %p \n", &arr[1][0]);
return 0;
}
arr[0]의 값은 arr[0][0]의 주소값과 같고,
arr[1]의 값은 arr[1][0]의 주소값과 같다.
🔖 배열 포인터
#include <stdio.h>
int main() {
int arr[3] = {1, 2, 3};
int (*parr)[3] = &arr;
printf("arr[1] : %d \n", arr[1]);
printf("parr[1] : %d \n", (*parr)[1]);
return 0;
}
💡배열 포인터는 왜 필요할까 ?
(이거는 그냥 배열 복사 붙여넣기 아닌가라 생각했다...?)
배열 포인터는 배열의 시작 주소를 참조하여,
배열이 저장된 메모리의 주소만 포인터에 저장한다.
이렇게 할 경우 메모리를 낭비하지 않고 배열의 원소들에 직접 접근 수정 가능하다.
#include <stdio.h>
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*parr)[3] = arr ;
printf("parr[1][2] : %d, arr[1][2] :%d \n",parr[1][2],arr[1][2]);
return 0;
}
parr은 크기가 3인 배열을 가리키는 포인터이다.
1차원 배열에서 배열의 이름이 첫번쨰 원소를 가리키는 포인터로 타입 변환이 된 것 처럼,
2차원 배열에서 배열의 이름이 첫 번째 행을 가리키는 포인터로 타입 변환이 되야한다.
#include <stdio.h>
int main() {
int arr[2][3];
int brr[10][3];
int crr[2][5];
int(*parr)[3];
parr = arr; // O.K
parr = brr; // O.K
parr = crr; // 오류!!!!
return 0;
}
위의 예시를 통해 좀 더 이해 할 수 있을 것이다.
🔖 포인터 배열
#include <stdio.h>
int main() {
int *arr[3];
int a = 1, b = 2, c = 3;
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;
printf("a : %d, *arr[0] : %d \n", a ,*arr[0]);
printf("b : %d, *arr[1] : %d \n", b ,*arr[1]);
printf("c : %d, *arr[2] : %d \n", c ,*arr[2]);
printf("&a : %p, arr[0] : %p \n", &a, arr[0]);
return 0;
}
포인터 배열은 말 그래도 포인터들로 구성된 배열이다.
🔖 포인터 받는 함수 인자
#include <stdio.h>
int change_val(int *pi){
printf("pi가 가르키는 값 : %d\n", *pi);
printf("pi의 값 : %p\n", pi);
*pi = 3 ;
return 0;
}
int main(){
int i = 0;
printf("i 변수의 주소값 : %p \n", &i);
printf("호출 이전의 i 값 : %d \n",i);
change_val(&i);
printf("호출 이후의 i 값 : %d \n",i);
return 0 ;
}