맨위로가기

포인터 (프로그래밍)

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

포인터는 메모리 주소를 값으로 가지는 변수로, 해당 주소에 저장된 데이터에 접근하고 조작하는 데 사용된다. 1955년 카테리나 유셴코에 의해 처음 개발되었고, 해럴드 로슨에 의해 1964년 널리 알려졌다. 포인터는 C, C++, 파스칼 등 다양한 프로그래밍 언어에서 지원되며, 간접 참조, 동적 자료 구조 구현, 함수 간 데이터 전달, 동적 메모리 할당 등에 활용된다. C/C++에서는 포인터를 직접 지원하며, 메모리 주소 조작과 포인터 연산을 통해 유연성을 제공하지만, 메모리 누수, 댕글링 포인터, 버퍼 오버런 등의 문제점도 발생할 수 있다. 이러한 문제점을 해결하기 위해 스마트 포인터, 가비지 컬렉션, 대여 검사기 등의 기술이 사용된다.

더 읽어볼만한 페이지

  • 원시 자료형 - 참조
    참조는 프로그래밍에서 메모리 주소나 다른 데이터를 가리키는 값으로, 데이터의 효율적인 전달과 공유를 위해 사용되며, 포인터, 파일 핸들, URL 등이 그 예시이다.
  • 원시 자료형 - 문자열
    문자열은 사람이 읽을 수 있는 텍스트를 저장하고 정보를 전달하거나 받는 데 사용되는 순서가 있는 문자들의 시퀀스로, 다양한 형태의 데이터를 표현하며 유한한 길이를 가지고, 프로그래밍 언어에서 기본 또는 복합 자료형으로 제공되고, 문자 집합과 인코딩 방식에 따라 표현 방식이 달라진다.
  • 프로그래밍 구성체 - 형 변환
    형 변환은 프로그래밍에서 변수의 데이터 타입을 변경하는 것으로, 암시적 형 변환과 명시적 형 변환으로 나뉘며, 객체 지향 프로그래밍에서는 업캐스팅과 다운캐스팅이 발생하고, 각 언어는 고유한 규칙과 방법을 제공하며 잘못된 형 변환은 오류를 유발할 수 있다.
  • 프로그래밍 구성체 - 연산자 오버로딩
    연산자 오버로딩은 프로그래밍 언어에서 기존 연산자를 사용자 정의 자료형에 대해 재정의하여 내장 자료형처럼 다루도록 하는 기능으로, 코드 가독성과 표현력을 높이지만 남용 시 코드 의미를 모호하게 만들 수 있다.
  • 자료형 - 참조
    참조는 프로그래밍에서 메모리 주소나 다른 데이터를 가리키는 값으로, 데이터의 효율적인 전달과 공유를 위해 사용되며, 포인터, 파일 핸들, URL 등이 그 예시이다.
  • 자료형 - 익명 함수
    익명 함수는 이름이 없는 함수로, 람다 추상, 람다 함수, 람다 표현식, 화살표 함수 등으로 불리며, 함수형 프로그래밍 언어에서 람다식 형태로 많이 사용되고 고차 함수의 인수, 클로저, 커링 등에 활용되지만, 재귀 호출의 어려움이나 기능 제한과 같은 단점도 존재한다.
포인터 (프로그래밍)

2. 역사

1955년, 소련의 우크라이나 컴퓨터 과학자 카테리나 유셴코는 포인터와 유사한 간접 주소 지정을 가능하게 하는 주소 프로그래밍 언어를 만들었다. 이 언어는 소련 컴퓨터에서 널리 사용되었으나, 소련 밖에서는 알려지지 않았다. 일반적으로 해럴드 로슨이 1964년에 포인터를 발명한 것으로 알려져 있다.[2] 2000년, 로슨은 IEEE로부터 "포인터 변수를 발명하고 이 개념을 PL/I에 도입하여 일반 목적의 고급 언어에서 연결된 목록을 유연하게 처리할 수 있는 기능을 처음으로 제공한 공로"를 인정받아 컴퓨터 개척자 상을 받았다.[3] 그의 개념에 관한 중요한 논문은 1967년 6월 CACM에 "PL/I 목록 처리"라는 제목으로 게재되었다. 옥스퍼드 영어 사전에 따르면, '''단어''' ''포인터''는 시스템 개발 공사의 기술 메모에서 "스택 포인터"로 처음 인쇄된 것으로 나타났다.

3. 개념

포인터는 메모리 주소를 값으로 가지는 변수이다. 포인터를 통해 해당 주소에 저장된 데이터에 접근하고 조작할 수 있다. 포인터는 간접 참조(역참조)를 가능하게 하여, 함수 간에 데이터를 효율적으로 전달하고 동적 자료 구조를 구현하는 데 사용된다.

모든 변수는 메모리에 값을 저장한다. `const`와 같은 고정값 변수를 제외한 모든 변수는 RAM에 할당된다. 메모리 공간은 메모리 주소값으로 구분되며, 각 주소는 고유한 위치를 나타낸다. 포인터 변수는 이러한 메모리 주소를 값으로 가지며, 이 주소값을 사용하여 데이터에 액세스한다. 즉, 특정 메모리 번지에 값을 쓰거나 읽는 방식이다. 정적 변수도 메모리에 배치되어 주소값을 가지지만, 기계어 코드에 주소값이 고정되어 액세스된다는 차이점이 있다. 반면 포인터 변수는 주소값을 가지고 액세스하므로 임의의 위치를 변경할 수 있다. 전역 변수 중 정적 변수는 일반적으로 기계어 코드에 주소값이 고정되는 반면, 지역 변수는 스택이나 CPU 레지스터를 사용하여 위치값이 설정된다. 포인터 변수는 메모리에 주소값을 저장하는 방식이므로, 이 주소값을 읽어 실제 데이터를 액세스하며, 직접 주소 지정 방식으로 데이터를 액세스한다.

메모리 주소는 CPU 설계 기준에 따라 주소값의 길이와 방식이 결정된다. 일반적인 CPU는 메모리를 지정하는 길이(비트 수)가 동일하며, RAM이든 ROM/FLASH든 모든 주소는 같다. 그러나 MCU(예: 8051)는 메모리 영역을 나누어 다른 주소 체계를 사용하는 경우가 많다. 예를 들어, 8051은 내부 256바이트 내에 변수를 할당하지만, 더 많은 데이터를 처리하기 위해 16비트 주소 체계를 함께 사용한다. 이 경우 주소값은 8비트 또는 16비트가 필요하며, C언어 컴파일러는 이를 지정할 수 있는 방법을 제공한다.

포인터 변수의 유연성은 프로그램 작성의 유연성과 관련된다. C언어는 UNIX 계열 OS를 작성하는 데 사용되었으므로, 커널 소스 코드에서 포인터 변수를 많이 볼 수 있다. 그러나 이러한 유연성은 정의되지 않은 메모리 영역을 액세스할 수 있다는 단점으로 작용할 수도 있다. 정적 변수도 배열 등의 공간 밖을 액세스할 수 있지만, 포인터를 사용하면 이러한 문제가 더 복잡해진다.

C/C++ 에서 포인터 변수를 선언하려면, 가리키고자 하는 변수의 자료형을 맞추어 변수명 앞이나 자료형 뒤, 또는 그 중간에 `*`(별표)를 붙이면 된다. (예: `char* a;`, `int * pa;`, `double *exm;`)

이렇게 선언된 포인터 변수는 실행문에서 일반 자료형 변수처럼 `*`을 빼고 변수명만 사용한다. 예를 들어, `int a;`로 선언된 정수형 변수 `a`는 실행문에서 `a`로만 사용되는 것처럼, 포인터 변수도 `*` 없이 변수명만 사용한다.

중요한 조건은 자료형을 동일하게 유지해야 한다는 것이다. 포인터의 목적은 변수명 대신 주소값으로 변수를 가리켜 값(자료값)을 간접 참조(역참조)하는 것이다. 따라서 정수형 4바이트 변수를 가리키는 포인터가 문자형이나 실수형 변수에 연결되면 기억 공간(memory) 크기가 달라져 문제가 발생한다. 예를 들어, 문자형은 1바이트만 읽거나, 실수형은 8바이트 중 4바이트만 읽게 되어 데이터가 제대로 처리되지 않는다. 따라서 포인터 변수와 가리켜지는 변수의 자료형은 반드시 일치해야 한다.

일반 변수의 주소(주소값)는 변수명에 `&`를 붙여 얻을 수 있다(`&a`). `int a;`로 정수형 변수를 선언했다면, `int *pa;`로 `a`를 가리키는 포인터 `pa`를 선언할 수 있다. 실행문에서 `pa`는 `&a`와 같다.

실행문에서 `*`은 곱셈 연산자 또는 참조 연산자로 사용된다. 참조 연산자로서 `*`은 해당 주소의 값(자료값)을 간접 참조(역참조)하므로, `*&a`는 `*pa`와 같다. 결과적으로 `a`, `*&a`, `*pa`는 모두 같은 값을 가진다.

교체(swap) 함수와 같이 간접 참조(call by reference)를 사용하는 경우, `*pa = xx`처럼 포인터에 참조 연산자를 붙여 값을 참조하고, 대입 연산자 `=`를 사용하여 간접 참조로 가리킨 변수의 값을 `xx`로 변경한다.

위의 내용을 요약하면 다음과 같다.


  • 포인터: 주소값(간접 참조하기 위한 변수의 주소)을 저장하는 변수
  • 포인터 선언: 선언문에서 주소를 저장하는 변수와 같은 자료형을 명시하고, 변수명 앞에 별표(`*`)를 붙여 표시
  • 포인터 사용: 실행문에서는 별표 없이 변수명만 사용 (일반 변수와 동일)
  • 주소: 공간의 첫 주소(첫 주소값). 자료형에 따라 차지하는 공간이 다르다. (예: 문자형은 1바이트, 정수형은 4바이트, 실수형은 8바이트)
  • 직접 참조: 변수에 직접 접근하여 자료값을 읽거나 변경
  • 간접 참조: 변수의 주소를 통해 자료값을 읽거나 변경


```cpp

int exp_a;

int * exp_a_p; // (int* exp_a_p;, int *exp_a_p;)

exp_a = 12;

exp_a_p = &exp_a;

  • exp_a_p == *&exp_a == exp_a == 12;

```

  • 변수 주소: 주소 연산자(`&`)를 붙인 변수명 (`&exp_a`)

1. 자료값(내용물): 변수에 대입된 자료

2. 연산 결과: 해당 변수 기억 공간의 주소

3. 연산 목적: 주소를 통해 해당 자료형의 공간 표시

  • 포인터 `exp_a_p`: 간접 참조하기 위한 변수의 주소를 저장한 변수

1. 자료값(내용물): 가리키는 해당 자료형 자료의 시작 주소(첫 주소)

2. 목적: 가리킨 주소를 통해 기억 공간(대상, 목적물)을 간접 참조하여 자료를 복사하거나 변경

3. 조건: 간접 참조가 목적이므로 지적한 변수와 자료형이 일치해야 함
가리킴, 지시, 지적, pointing = 주소, 주소값 저장(보유)가리키는 것, 지적 대상은 목적에 따라 자료, 자료값이기도 하고 공간이기도 함1. 지적된 해당 자료형의 변수에 저장된 자료값을 참조(복사)하려면 실행문 우변에 위치(r value)하여 참조 연산자 별을 붙여 지적하는 주소에(를 통해) 접근하여 자료값을 간접참조(복사)

2. 지적된 해당 자료형의 변수의 공간에 접근하여 자료를 참조(변경, 재할당?)하려면 실행문 좌변에 위치(l value)하여 참조 연산자 별을 붙여 지적하는 주소인(주소를 통해) 공간에 접근하여 자료값을 간접참조(변경)
대상에 접근하여 참조(사용)하기 위해 참조 연산자 별 표시역할 구분

  • `int * pa = &a;` (선언 시): 선언문에 변수 `a`의 주소를 포인터 변수 `pa`에 저장
  • 포인터 변수 `pa`가 `a`의 주소 `&a`를 저장하고 있으므로, `pa == &a`
  • `*pa`는 `a`의 주소 `&a`를 통해 간접 참조한 `a`의 자료값이므로, `*pa == *&a == a`
  • `&pa`는 포인터 변수 `pa` 자체의 주소 (다중 포인터에서 사용)
  • `scanf`에서 입력받아 변수 `a`에 직접 저장할 수 없어 그 주소 `&a`를 통해 넣음

배열명 - 배열의 첫 주소인 상수(첫 주소값이므로 포인터 변수, 주의: 배열이 아니라 배열명)1955년, 소련의 우크라이나 컴퓨터 과학자 카테리나 유셴코는 포인터와 유사한 간접 주소 지정을 가능하게 하는 주소 프로그래밍 언어를 만들었다.[2] 이 언어는 소련 컴퓨터에서 널리 사용되었지만, 소련 밖에서는 알려지지 않았다. 일반적으로 해럴드 로슨이 1964년에 포인터를 발명한 것으로 알려져 있다.[3] 2000년, 로슨은 IEEE로부터 "포인터 변수를 발명하고 이 개념을 PL/I에 도입하여 일반 목적의 고급 언어에서 연결된 목록을 유연하게 처리할 수 있는 기능을 처음으로 제공한 공로"를 인정받아 컴퓨터 개척자 상을 받았다.[3] 그의 개념에 관한 중요한 논문은 1967년 6월 CACM에 "PL/I 목록 처리"라는 제목으로 게재되었다. 옥스퍼드 영어 사전에 따르면, '''단어''' ''포인터''는 시스템 개발 공사의 기술 메모에서 "스택 포인터"로 처음 인쇄된 것으로 나타났다.

컴퓨터 과학에서 포인터는 일종의 참조이다.

''데이터 기본형''(또는 간단히 ''기본형'')은 하나의 메모리 접근을 사용하여 읽거나 쓸 수 있는 모든 데이터이다. (예: ''바이트''와 ''워드'' 둘 다 기본형이다)

''데이터 집합''(또는 간단히 ''집합'')은 메모리에서 논리적으로 연속되어 있으며 하나의 데이터로 집합적으로 간주되는 기본형 그룹이다(예를 들어, 집합은 3개의 논리적으로 연속된 바이트일 수 있으며, 그 값은 공간의 한 점의 3개의 좌표를 나타낸다). 집합이 동일한 유형의 기본형으로 완전히 구성된 경우, 해당 집합을 ''배열''이라고 할 수 있다. 어떤 의미에서 멀티 바이트 ''워드'' 기본형은 바이트 배열이며, 일부 프로그램은 이러한 방식으로 워드를 사용한다.

포인터는 컴퓨터 과학에서 값 또는 객체를 저장하는 메모리 위치를 참조하거나 가리키는 데 사용되는 프로그래밍 개념이다. 본질적으로 데이터 자체를 저장하는 대신 다른 변수 또는 데이터 구조의 메모리 주소를 저장하는 변수이다.

포인터는 C 및 C++와 같이 직접적인 메모리 조작을 지원하는 프로그래밍 언어에서 일반적으로 사용된다. 이를 통해 프로그래머는 메모리를 직접 작업하여 효율적인 메모리 관리와 더 복잡한 데이터 구조를 사용할 수 있다. 포인터를 사용하면 메모리에 있는 데이터에 액세스하고 수정하고, 함수 간에 데이터를 효율적으로 전달하고, 연결된 목록, 트리 및 그래프와 같은 동적 데이터 구조를 만들 수 있다.

더 간단히 말해서 포인터를 컴퓨터의 메모리의 특정 지점을 가리키는 화살표로 생각하여 해당 위치에 저장된 데이터와 상호 작용할 수 있다.

''메모리 포인터''(또는 간단히 ''포인터'')는 기본형이며, 그 값은 메모리 주소로 사용될 의도이다. ''포인터는 메모리 주소를 가리킨다''라고 한다. 또한 포인터의 값이 데이터의 메모리 주소일 때 ''포인터는 [메모리 내의] 데이터를 가리킨다''라고도 한다.

더 일반적으로, 포인터는 일종의 참조이며, ''포인터는 메모리 어딘가에 저장된 데이터를 참조한다''라고 하며, 해당 데이터를 얻는 것을 ''포인터를 역참조한다''라고 한다. 포인터를 다른 종류의 참조와 구분하는 특징은 포인터의 값이 메모리 주소로 해석된다는 점인데, 이는 상당히 저수준의 개념이다.

참조는 간접성의 수준 역할을 한다. 포인터의 값은 계산에 사용할 메모리 주소(즉, 데이터)를 결정한다. 간접성은 알고리즘의 기본적인 측면이므로 포인터는 종종 프로그래밍 언어에서 기본 데이터 유형으로 표현된다. 정적으로 (또는 강력하게) 형식화된 프로그래밍 언어에서 포인터의 타입은 포인터가 가리키는 데이터의 타입을 결정한다.

4. C/C++ 에서의 포인터

C/C++는 포인터를 직접 지원하는 대표적인 언어이다. C/C++에서 포인터 변수는 `*` 기호를 사용하여 선언하며, 변수의 주소는 `&` 연산자를 사용하여 얻을 수 있다. 포인터 연산을 통해 메모리 주소를 직접 조작할 수 있다.

C에서 변수는 처리할 데이터의 숫자를 예측할 수 있다면 정적으로 선언하고, 예측할 수 없다면 동적 변수를 사용한다. C에서는 `malloc()`, `realloc()`, `free()` 함수를 사용하여 동적 메모리 할당을 관리한다. C++에서는 `new`와 `delete` 연산자를 사용하여 동적 메모리 할당을 관리하며, 내부적으로 `malloc()` 함수와 같은 기능을 수행한다.

C 언어에서 배열은 포인터와 유사하게 동작하며, 배열 이름은 배열의 첫 번째 요소를 가리키는 포인터로 해석될 수 있다. 함수 포인터를 사용하여 함수를 변수처럼 다룰 수 있다.

4. 1. C 포인터

포인터 변수는 가리키고자 하는 변수의 자료형과 일치해야 한다. 포인터 변수는 선언문과 실행문에서 사용법이 다르므로 주의해야 한다. `NULL` 포인터를 사용하여 유효하지 않은 주소를 나타낼 수 있다. 32비트 CPU와 64비트 CPU에서 포인터의 크기는 각각 4바이트, 8바이트이다. 포인터 연산은 자료형의 크기를 고려하여 수행된다.

C 언어에서 포인터는 특정 메모리 영역을 가리키는 것으로, 효율성 문제 때문에 존재한다. C는 원래 UNIX를 기술하기 위한 시스템용 언어로 개발되었기 때문에, 어셈블러가 실행할 수 있는 거의 모든 조작을 수행할 수 있어야 했다. 따라서 C는 메모리 영역에 값을 직접 대입하는 등 강력한 포인터 기능을 갖추고 있다.

C 언어의 실행 모델에서는 함수 코드와 데이터가 모두 1차원 주소에 직렬 배치된다. 따라서 데이터뿐만 아니라 함수의 주소를 얻어 다른 함수에 엔트리 포인트 정보로 전달할 수도 있다.

C 언어의 함수는 인수를 값 전달로만 지원하고, 참조 전달은 지원하지 않는다. 주소값을 취득하면 참조에 가능한 모든 것을 수행할 수 있기 때문에, 실질적으로 참조를 수치와 동일시할 수 있다. 초기 C 언어에서는 주소값이 정수형과 호환되는 것으로 취급되었다.

코드 영역을 포함한 메모리를 직접 다룰 수 있다는 것은 언어 수준에서 부정한 메모리 접근을 보호할 수 없음을 의미하며, C 언어 프로그램에서 포인터 관련 버그가 많이 발생하는 원인이 된다.

일반적인 C 언어 소스 코드에서는 포인터가 가리키는 영역의 값을 참조하는 간접 연산자 "``*``"와 주소 연산자 "``&``"를 사용하여 기술된다. 초기화되지 않은 포인터 변수는 불특정 영역을 가리키므로, Null(널) 값을 대입하여 포인터가 무효한 영역을 가리키고 있음을 명시해야 한다.
선언 예시:```c

/* int형 변수 n을 선언 */

int n;

/* int형 포인터 변수 ptr을 선언 */

int *ptr;

/* int형 변수 n의 주소를 포인터에 대입 */

ptr = &n;

```

별표 `*` 앞뒤의 공백 유무는 상관없다.
여러 변수 선언:```c

/* int형 포인터 변수 ptr과 int형 변수 n을 선언 */

int *ptr, n;

```

여러 개의 포인터 변수를 묶어서 선언하는 경우에는 다음과 같이 작성해야 한다.

```c

int *ptr0, *ptr1;

```
널 포인터:C 언어 처리 시스템에서는 무효한 포인터를 나타내는 값으로 `NULL` 매크로가 정의되어 있다.

```c

#define NULL ((void*)0)

```

C 언어에서는 void형 포인터는 임의의 타입의 포인터에 자유롭게 대입할 수 있다. 포인터에 무효 값을 대입하는 경우, `NULL` 매크로를 사용한다.

```c

int *ptr = NULL;

```
이용 예시:```c

int n = 0;

int *ptr = &n;

  • ptr = 10;

printf("%d\n", n); /* 10 */

```
배열과 포인터:C 언어에서 배열과 포인터는 각각 다른 데이터 타입이지만, 배열의 첨자 연산자는 포인터의 가감산과 디레퍼런스(역참조)의 구문 설탕이다.

```c

double a[10];

int i;

for (i = 0; i < 10; ++i) {

const double x = i * 0.1;

/* 아래는 모두 같은 의미를 갖는다. */

a[i] = x;

printf("%f\n", a[i]);

  • (a + i) = x;

printf("%f\n", *(a + i));

}

```

실행 시 요소 수가 결정되는 배열을 생성할 때 등, 동적으로 메모리 영역을 확보할 때는 결과를 포인터로 받는다. 확보한 메모리를 해제할 때도 포인터를 사용한다. 메모리 해제 직후의 포인터는 무효한 영역을 가리키는 "댕글링 포인터"(dangling pointer)가 되므로, 명시적으로 NULL을 대입하는 것이 좋다.[39]
함수의 인자:C 언어의 함수는 값 전달만 지원하므로, 출력은 반환값에 의한 1개만 가질 수 있지만, 포인터를 사용하면 여러 개의 출력을 갖는 함수를 정의할 수 있다.

```c

/* x는 double형 배열에 대한 포인터이며, const double *x로 선언할 수도 있다. */

double func(const double x[], int num, double *minVal, double *maxVal) {

int i;

double sum = 0.0;

assert(num > 0);

  • minVal = +DBL_MAX;
  • maxVal = -DBL_MAX;

for (i = 0; i < num; ++i) {

  • minVal = MIN(x[i], *minVal);
  • maxVal = MAX(x[i], *maxVal);

sum += x[i];

}

return sum;

}

/* 함수 호출 예시. */

double x[] = { 3, 19, 1, -3, -8, 0, 4 };

double minVal, maxVal, sum;

sum = func(x, 7, &minVal, &maxVal);

```

표준 입력을 정수 값으로 변환하고, scanf 함수의 두 번째 인자의 참조 대상인 `n`에 해당 정수 값을 출력하는 예시는 다음과 같다.

```c

int n;

int *ptr = &n;

scanf("%d", ptr);

```

또는

```c

int n;

scanf("%d", &n);

```
포인터에 대한 포인터:"포인터에 대한 포인터" (이중 간접 참조, 더블 포인터)를 정의할 수 있다. 동적으로 확보한 메모리에 대한 포인터를 함수 인자로 반환할 때나, 포인터 배열을 다룰 때 등에 사용된다.

```c

int *ptr;

int **pptr;

pptr = &ptr;

```
포인터 배열의 예시:```c

#include

/*

argc : 명령행 인자의 수.

argv : 명령행 인자의 문자열 그룹. 널 종료 문자열의 첫 번째 요소에 대한 포인터 char*의 배열이 있으며, 해당 첫 번째 요소에 대한 포인터이며, char *argv[]로 선언할 수도 있다. 배열의 요소 수는 argc + 1개.

마지막 요소 argv[argc]는 항상 NULL이 된다 (보초).

https://ja.cppreference.com/w/cpp/language/main_function

  • /

int main(int argc, char **argv) {

while (*argv != NULL) printf("%s\n", *argv++);

}

4. 2. C++ 포인터

C++는 C 포인터를 완벽하게 지원한다. C++11부터 C++ 표준 라이브러리는 스마트 포인터 ( `unique_ptr`, `shared_ptr`, `weak_ptr` )를 제공하여 메모리 누수 및 댕글링 포인터 문제를 해결하는 데 도움을 준다.

C++에서는 클래스의 비정적 멤버에 대한 포인터를 정의할 수 있다. 이러한 포인터는 연산자 `.*` 및 `->*`의 오른쪽에 사용되어 해당 멤버에 접근할 수 있다.[16]

```cpp

struct S {

int a;

int f() const { return a; }

};

S s1{};

S* ptrS = &s1;

int S::* ptr = &S::a; // S::a에 대한 포인터

int (S::* fp)() const = &S::f; // S::f에 대한 포인터

s1.*ptr = 1;

std::cout << (s1.*fp)() << "\n"; // 1 출력

ptrS->*ptr = 2;

std::cout << (ptrS->*fp)() << "\n"; // 2 출력

```

C++는 `void*` (void에 대한 포인터)를 보완하는 `void&` (void에 대한 참조)는 없다. 참조는 가리키는 변수에 대한 별칭처럼 작동하며, 유형이 `void`인 변수는 있을 수 없기 때문이다.

C++에서의 '''참조'''는 포인터와 마찬가지로 "변수가 메모리상에 위치한 곳"으로 해석될 수도 있지만, 그보다는 "해당 변수를 참조하는 (즉, 변수의 값을 조작할 수 있는) 권한"으로 해석되는 경우가 많다. 참조의 개념 자체는 메모리의 개념과 분리하여 생각하는 것이 가능하다 (구현상으로는 포인터와 같은 경우도 많다).

```cpp

int n = 5;

int& n2 = n; // n2를 n에 대한 참조로 초기화한다.

```

이 경우 변수 n2는 n을 참조한다. n2와 n은 객체를 공유하므로 n2라고 불러도 n이라고 불러도 같은 것을 나타낸다. 즉, 변수 n에 별칭 n2가 붙은 셈이다.

포인터는 미초기화 상태나, 아무것도 참조하지 않는 상태(널 포인터)가 허용되지만, C++에서의 참조는 반드시 초기화가 필요하며, 아무것도 참조하지 않는 상태는 허용되지 않는다. 단, 댕글링 포인터와 마찬가지로, 파괴된 영역에 대한 참조는 부적절하다.

```cpp

int& foo() {

int a = 0;

return a; // 부적절. 제어가 함수 호출 측으로 돌아간 시점에서 a는 파괴된다.

}

```

동적으로 할당된 메모리의 주소를 포인터를 통해 관리할 때, 주의 깊게 프로그래밍하지 않으면 해제 누락이나 댕글링 포인터와 같은 문제가 발생하기 쉽다. C++에서는 포인터를 클래스로 래핑하고, 소멸자의 메커니즘을 활용하여 해제 처리를 자동화한 "스마트 포인터"가 자주 사용된다. 또한 객체의 소유권 이동이나 공유와 같은, 원시 포인터로는 다루기 어려운 개념을 스마트 포인터의 기능으로 간단하게 구현하는 라이브러리도 존재한다.

4. 3. 포인터 연산

포인터 연산은 해당 자료형의 크기를 단위로 수행된다. 예를 들어, `int`형 포인터에 1을 더하면, 실제 메모리 주소는 `int`의 크기(보통 4바이트)만큼 증가한다. 이는 포인터가 연속된 메모리 공간에 있는 배열의 다음 요소를 가리키도록 하는 데 유용하다. C 언어에서 배열 인덱싱은 포인터 연산으로 정의되는데, `array[i]`는 `*(array + i)`와 동일하다.[8] 즉, 배열의 이름은 배열의 첫 번째 요소를 가리키는 포인터처럼 동작한다.

```c

int array[5]; // 5개의 정수를 저장할 수 있는 배열 선언

int *ptr = array; // 배열의 첫 번째 요소를 가리키는 포인터

ptr[0] = 1; // 포인터를 배열처럼 사용

  • (array + 1) = 2; // 포인터 연산을 통해 배열의 두 번째 요소에 접근

```

`void` 포인터는 어떤 자료형의 변수도 가리킬 수 있는 특수한 포인터이다. 그러나 `void` 포인터는 가리키는 자료형의 크기를 알 수 없기 때문에, 역참조하거나 연산을 수행하기 전에 명시적으로 형 변환을 해야 한다.[7] C와 C++에서는 `void*`를 사용하여 일반 포인터 유형을 지원한다. `void` 포인터는 모든 객체의 주소를 저장할 수 있지만, 직접 역참조할 수는 없다.

```c

int x = 4;

void* p1 = &x; // void 포인터는 어떤 자료형의 주소든 저장 가능

int* p2 = (int*)p1; // void 포인터를 사용하려면 명시적 형변환 필요

int a = *p2; // 이제 p2를 통해 x의 값에 접근 가능

```

포인터 연산은 C 및 C++에서 강력한 기능이지만, 주의해서 사용해야 한다. 포인터 연산은 언어 표준에 의해 단일 배열 객체의 경계 내(또는 그 직후)로 제한되며, 그렇지 않으면 정의되지 않은 동작을 유발한다. 포인터에 더하거나 빼면 데이터 유형 크기의 배수만큼 이동한다.

```c

int arr[5] = {1, 2, 3, 4, 5};

int *ptr = arr; // ptr은 arr의 첫 번째 요소를 가리킴

ptr = ptr + 2; // ptr은 이제 arr의 세 번째 요소를 가리킴 (arr[2])

4. 4. 동적 메모리 할당

C에서는 전통적으로 `malloc()` 함수를 사용하여 데이터 저장 공간을 힙 영역에서 확보한다. 이와 관련된 함수는 다음과 같다.[36]

함수설명
`malloc()`힙 영역으로부터 데이터 공간을 할당 받는다.
`realloc()`이미 할당된 메모리 공간의 크기를 조정한다.
`free()`더 이상 쓸 필요가 없는 메모리 공간을 힙 영역에 반환한다.



사용이 완료되면 `free()` 함수로 메모리를 해제해야 한다. 해제된 메모리 영역은 다시 재활용된다. 프로세서의 메모리 맵에서 힙(heap) 영역을 활용하여 동적 저장 공간을 확보한다. 각 프로세스마다 힙 영역은 크기가 정해져 있기 때문에 무한정 `malloc()` 함수를 사용하여 동적으로 메모리를 할당받을 수 없다. 따라서 개발자는 사용이 끝난 메모리를 `free()` 함수로 해제해 주어야 한다. 만약 동적 할당을 하지 못하면 주소값이 NULL로 반환된다.

```c

int main(int argc, char *argv[])

{

int *pa = NULL;

// ...

pa = (int *) malloc( sizeof(int));

if (pa == NULL) { // malloc()가 NULL 리턴하면 확보 실패.

printf("저장공간 확보 실패.\n");

return -1;

}


  • pa = 10;

printf("&pa = 0x%08X\n", &pa);

printf("pa = 0x%08X - 내용은 %d\n", pa, *pa);

// ...

if (pa) free(pa); // 사용 후 할당 영역을 반납한다.

return 0;

}

```

C++에서는 `new`와 `delete`가 추가되었다. 이것은 내부에서 `malloc()` 함수와 같은 기능을 수행한다. 따라서 표현법이 확장되었고, 힙 영역을 활용하는 것은 같다.[36]

포인터는 동적으로 할당된 메모리 블록의 주소를 저장하고 관리하는 데 사용된다. 이러한 블록은 데이터 객체 또는 객체 배열을 저장하는 데 사용된다. 대부분의 구조적 및 객체 지향 언어는 객체가 동적으로 할당되는 "힙" 또는 "프리 스토어"라고 하는 메모리 영역을 제공한다.

아래 C 코드 예제는 구조체 객체가 동적으로 할당되고 참조되는 방식을 보여준다. 표준 C 라이브러리는 힙에서 메모리 블록을 할당하기 위한 `malloc()` 함수를 제공한다. 이 함수는 할당할 객체의 크기를 매개변수로 사용하고 객체를 저장하기에 적합한 새로 할당된 메모리 블록에 대한 포인터를 반환하거나, 할당에 실패하면 널 포인터를 반환한다.

```c

/* 부품 재고 항목 */

struct Item {

int id; /* 부품 번호 */

char * name; /* 부품 이름 */

float cost; /* 비용 */

};

/* 새로운 Item 객체를 할당하고 초기화합니다 */

struct Item * make_item(const char *name) {

struct Item * item;

/* 새로운 Item 객체를 위한 메모리 블록을 할당합니다 */

item = malloc(sizeof(struct Item));

if (item == NULL)

return NULL;

/* 새로운 Item의 멤버를 초기화합니다 */

memset(item, 0, sizeof(struct Item));

item->id = -1;

item->name = NULL;

item->cost = 0.0;

/* 새로운 Item에 이름의 사본을 저장합니다 */

item->name = malloc(strlen(name) + 1);

if (item->name == NULL) {

free(item);

return NULL;

}

strcpy(item->name, name);

/* 새로 생성된 Item 객체를 반환합니다 */

return item;

}

```

아래 코드는 메모리 객체가 동적으로 할당 해제되는 방식, 즉 힙 또는 프리 스토어로 반환되는 방식을 보여준다. 표준 C 라이브러리는 이전에 할당된 메모리 블록을 할당 해제하고 다시 힙으로 반환하기 위한 `free()` 함수를 제공한다.

```c

/* Item 객체를 할당 해제합니다 */

void destroy_item(struct Item *item) {

/* 널 객체 포인터를 확인합니다 */

if (item == NULL)

return;

/* Item 내에 저장된 이름 문자열을 할당 해제합니다 */

if (item->name != NULL) {

free(item->name);

item->name = NULL;

}

/* Item 객체 자체를 할당 해제합니다 */

free(item);

}

4. 5. 자료 구조에서의 활용

포인터는 연결 리스트, 트리, 그래프 등 다양한 자료 구조를 구현하는 데 필수적이다. C 언어에서는 구조체와 포인터를 함께 사용하여 자료 구조를 정의한다.[4]

연결 리스트와 같은 자료 구조에서 포인터는 구조체의 한 부분을 다른 부분에 명시적으로 연결하기 위한 참조로 사용된다.[4]

다음은 C 언어에서 연결 리스트의 예시 정의이다.

```c

/* 빈 연결 리스트는 NULL

  • 또는 다른 센티넬 값으로 표현됩니다 */

#define EMPTY_LIST NULL

struct link {

void *data; /* 이 링크의 데이터 */

struct link *next; /* 다음 링크; 없으면 EMPTY_LIST */

};

```

이 포인터 재귀적 정의는 Haskell 언어의 참조 재귀적 정의와 기본적으로 동일하다.

```haskell

data Link a = Nil

| Cons a (Link a)

```

`Nil`은 빈 리스트이고, `Cons a (Link a)`는 `a` 형식의 cons 셀이며, 다른 링크도 `a` 형식이다.

C 언어의 자료 구조는 일반적으로 정확성을 신중하게 검사하는 래퍼 함수를 통해 처리된다.

이중 연결 리스트 또는 트리 구조에서, 요소에 저장된 백 포인터는 현재 요소를 참조하는 항목을 '다시 가리킨다'. 이는 메모리 사용량이 증가하는 대가로 탐색과 조작에 유용하다.

자료 구조인 리스트, 큐 및 트리를 설정할 때 구조의 구현 및 제어를 관리하는 데 도움이 되는 포인터가 필요하다. 포인터의 전형적인 예로는 시작 포인터, 끝 포인터 및 스택 포인터가 있다. 이러한 포인터는 '''절대''' (실제 물리 주소 또는 가상 메모리가상 주소) 또는 '''상대'''(전체 주소보다 적은 비트를 사용하는 절대 시작 주소("베이스")의 오프셋이지만 일반적으로 해결하려면 하나의 추가 산술 연산이 필요함)일 수 있다.

1바이트 오프셋, 예를 들어 문자의 16진수 아스키 값(예: X'29')을 사용하여 배열의 대체 정수 값(또는 인덱스)(예: X'01')을 가리킬 수 있다. 이러한 방식으로 문자는 '원시 데이터'에서 사용 가능한 순차적 인덱스로, 그리고 룩업 테이블 없이 절대 주소로 매우 효율적으로 변환될 수 있다.

4. 6. 함수 포인터

함수 포인터는 실행 가능한 코드, 즉 함수, 메서드 또는 프로시저를 가리킬 수 있는 포인터이다. 함수 포인터는 호출할 함수의 주소를 저장한다.[1] 이 기능을 사용하여 함수를 동적으로 호출할 수 있다.[1]

```C

int sum(int n1, int n2) { // 두 개의 정수 매개변수를 사용하여 정수 값을 반환하는 함수

return n1 + n2;

}

int main(void) {

int a, b, x, y;

int (*fp)(int, int); // sum과 같은 함수를 가리킬 수 있는 함수 포인터

fp = ∑ // fp는 이제 sum 함수를 가리킵니다

x = (*fp)(a, b); // 인수 a와 b로 sum 함수를 호출합니다

y = sum(a, b); // 인수 a와 b로 sum 함수를 호출합니다

}

```

위의 C 코드에서 `fp`는 `sum` 함수를 가리키는 함수 포인터이다. `(*fp)(a, b)`와 같이 함수 포인터를 사용하여 `sum` 함수를 호출할 수 있다.[1]

함수 포인터를 사용하면 다음과 같은 장점이 있다.

  • 함수를 다른 함수의 인자로 전달: 함수를 다른 함수의 인자로 전달할 수 있다.
  • 동적 함수 호출: 런타임에 어떤 함수를 호출할지 결정할 수 있다.
  • 콜백 함수 구현: 특정 이벤트가 발생했을 때 호출되는 콜백 함수를 구현하는 데 사용된다.


C에서는 함수를 가리키는 포인터 (함수 포인터)를 만들 수 있다.[1]

5. 다른 프로그래밍 언어에서의 포인터

Ada는 모든 포인터가 형식이 지정되고 안전한 형식 변환만 허용되는 강력한 형식의 언어이다. Ada의 포인터는 '액세스 타입'이라고 불린다.

BASIC의 일부 버전은 문자열 및 변수의 주소를 반환하는 함수를 지원한다. 그러나 FreeBASIC 또는 BlitzMax와 같은 최신 BASIC 방언은 포괄적인 포인터 구현을 가지고 있다.

C#에서 포인터는 코드 블록에 `unsafe` 키워드를 표시하여 사용할 수 있다.

COBOL은 `LINKAGE SECTION`을 통해 포인터 기반 데이터 객체를 선언하고, `USAGE IS POINTER` 절을 사용하여 포인터 변수를 선언할 수 있다.

D는 C/C++의 포인터를 완벽하게 지원한다.

Eiffel은 포인터 산술 없이 값과 참조 의미 체계를 사용하며, 포인터 클래스를 제공한다.

Fortran-90부터 강력한 형식의 포인터를 지원하며, Fortran-2003에서는 C 스타일 포인터와의 상호 운용성을 위한 기능을 제공한다.

Go는 포인터를 지원하지만, 포인터 연산은 허용하지 않는다.

자바에는 명시적인 포인터는 없지만, 참조를 통해 객체 및 배열을 다룬다.

Modula-2는 파스칼과 유사한 포인터를 지원한다.

Oberon은 Modula-2와 유사한 포인터를 지원하며, 가비지 컬렉션을 제공한다.

파스칼은 명시적으로 동적 변수만 참조할 수 있는 포인터를 지원하며, 포인터 연산은 허용하지 않는다. (일부 컴파일러는 확장 기능을 제공)

Perl은 `pack` 및 `unpack` 함수를 통해 제한적으로 포인터를 지원한다.

6. 포인터 관련 문제점 및 해결 방안

포인터는 여러 프로그래밍 오류의 원인이 될 수 있지만, 그 유용성 때문에 포인터 없이 프로그래밍하기는 어렵다. 많은 언어들은 포인터의 위험을 줄이기 위해 '참조'와 같은 더 안전한 구조를 제공한다.


  • '''와일드 포인터''': 초기화되지 않아 임의의 메모리 영역을 가리키는 포인터이다. 이 포인터를 사용하면 예상치 못한 동작이나 세그먼테이션 오류를 일으킬 수 있다.
  • '''댕글링 포인터''': 이미 해제된 메모리 영역을 가리키는 포인터이다.[42] 가비지 컬렉션이 있는 언어는 이러한 오류를 방지하는 데 도움이 된다.
  • '''메모리 누수''': 동적으로 할당된 메모리를 해제하지 않아 시스템의 사용 가능한 메모리가 줄어드는 현상이다.
  • '''버퍼 오버플로우''': 포인터 연산 오류로 인해 할당된 버퍼의 경계를 넘어선 메모리 영역에 접근하여 발생하는 문제이다. 주로 보안 문제로 이어진다.


C++와 같은 일부 언어는 스마트 포인터를 지원하여 댕글링 포인터와 메모리 누수의 가능성을 줄인다. 러스트 프로그래밍 언어는 '대여 검사기', '포인터 수명' 및 null pointer에 대한 옵션 유형을 도입하여 포인터 관련 버그를 제거한다.[43]

참조

[1] 논문 Structured Programming with go to Statements http://pplab.snu.ac.[...]
[2] 서적 Milestones in Computer Science and Information Technology https://archive.org/[...] Greenwood Publishing Group 2018-04-13
[3] 웹사이트 IEEE Computer Society awards list http://awards.comput[...] Awards.computer.org 2018-04-13
[4] 문서 ISO/IEC 9899
[5] 문서 ISO/IEC 9899
[6] 문서 ISO/IEC 9899
[7] 문서 ISO/IEC 9899
[8] 서적 ANSI and ISO Standard C Programmer's Reference https://archive.org/[...] Microsoft Press
[9] 웹사이트 WG14 N1124 http://www.open-std.[...]
[10] 웹사이트 Pointers Are Complicated II, or: We need better language specs https://www.ralfj.de[...]
[11] 웹사이트 Pointers Are Complicated, or: What's in a Byte? https://www.ralfj.de[...]
[12] 특허 Pointers that are relative to their own present locations
[13] 특허 System and method for database save and restore using self-pointers
[14] 웹사이트 Based Pointers http://msdn.microsof[...] Msdn.microsoft.com 2018-04-13
[15] 웹사이트 CWG Issue 195 https://cplusplus.gi[...] 2024-02-15
[16] 웹사이트 Pointers to Member Functions https://isocpp.org/w[...] isocpp.org 2022-11-26
[17] 웹사이트 c++filt(1) - Linux man page https://linux.die.ne[...]
[18] 웹사이트 Itanium C++ ABI http://refspecs.linu[...]
[19] 서적 Vägen till C
[20] 간행물 Pointers and Memory http://cslibrary.sta[...] Stanford Computer Science Education Library
[21] 웹사이트 6.4.4 Pointer-types http://standardpasca[...] ISO 7185 Pascal Standard (unofficial copy)
[22] 간행물 Ambiguities and Insecurities in Pascal Software: Practice and Experience 7
[23] 웹사이트 3.4 Pointers http://www.freepasca[...] Free Pascal Language Reference guide
[24] 웹사이트 "// Making References (Perl References and nested data structures)" http://perldoc.perl.[...] Perldoc.perl.org 2018-04-13
[25] 서적 プログラミング言語C 共立出版
[26] 웹사이트 https://riscv.org/te[...]
[27] 웹사이트 https://developer.ar[...]
[28] 웹사이트 https://www.intel.co[...]
[29] 서적 プログラミング言語C 共立出版
[30] 웹사이트 https://riscv.org/te[...]
[31] 웹사이트 https://developer.ar[...]
[32] 웹사이트 https://www.intel.co[...]
[33] 웹사이트 https://riscv.org/te[...]
[34] 웹사이트 https://developer.ar[...]
[35] 웹사이트 https://www.intel.co[...]
[36] 문서 "pointer of" ではない。
[37] 웹사이트 DCL04-C. ひとつの宣言で2つ以上の変数を宣言しない | JPCERT/CC https://www.jpcert.o[...]
[38] 웹사이트 NULL - cppreference.com https://ja.cpprefere[...]
[39] 웹사이트 MEM01-C. free() した直後のポインタには新しい値を代入する https://www.jpcert.o[...]
[40] 웹사이트 Using and Porting the GNU Compiler Collection (GCC) - C 言語ファミリに対する拡張機能 https://www.asahi-ne[...]
[41] 웹사이트 Using the GNU Compiler Collection (GCC): Warning Options https://gcc.gnu.org/[...]
[42] 논문 メモリ再利用を禁止するライブラリにより Use-After-Free 脆弱性攻撃を防止する手法の提案 https://cir.nii.ac.j[...] Computer Security Symposium 2014
[43] 웹사이트 所有権とライフタイム https://doc.rust-jp.[...] 2022-12-22



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com