[C언어] #3 포인터

Ilhoon·2022년 1월 23일
1
post-thumbnail

포인터란?

  • 메모리 주소값을 저장하기 위한 변수
    • 포인터 변수값도 메모리 어딘가에 저장되어있다.
    • 간접연산자이다
      • 주소를 이용해 간접적으로 원본에 직접 접근한다.
        • 값에 의한 전달 : 원본이 안바뀐다.
        • 참조에 의한 전달 : 원본이 바뀔 수 있다.

    • java, c# 등 기본 자료형을 제외한 모든 것은 사실 포.인.터
      • 위 언어들이 포인터의 연산자를 사용하지 않는 이유는?
      • 주소값의 산술 연산을 허용하지 않기위해서 (안전하지 않아서)

  • 포인터 변수의 자료형은 해당 주소에서 몇 바이트를 읽어야하는지 알려주는 의미
    • int 포인터(저장된 주소로부터 4바이트), char 포인터(저장된 주소부터 1바이트)

  • 포인터 변수를 선언하려면 자료형 뒤에 *를 붙인다.
  • 변수가 저장된 메모리 주소를 가져오는 연산자 &
    • 참고) 요즘 운영체제에서는 보안강화를 위해 실행할 때마다 주소를 바꿔준다. (ASLR)
      • 스택메모리 내부에서 상대적인 주소는 항상 같지만 스택메모리 주소 자체가 변경된다.

  • 메모리 주소에 저장된 값을 가져오는 연산자 * (역 참조 연산자)
    • 역 참조연산자를 통해 값 변경도 가능
int num = 123;
int* num_address = #	// int형 num 값이 저장된 주소값을 가리키는 포인터 num_address

printf("%p", &num);			//  0x00DF0213 <- 123이 저장된 주소값 반환
printf("%p", num_address);	//  0x00DF0213 <- num_address = &num
printf("%p", &num_address);	//  0x00FA0255 <- 0x00DF0213이 저장된 주소값 반환
printf("%d", *num_address);	//  123 <- num_address에 저장된 주소값에 저장된 값 

A, B 코드의 차이점

매개변수를 받아서 두 매개변수의 값을 변경하려는 코드

A

#include <stdio.h>
int swap(int num1, int num2){
  int temp;

  temp = num1;
  num1 = num2;
  num2 = temp;
}

int main(void){
  int num1 = 20;
  int num2 = 10;

  printf("num1 : %d, num2 :%d\n", num1, num2);
  swap(num1, num2);
  printf("num1 : %d, num2 :%d\n", num1, num2);

  return 0;
}

B

#include <stdio.h>
int swap(int* num1_address, int* num2_address){
  int temp;

  temp = *num1_address;
  *num1_address = *num2_address;
  *num2_address = temp;
}

int main(void){
  int num1 = 20;
  int num2 = 10;

  printf("num1 : %d, num2 :%d\n", num1, num2);
  swap(&num1, &num2);
  printf("num1 : %d, num2 :%d\n", num1, num2);

  return 0;
}

  • A : swap함수를 실행할 때 스택메모리에 num1,num2 값이 복사
    • num1, num2 원본 데이터에 접근할 방법이 없다.

  • B : swap함수를 실행할 때 스택메모리에 num1,num2 메모리 주소값이 복사되느냐
    • num1, num2 원본 데이터에 접근하여 값을 변경한다

댕글링 포인터

포인터가 유효하지 않은 주소를 가리키는 것

함수의 지역변수 주소를 리턴값으로 사용한다면?

  • 함수가 반환되는 순간 지역변수의 주소는 메모리에는 있지만 다른 함수 호출 시 바뀔 수 있다. (유효하지 않은 주소값)
  • 그 주소값 데이터를 변경하거나 불러올 때 의도치 않은 결과를 발생시킬 수 있다.
  • 스택 메모리에 저장되는 변수 포인터를 반환값으로 사용하면 안된다.
    • 전역변수, static, 힙메모리 생성 데이터 등은 주소값이 반환되지 않기 때문에 괜찮다.
/* 안 좋은 사용 예 */
int* add(int a, int b){
    int result;
    result = a + b;
    return &result;
}

int main(void){
    int* result;
    result = add(10, 20);
}

NULL 포인터

아무것도 가리키지 않는 포인터

NULL포인터 = 값이 0인 주소 = 0으로 채워진 비트패턴 = 하드웨어에서 존재하지 않는 주소

포인터 변수와 NULL 비교 가능 (포인터 변수와 0도 비교 가능하지만, 가독성에 좋지 않은 코드)

좋은 코딩 표준

  • 함수의 매개변수 혹은 리턴 값에서 NULL을 반환할 수도 있는 경우라면 매개변수이름, 함수이름에 명시할 것

NULL포인터 사용법

  • 포인터 변수를 초기화할 때
  • 역 참조를 하기 전에는 반드시 NULL포인터인지 체크

포인터의 크기

모든 포인터는 동일한 크기를 가짐 (메모리 주소의 데이터크기는 동일하기 때문에)

보통 32bit 컴퓨터는 4바이트, 64bit 컴퓨터는 8바이트

배열과 포인터

배열의 이름은 사실 배열이 저장된 메모리의 시작 주소 (초기화 이후 주소 값을 변경할 수 없다.)

int nums[4] = {1,2,3,4};
int* ptr = NULL;

ptr = nums;
ptr = &nums[0];	// 위에 2개는 사실 같은 의미
ptr = nums[0]; 	// 컴파일 오류, 주소값을 저장해야됨

포인터에 덧셈 뺄셈으로 배열의 요소에 접근할 수 있다.

int main(void){
  int num = 10;
  int* num_address = &num;

  printf("%p\n", num_address);
  printf("%p\n", num_address+1);
}

+1을 했는데 왜 4만큼 더해졌지?

  • 포인터 변수에 +1한다는 것은 포인터변수 자료형의 바이트 크기 만큼 더한다는 의미
  • 즉, 다음 요소에 접근하겠다는 의미
  • nums[1]을 내부적으로 뜯어 보면 &nums[0] + 1 <-여기에 접근하는 것이다
int main(void){
  int nums[3] ={1,2,3};
  int* ptr = nums;

  printf("%d %d %d\n", nums[1], nums[1], *(ptr + 1));
}

위 코드는 모두 같은 값을 출력한다.

근데 만약 주소값을 정말 1바이트만 옮기고 싶다면 어떻게 할까

  • 포인터 변수를 다른 자료형으로 캐스팅하면 된다.
  • 포인터 변수의 자료형은? 해당 주소에서 몇바이트 읽을 것인지 말해주는 것.
  • (char*) 포인터 변수앞에 이렇게 붙여주면 char형 데이터를 읽는 것 처럼 포인터 변수를 바꿔주겠다는 의미다.
  • 잘못 사용하면 이상한 오류가 날 수 있는 부분

포인터배열

포인터변수들을 담는 배열

배열의 주소들을 배열 형태로 저장해서 배열의 배열 형태로 사용할 수 있다.

int num1[3] = {11, 22, 33};
int num2[1] = {90};
int num3[4] = {88, 36, 47, 22};
int* num_pointers[3];

num_pointers[0] = &num1;
num_pointers[1] = &num2;
num_pointers[2] = &num3;

포인터와 const

const는 변수에 저장된 값을 변경하지 못하도록 하는 명령어

포인터 변수에서 const는 저장된 메모리 주소, 주소가 가리키는 값 2가지를 보호하는 용도로 사용가능하다.

  • 더 중요한 것은 값을 변경하지 못하도록 막는 것!
int* const p = &num; 		// 주소를 보호할 때
const int* p = &num;		// 값을 보호할 때
const int* const p = &num; 	// 주소도 값도 보호할 때

다중 포인터

포인터의 포인터 (주소를 저장하는 변수의 주소를 저장하는 변수..!)

2차원 배열이 사실 이중포인터와 비슷하다 (배열의 포인터, 각 배열도 포인터)

int main(int argv, char* argv[])
int main(int argv, char** argv)
int num = 10;
int* p = &num;
int** pp = &p;

연습

int num = 10;
int* p = &num;
int** pp = &p;

printf("%d\n", num);  
printf("%p\n", p);    
printf("%d\n", *p);  
printf("%p\n", &p);    
printf("%p\n", pp);  
printf("%p\n", *pp); 
printf("%p\n", &pp);

profile
꾸준하게!

0개의 댓글