SturdyCobble's Study Note

[프로그래밍] 6.4 포인터와 배열 (1) 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 6.4 포인터와 배열 (1)

StudyingCobble 2020. 2. 15. 13:40

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

포인터는 C/C++에 존재하는 개념입니다. 포인터에 대한 설명을 Wikipedia에서 가져와 봤습니다.

 

   밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

 

포인터는 C/C++에 존재하는 개념입니다. 포인터에 대한 설명을 Wikipedia에서 가져와 봤습니다.

 

    ■  pointer is a programming language object that stores a memory address

    ■  A memory pointer (or just pointer) is a primitive, the value of which is intended to be used as a memory

         address; it is said that a pointer points to a memory address.

    ■  More generally, a pointer is a kind of reference, and it is said that a pointer references a datum stored

         somewhere in memory; to obtain that datum is to dereference the pointer. The feature that separates

         pointers from other kinds of reference is that a pointer's value is meant to be interpreted as a memory

         address, which is a rather low-level concept.

(From https://en.wikipedia.org/wiki/Pointer_(computer_programming))

 

그리고 여기에 존재하는 reference의 개념은 다음과 같이 정의되어 있었습니다.

 

    ■  In computer science, a reference is a value that enables a program to indirectly access a particular datum.

(From https://en.wikipedia.org/wiki/Reference_(computer_science))

 

위의 내용을 요약해보자면, 포인터는 메모리 주소(memory address)를 저장하는 것으로서, 이를 통해 특정 데이터에 대한 간접적인 접근을 가능하게 한다고 볼 수 있겠습니다. 그리고 이는 넓은 의미에서 reference에 포함됩니다.

 

 그런데, C++, Java에는 또다른 reference의 개념이 있습니다. 여기서 reference 개념은 보다 좁은 의미의 개념으로 볼 수 있습니다. 이러한 흐름을 요약해보자면 다음과 같이 그려볼 수 있겠습니다. (관련 내용은 이후 다른 글에서 다루어보겠습니다.)

하지만, 이러한 구현은 컴파일러나, 운영 환경, 언어와 버전마다 다를 수 있음을 알아두어야 합니다. 또한, 정의하기 나름이겠지만 Reference가 넓은 의미에서 저 두가지만을 포함하는 것은 아닙니다. 그리고 분류하기 나름이겠지만, C++의 Reference에는 없는 C++의 Pointer와 Java의 Reference의 공통점이 존재하기도 합니다.

 

 

 

 서론이 길었지만, 결국 포인터는 (적어도 C/C++에서는) 메모리 주소를 저장하여, 특정 데이터에 간접적으로 접근할 수 있게 해주는 것이라 할 수 있습니다. 이러한 포인터 변수를 만든다고 생각하면, 일반적인 변수 선언과 함께, 이게 포인터라는 점, 그리고 어떤 데이터를 가리킬 것이라는 점을 알려줘야 할 것입니다.

특별히 그 데이터가 어떤 데이터인지 알려주면, 변수의 크기가 다양하기 때문에, 시작하는 곳의 메모리 주소부터 시작해서 정확히 원하는 지점까지 가져올 수 있을 것입니다.

 

 예를 들어, int형 (4 Byte) 변수 bar를 간접적으로 가리키는 포인터 변수 foo를 선언해 볼 수 있을 것입니다. 이는 코드 상에서는 다음과 같이 나타납니다. (bar는 위에 어딘가 선언되었다고 가정해봅시다)

int *foo;
foo = &bar;

여기서 첫번째 줄의 *는 연산자가 아니라, 그냥 포인터임을 알려주는 용도일 뿐입니다. 일종의 구두점으로서, 우리가 말하면서 ?나 !을 사용하는 것과 비슷한 느낌입니다.

두번째 줄은 foo라는 int형 포인터에 bar의 주소를 대입하는 식입니다. 여기서 &는 주소 연산자로, 변수의 주소를 얻기 위해 사용됩니다. 

 

참고로 다음과 같은 식도 가능합니다. 이 경우, foo는 정수형 변수, bar는 정수형 포인터 변수가 됩니다.

int foo, *bar;

즉 *의 위치와 사이 공백의 유무는 의미차이를 주지 않습니다. 만약 둘다 포인터로 만들고 싶다면, ' * '을 두 번 사용해야 할 것입니다. 

 

포인터의 개념을 말하면서, 간접적으로 데이터에 접근한다는 점을 위에서 언급했었습니다. 다시 그 값에 접근하기 위해서는 *연산자를 사용합니다. 여기서는 일종의 단항(Unitary) 연산자라고 볼 수 있습니다. 

int bar = 10;
int *foo;
foo = &bar;
printf("%d",*foo);

 

선언할 때의 *는 일종의 구두점으로 값을 참조할 때 사용하는 *는 연산자로서 다른 의미를 가집니다. 아래 구문은 따라서 가리키는 값에 주소값을 집어넣는 게 아니라, 포인터 변수를 초기화하는 예시가 됩니다. (애초에 가리키는 값에 주소값을 집어넣는 다는 이야기 자체가 성립하지 않습니다.)

 

int bar = 100;
int *foo = &bar;
// int *foo; foo = &bar; 와 동일

 

포인터가 값을 저장하지 않는다는 사실은 직접 그 값을 출력해 보면 확인할 수 있습니다. 또한 그 크기를 구해봐도 확인할 수 있습니다.

그리고 아래 예시로 부터, 참조하는 변수의 값이 변화하면 가리키는 값도 당연히 변화하는 것을 확인할 수 있습니다.

 

# include <iostream>

using namespace std;

int main(){
  int bar = 300;
  int *foo = &bar;
  __int64 big_bar = 300;
  __int64 *bfoo = &big_bar;
  cout << "*foo : " << *foo << ", sizeof(foo) : " << sizeof(foo) ;
  cout << ", sizeof(bfoo) : " << sizeof(bfoo) << endl;
  cout << "sizeof(*bfoo) : " << sizeof(*bfoo) << ", foo : " << foo << endl;
  bar = 500;
  cout << "*foo after the change in bar : " << *foo << endl;
}

-----------------RESULT-----------------
*foo : 300, sizeof(foo) : 4, sizeof(bfoo) : 4
sizeof(*bfoo) : 8, foo : 0x61ff04
*foo after the change in bar : 500

 

포인터 변수의 크기는 컴파일러나 운영환경, 그리고 CPU에 따라 달라집니다. 1 word만큼(CPU가 한 번에 처리하는 데이터 크기)을 사용하는 것이 일반적입니다. 이는 32비트 시스템에서는 32비트 = 4 바이트, 64비트 시스템에서는 64비트 = 8 바이트가 됩니다. 32비트 시스템은 32개 비트를 가지고 메모리의 주소를 표현하므로, 이렇게 설정하는 것이 합당해 보입니다.

 

(하지만, 운영체제에 따라 주소를 64비트로 표현하는 것이 아닌 더 작은 비트만으로 표현하기도 합니다. 이 경우 64비트에 해당하는 주소를 모두 차지할 만큼 메모리를 증설하는 것은 불가능합니다. 그리고 이러한 세부 사항은 컴파일러나 여러가지 조건에 좌우될 수 있습니다.)

 

다음 글에서는 본격적으로 포인터를 활용하고 연산하는 과정에 대해 알아보겠습니다.

Comments