소년코딩

07.10 - 포인터 산술과 배열 인덱싱 (pointer arithmetic and array indexing)

포인터 산술 (pointer arithmetic)

C++ 언어를 사용하면 포인터에서 정수 추가 또는 빼기 작업을 수행할 수 있다. 만약 ptr이 정수를 가리키면 ptr+1ptr 다음 메모리의 정수 주소가 된다. ptr-1ptr 이전 정수의 주소다.

ptr+1ptr 뒤 메모리 주소를 반환하지 않고 ptr이 가리키는 객체의 다음 메모리 주소를 반환한다. ptr이 정수 (4바이트라고 가정)를 가리키면, ptr+3ptr 이후에 3개의 정수 (12바이트)를 의미한다. ptr이 항상 1바이트인 char을 가리키면, ptr+3ptr 이후에 3개의 문자(3바이트)를 의미한다.

포인터 산술 표현식을 계산할 때 컴파일러는 항상 피연산자에 가리키고 있는 객체의 크기를 곱한다. 이를 스케일링(scaling)이라고 한다.

#include <iostream>

int main()
{
    int value = 7;
    int *ptr = &value;

    std::cout << ptr << '\n';
    std::cout << ptr+1 << '\n';
    std::cout << ptr+2 << '\n';
    std::cout << ptr+3 << '\n';

    return 0;
}
0012FF7C
0012FF80
0012FF84
0012FF88

위 예제의 출력 결과를 보면, 출력된 주소는 각각 4바이트씩 차이가 난다. (7C+4 = 80) (테스트한 컴퓨터는 정수가 4바이트인 환경이다.)

int 대신 short를 사용하는 같은 프로그램 :
#include <iostream>

int main()
{
    short value = 7;
    short *ptr = &value;

    std::cout << ptr << '\n';
    std::cout << ptr+1 << '\n';
    std::cout << ptr+2 << '\n';
    std::cout << ptr+3 << '\n';

    return 0;
}
0012FF7C
0012FF7E
0012FF80
0012FF82

short는 2바이트이므로 각 주소는 2바이트씩 차이가 난다.


배열은 메모리에 순차적으로 배치된다. (Arrays are laid out sequentially in memory)

주소 연산자(&)를 사용해서 배열이 메모리에 순차적으로 배치되었는지 확인할 수 있다. 즉 요소 0, 1, 2, ...은 모두 순서대로 인접해 있다.

#include <iostream>

int main()
{
    int array[] = { 9, 7, 5, 3, 1 };

    std::cout << "Element 0 is at address: " << &array[0] << '\n';
    std::cout << "Element 1 is at address: " << &array[1] << '\n';
    std::cout << "Element 2 is at address: " << &array[2] << '\n';
    std::cout << "Element 3 is at address: " << &array[3] << '\n';

    return 0;
}
Element 0 is at address: 0041FE9C
Element 1 is at address: 0041FEA0
Element 2 is at address: 0041FEA4
Element 3 is at address: 0041FEA8

이 메모리 주소는 각각 4바이트씩 떨어져 있으며, 테스트한 컴퓨터의 정수 크기다.


Pointer arithmetic, arrays, and the magic behind indexing

  • 위에서 배열은 메모리에 순차적으로 배치된다는 것을 배웠다.
  • '07.09 - 포인터와 배열' 포스트에서 고정 배열이 배열의 첫 번째 요소(인덱스 0)를 가리키는 포인터로 변환될 수 있다는 것도 배웠다.
  • 포인터에 1을 추가하면 메모리가 가리키는 다음 객체의 메모리 주소가 반환된다는 것을 배웠다.

따라서 배열에 1을 추가하면 배열의 두 번째 요소(인덱스 1)를 가리킨다는 걸 알 수 있다.

이것을 다음 예제로 증명할 수 있다.

#include <iostream>

int main()
{
     int array [5] = { 9, 7, 5, 3, 1 };

     std::cout << &array[1] << '\n'; // print memory address of array element 1
     std::cout << array+1 << '\n'; // print memory address of array pointer + 1 

     std::cout << array[1] << '\n'; // prints 7
     std::cout << *(array+1) << '\n'; // prints 7 (note the parenthesis required here)

    return 0;
}

연산자 *가 연산자 +보다 우선순위가 높으므로, 포인터 산술 결과를 역참조하려면 연산자 우선순위를 올바르게 하려면 괄호가 필요하다.

0017FB80
0017FB80
7
7

일반적으로 array[n]*(array + n)과 같다.


포인터를 사용해서 배열 반복 (Using a pointer to iterate through an array)

포인터와 포인터 산술을 통해 배열을 반복할 수 있다. 첨자([]) 사용이 일반적으로 더 읽기 쉬우므로 이 방법은 잘 쓰이지 않지만, 다음 예제는 이것이 가능하다는 것을 보여준다.

const int arrayLength = 7;
char name[arrayLength] = "Mollie";
int numVowels(0); // 모음의 갯수

for (char* ptr = name; ptr < name + arrayLength; ++ptr)
{
    switch (*ptr)
    {
        case 'A':
        case 'a':
        case 'E':
        case 'e':
        case 'I':
        case 'i':
        case 'O':
        case 'o':
        case 'U':
        case 'u':
            ++numVowels;
    }
}

cout << name << " has " << numVowels << " vowels.\n"; // Mollie has 3 vowels

어떻게 작동할까? 위 프로그램은 포인터를 사용하여 배열의 각 요소에 접근한다. 고정 배열은 배열의 첫 번째 요소를 가리키는 포인터 변환될 수 있다는 걸 기억하자. 따라서 ptr은 배열의 첫 번째 요소를 가리키게 됩니다. 각 요소의 값은 스위치 식에 의해 구분되며, 모음일 경우 numVowels가 증가한다. 그런 다음 루프의 ++연산자를 사용하여 포인터를 배열의 다음 문자로 전진시킨다. 모든 문자를 검사하면 for 루프가 종료된다.


cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기