TIL 11월 7일 2023년 화요일

ORCASUIT·2023년 11월 10일

포인터

  • 포인터는 메모리 주소를 저장하는 변수
  • 16진수로 표현된다. (0x005AF7AC)

메모리 주소에 저장된 자료형을 알려주기.

  • 메모리에 있는 비트패턴(주소)에 접근 할 때, 하드웨어가 어떤 '자료형' 으로 읽어야 할지 명시해준다. char, int, float*

void save_address(void)

{

int num = 10;

int* num_address = #

}
  • 포인터 변수를 선언할 때는 자료형 옆에 * 을 붙임
  • 별표 왼쪽에 있는 자료형은 그 메모리 주소로 가면 저장되어 있는 자료형

포인터 변수를 부르는 법

  • 보통 numadress를 int 포인터라고 부르는데 영어로는 pointer to an int (int를 가르키는 포인터) 라고도 함.
  • 왜? 포인터 변수도 메모리 어딘가에 존재하기 때문.

역 참조 연산자

  • 포인터도 변수임으로 변수 쓰는 곳에는 다 쓸 수 있음 따라서 매개변수도 가능하다.

void print_value(void)  
{

int score = 100;

int* pointer = &score;

printf("%d", *pointer);

}

void print_argument(float* arg)

{

printf("%f", *arg);

}

/*메인 함수*/

float pi;

print_value( );

pi = 3.14f;

print_argument(&pi);

참조와 역 참조

  • 참조
    - 포인터가 이미 하고 있음.
    - 어떤 변수의 값을 직접 가져다 쓰는게 아니라 그게 어디 있다 참조함
    - 즉, 값이 어디에 있는지 가리키고 있는 것

  • 역 참조
    - 주소로 직접 가서 거기 저장 되어있는 값에 접근

실제 데이터에 간접적으로 접근

  • 역참조는 값에 직접 접근하는 게 아님
  • 주소를 이용해 간접적으로 접근.
  • 따라서 간접 연산자라고 함

지역 변수의 주소를 반환 : 매우 위험한 코드

  • 지역변수의 주소를 반환하면 안된다.
  • 왜? 스택메모리 함수 프레임 때문에 문제가 생김.
  • 주소와 값이 있지만 포인터가 가르키지 않은 유효하지 않은 주소.
  • 이러한 포인터를 댕글링 포인터라고 함.

int* add(const int op1, const int op2)

{

int result = op1 + op2;

return &result;

}

int main(void)

{

int* result;

result = add(10, 20);

return 0;

}
  • 포인터를 반환해도 되는 경우…!

  • 전역 변수

  • 파일 속 static 전역 변수

  • 함수 내 static 변수

  • 힙 메모리에 생성한 데이터

  • 언제 포인터를 반환할까?

  • 도우미 함수 안에 생성한 변수를 다른 함수에서 사용하고자 할 때

  • 단, 일반 지역변수면 안됨 (함수호출이 끝나면 스택에서 사라짐)

  • 함수 안에서 대용량을 데이터를 생성하고 그걸 반환하고자 할 때

  • 이 경우에는 데이터를 스택 메모리가 아니라 힙 메모리라는 곳에 생성함

널(NULL) 포인터


void do_something()

{

int number;

int* num_ptr = &number;

/* 코드 생략 */

num_ptr = NULL;

}
  • 아무것도 가리키지 않는 포인터
  • 값이 0인 정수 상수 표현식, 혹은 void*로 캐스팅된 표현식
  • 전용매크로가 있음#define NULL ((void*)0)
  • 널 포인터를 표현할 때 이 매크로를 사용할 것

int* ptr;

if (ptr == NULL) {

}

if (ptr != NULL){

}
  • 포인터 변수와 NULL은 비교 가능하다.

코딩 표준 : 매크로 NULL을 반드시 사용할 것


#define PRICE (2)

void increase_price(int* current_price)

{

if (current_price != NULL) {  
 *current_price += PRICE;

}

}
  • NULL 대신 0을 사용하지 않아야함.

NULL은 골칫덩어리다 : 매개변수 편 1

  • 함수 매개변수로 포인터가 들어올 때는…
  • 누구나 NULL을 넣을 수 있기 때문에
  • 함수의 선조건(precondition) 문제
  • 기본적으로 NULL이 안 들어온다고 가정하고 함수를 작성할 것
  • NULL이 들어올 수 있는 함수는 매개변수명에서 분명히 밝힐 것

코딩 표준 : 널 포인터를 허용하는 매개변수

  • 함수의 매개변수가 널 포인터를 허용한다면, 매개변수 이름 끝에 or_null을 붙인다.

int get_score(const char* const student_id_or_null)

{

/* 코드 생략 */

}

NULL은 골칫덩어리다 : 매개변수 편 2

  • NULL이 안들어온다고 가정한 경우 assert()를 사용해 검증

#include<assert.h>

#define PRICE (2)

void increase_price(int* current_price)

{

assert(current_price != NULL);

*current_price += PRICE;

}

#### NULL은 골칫덩어리다 : 반환값 편

- NULL을 반환할 때도 마찬가지
- 기본적으로 안 함 (NULL을)
- 반환을 해야 한다면 함수 이름에 NULL을 반환하는 것을 명시할 것

```C

const char* get_name_or_null(const int id)

{

/* 코드 생략*/

return NULL;

}

널 포인터는 언제 사용하나?

  1. 포인터 변수를 초기화 하고 싶을 때
    • 아직 참조할 주소가 없을 때

void do_something(void)

{

int* ptr = NULL; /* 당장 사용하지 않으므로 널 포인터로 초기화 */

/* 코드 생략 */

ptr = &g_moster_count; /* 전역 변수의 주소 저장 */

/* 코드 생략*/

}
  1. 포인터 변수가 유효한 주소를 참조하고 있는지 확인하고 싶을 때
  2. 아무것도 가리키지 않는 변수를 역 참조하면?
    • 결과가 정의되지 않음(undefined behavior)
    • 역 참조를 하기 전에 널 포인터인지 확인할 것

void do_something(void)  
{

/*다른 변수 생략 */

int* ptr = &num;

/* 코드 200줄 */

ptr = NULL;

/* 코드 500줄 */

if (ptr != NULL){

*ptr = 100;

}

}

0개의 댓글