포인터를 잘 알아야 하는 이유

a1.jpg

Understanding and Using C Pointers: Core Techniques for Memory Management 1st Edition을 읽고 정리한 내용을 기술합니다.

Understanding and Using C Pointers 정리 시리즈
  1. 포인터를 잘 알아야 하는 이유
  2. 포인터 선언하기
  3. Null의 개념
  4. void포인터와 전역, 정적포인터
  5. 포인터의 크기와 데이터 타입
  6. 일반적인 포인터 사용
  7. 동적 메모리 관리

포인터를 잘 알아야 하는 이유

포인터는 다음과 같은 용도로 사용된다.

  • 빠르고 효율적인 코드 작성
  • 다양한 문제에 대한 효과적인 해결 방법 제공
  • 동적 메모리 할당 지원
  • 작고 간결한 표현의 사용
  • 큰 오버헤드 없이 데이터 구조를 포인터로 전달
  • 함수의 매개변수로 전달된 데이터 보호
  1. 포인터는 하드웨어의 구조에 좀 더 가까우므로 빠르고 효율적인 코드의 작성이 가능하다.
    ⇨ 컴파일러는 좀 더 쉽게 포인터의 동작을 머신 코드로 변환할 수 있다. 포인터는 다른 연산자에 비해 발생하는 오버헤드가 적다. (ex: 배열과 포인터 연산의 기계어 코드량이 다르다.)
  2. 많은 데이터 구조들은 포인터를 사용하면 더욱 쉽게 구현된다. ⇨ 연결리스트는 배열 또는 포인터를 사용하여 구현될 수 있지만, 포인터를 사용하여 구현하기가 좀 더 쉽고 다음 연결 또는 이전 연결을 포인터로 직접 지정할 수 있다.

포인터의 양면성

포인터를 사용한 간결한 표현은 매우 기술적인 표현이면서도 많은 프로그래머가 제대로 이해할 수 없는 난해한 표현이 되기도 함.

예시

char *names[] = {"Miller", "Jones", "Anderson"}
printf("%c", *(*(names+1)+2));;
printf("%c", names[1][2]);

printf()문은 결국 같은 결과를 출력하지만, 배열 표기법을 사용한 접근 방식이 더 단순하고 이해하기 쉽다.

포인터가 야기하는 다양한 문제들

  • 배열이나 데이터 구조의 경계를 넘는 접근
  • 소멸한 자동/로컬 변수의 참조
  • 할당 해제된 힙 메모리의 참조
  • 아직 할당되지 않은 포인터에 대한 역참조

예외적 포인터 동작의 정의

  1. 구현 방법에 따라 정의된 행동(Implementation-defined behavior)
    동작에 대한 문서화된 구현을 제공한다. 구현 방법에 따라 정의된 행동의 예로, 정수에 대한 오른쪽 시프트 연산에서 상위 비트의 확장 방법이 있다.

  2. 명시되지 않은 행동(Unspecified behavior)
    동작에 대한 구현을 제공하지만 문서화하지 않는다. malloc 함수에 인자로 0을 죽, 실행할 때 메모리가 얼마나 할당되는가 하는 것이 명시되지 않은 행동의 예가 될 수 있다. 명시되지 않은 행동의 목록을 CERT Secure Codinf Appendix DD에서 볼 수 있다.

  3. 정의되지 않은 행동(Undefined behavior)
    포인터의 동작에 대해 어떠한 것도 강요하지 않으므로 어떠한 동작도 발생할 수 있다. 이 경우의 예로, free 함수에 의해 해제된 포인터의 값이 있다. 정의되지 않은 행동의 목록은 CERT Secure Coding Appendix CC에서 찾을 수 있다.

a2.png 로케일에 따라(locale-specific) 다른 동작도 있다. 이런 동작은 일반적으로 컴파일러 제조사가 문서화한다. 로케일에 따라 정의된 동작은 컴파일러 제작자가 더 효율적인 코드를 생성할 수 있는 자율성을 부여한다. ex) 문자열