Programming

[C] C언어 특성 정리 (2) - 포인터

Nobb 2025. 10. 8. 19:49

## 포인터 Pointer 

    : "메모리 주소"를 값으로 저장하는 변수  (cf. 일반변수 - 실제 데이터값을 저장)

    - 용도: 메모리 직접 접근/조작 , 동적 메모리 할당 , 함수로 변수 값 변경 , 배열/문자열 효율적 처리

    - 핵심 연산자: & (앰퍼샌드) - 주소 연산자   /   * - 간접참조 연산자(가리키는 주소의 실제데이터값 접근)

    - 크기: 1워드의 크기 (8 / 16 / 32 / 64 bit)

 

 

## 포인터 선언

int *p1;	# 정수 가리키는 포인터
char *p2;	# 문자열 가리키는 포인터
double *p3;	# 실수 가리키는 포인터

 

 

 

## 포인터 사용 주의사항 (3)

 1- 포인터는 초기화하고 사용하는 게 안전.

      (어떤 변수 가리킬 지 알 수 없으면 NULL로 초기화)

int *q; #쓰레기값
*q = 10; #실행 에러 발생
>>>
int *q = NULL; ## 권장

 2- 포인터의 데이터형 & 가리키는 변수의 데이터형 같아야함.

 3- 포인터 안전하게 사용하려면, 사용할 때 null포인터인지 검사하기

if (q != NULL)
	*q = 100;
##=====
if (q)
	*q = 100;

 

 

 

##  포인터 연산

 - 포인터 + N  --> 결과값 : ( 포인터가 가리키는 자료형의 크기 x N ) 만큼 증가

 

 

## 상수 포인터

 

 

##  이중 포인터 (**)

    : 주소의 주소 담고있는 포인터 (포인터를 가리키는 포인터)

int n = 10;		# n <- 10(상수값)
int* n_p = &n;		# n_p <- n의주소
int** n_pp = &n_p;	# n_pp <- n_p의주소

printf("%d", n);	# 10
printf("%d", *n_p);	# 10 (n의 값)	: n_p가 가리키는 값
printf("%d", *n_pp);	# 0x... (n의 주소)	: n_p에 든 값
printf("%d", **n_pp);	# 10 (n의 값) :n_pp가 가리키는 주소가 가리키는 값

 - 주 용도:

    > 1) 포인터를 함수 인자로 전달해서 "원본 포인터" 수정할 때 (Call-by-Reference) **

void allocMemory(int **ptr) {
    *ptr = (int *)malloc(sizeof(int));
    **ptr = 10;
}

int main(){
    int *p = NULL;
    
    allocMemory(&p);
    printf("%d\n", *p);	# 10
    
    free(p);
}

 

    > 2) 동적 2차원 배열 구현할 때 (가변크기 배열)

int main(){
    int rows = 3, cols = 4;
    int **arr = (int **)malloc(rows * sizeof(int *));	#행 포인터 배열
    
    for (int i = 0; i < rows; i++){
    	arr[i] = (int *)malloc(cols * sizeof(int));	#각 행의 열 메모리
    }
    
    arr[1][2] = 7;
    printf("%d\n", arr[1][2]);	#7
    
    for (int i = 0; i < rows; i++)
    	free(arr[i]);
    free(arr);
}
## ======== 메모리 순서대로 할당하는법
 int(*arr)[width] = (int(*)[width])malloc(height * width * sizeof(int));
  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      int data;
      scanf("%d", &data);
      arr[i][j] = data;
    }
  }

 

    > (3) 문자열 배열 다룰 때

## 문자열 배열 (char argv)
int main(int argc, char **argv)

 

**Call-by-Reference

     * 일반적으로 함수에 변수 전달 시 복사본 전달됨. >>포인터 전달해야 원래 변수 값 변경 가능.

    > 변경대상:  일반변수 값 --> 변수 주소(=단일 포인터) 전달

                        포인터의 주소 --> 포인터의 주소(=이중 포인터) 전달

         ** 포인터의 주소 바꾸기   = 해당 포인터의 화살표 방향(목적지) 바꾸기

## 일반변수 변경할 때 :pointer ========
int a = 10;

void change(int* ptr) {
    *ptr = 20;
}
change(&a);


## 포인터변수 변경할 때 :double-pointer ========
int* p = &a;

 

 

## malloc()

 : 프로그램 실행 중(runtime)에 메모리를 직접 빌려오는 함수  (**어원: memory allocation 메모리 할당)

    - 용도: 컴파일할 때 크기를 모르는 데이터를 나중에 동적으로 만들 때

#정수 1개 할당 ========
int *p = (int *)malloc(sizeof(int));

#정수 n개 할당 ========
int *arr = malloc(n * sizeof(int));

#2차배열(m*n) _각 행에 malloc 반복 ========
int **mat = malloc(m * sizeof(int*));

#포인터 수정 함수 ========
void alloc(int **p){
	*p = malloc(...);
}

 

 

 

 

 

 

 

 

 


## 포인터 문제 풀이 

https://www.geeksforgeeks.org/quizzes/pointers-gq/

 

C Pointer Basics

C Pointer Basics Quiz will help you to test and validate your C Quiz knowledge. It covers a variety of questions, from basic to advanced. The quiz contains 43 questions. You just have to assess all the given options and click on the correct answer.

www.geeksforgeeks.org

(g2g_'C Pointer Basics'  #1~5)


'*'의 쓰임 2가지 잘 구분하기

  1- 포인터 변수 선언할 때

  2- 역참조 할 때

 

**c = &b;

  >> 이중포인터 c = b의주소

 

 


mystery() 분석

  - 인자: 포인터변수 ptra, ptrb

  - 전달받은 주소값: &a, &b

  - 함수 내용: ptra = &a, ptrb = &b ----> ptra = &b, ptrb = &a

 

  >> 어차피 ptra, ptrb는 이 함수 내에서만 존재하는 포인터라 걔네 값 바꿔도 원본에 영향 없음

 ## 만약 함수에서 원본값 바꾸고싶다면 (특정 주소의 값 변경),

:  " 이중포인터 " 사용

void swap(int **pp_a, int **pp_b){
	int *tmp = *pp_a;
    *pp_a = *pp_b;
    *pp_b = *tmp;
}

int main(){
	int x = 10, y = 20;
    int *a = &x;
    int *b = &y;
    
    swap(&a, &b);
    
    printf("%d %d\n", *a, *b);    #20, 10

 


** 배열 이름- 그 배열의 첫번째 요소의 주소 포인터 역할

int arr[5] = {10,20,30,40,50}
## arr = &arr[0]

 

** 2차원배열에서의 포인터

int a[2][3] = { {1,2,3}, {4,5,6} };
## a <- 배열의 첫번째요소 주소값

print("%p\n", a);	# &a[0][0]
print("%p\n", a+1);	# &a[1][0]
print("%p\n", *(a+1));	# &a[1][0]
print("%p\n", *(*(a+1) + 2));	# a[1][2]

## 왜 a+1와 *(a+1) 값이 같을까? ============
a: int 3개까지 배열을 가리키는 포인터
    > int (*)[3]
    	>> a[0] (int[3]) -> [1][2][3]
    	>> a[1] (int[3]) -> [4][5][6]

a+1 : &a[1]
*(a+1) : a[1]	#포장지 하나만 깐 상태
>> 둘다 두번째 행 전체를 뜻함(그 안의 값은 아직 아님)
>> 본문에서 둘다 %p로 출력하므로, 둘다 그 행의 첫번째 요소 주소가 찍히는 것

 

 


** 대부분의 CPU는 리틀엔디안 방식 사용함 (x86, ARM 등)

   > 정수 a=300이 메모리에 저장될 때는 낮은 바이트부터 거꾸로 저장됨

[10진수] 300 = [16진수] 0x0000012c
## >> 리틀엔디안으로 메모리 저장 시,
0x100	0x2C
0x101	0x01
0x102	0x00
0x103	0x00

>> 이렇게 4Byte로 저장됨

 

** char *b = (char *)&a; 의 의미

    > a의 주소에 담긴 값을 1바이트 단위로 접근하겠다 (char=1Byte)

int main()
{
   int a = 300;    
   ## ex. 메모리 - 0x0000012c 리틀엔디안으로 저장하면(300->16진수값)
   ## >> [ 2c 01 00 00 ]
   char *b = (char *)&a; # b는 a의 '값'을 1바이트씩 접근
   *++b = 2;
   ## >> [ 2c 02 00 00 ]  --> (22c->10진수로 바꾸면 556됨)
   printf("%d ",a);
   return 0;
}

 

 


 

** y = *ptr --> *ptr=x값=0 들어감