C언어는 포인터에 산술 연산을 허용합니다. 계산된 값은 포인터가 참조하는 데이터 타입의 크기에 따라 스케일됩니다. 즉, p
가 T
타입 데이터를 가리키는 포인터이고, p
의 값이 xp
라면, 식 p+i
의 값은 xp + L * i
가 됩니다. 여기서 L
은 데이터 타입 T
의 크기입니다.
단항 연산자 &
와 *
를 사용하면 포인터를 생성하고 역참조할 수 있습니다. 즉, 어떤 객체를 나타내는 표현식 Expr
에 대해, &Expr
는 그 객체의 주소를 가리키는 포인터입니다. 주소를 나타내는 표현식 AExpr
에 대해, *AExpr
는 그 주소에 있는 값을 반환합니다. 따라서 Expr
와 *&Expr
는 동일합니다.
배열 참조 A[i]
는 식 *(A+i)
와 동일합니다. 이는 i번째 배열 원소의 주소를 계산한 다음 해당 메모리 위치에 접근합니다.
정수 배열 E
의 시작 주소와 정수 인덱스 i
가 각각 레지스터 %rdx
와 %rcx
에 저장되어 있다고 가정합니다. 각 표현식의 어셈블리 코드 구현도 함께 나와 있습니다.
E
: int *
, 값 xE
, 어셈블리: movl %rdx,%rax
E[0]
: int
, 값 M[xE]
, 어셈블리: movl (%rdx),%eax
E[i]
: int
, 값 M[xE + 4i]
, 어셈블리: movl (%rdx,%rcx,4),%eax
&E[2]
: int *
, 값 xE + 8
, 어셈블리: leaq 8(%rdx),%rax
E+i-1
: int *
, 값 xE + 4i - 4
, 어셈블리: leaq -4(%rdx,%rcx,4),%rax
*(E+i-3)
: int
, 값 M[xE + 4i - 12]
, 어셈블리: movl -12(%rdx,%rcx,4),%eax
&E[i]-E
: long
, 값 i
, 어셈블리: movq %rcx,%rax
이 예시에서 배열 값을 반환하는 연산들은 int
타입을 가지고, 따라서 4바이트 연산 (movl
)과 레지스터 (%eax
)을 사용합니다. 포인터를 반환하는 연산들은 int *
타입을 가지며, 8바이트 연산 (leaq
)과 레지스터 (%rax
)를 사용합니다. 마지막 예시는 같은 데이터 구조 내에서 두 포인터의 차이를 계산할 수 있음을 보여주며, 결과는 long
타입과 두 주소의 차이를 데이터 타입의 크기로 나눈 값이 됩니다.
이러한 포인터 산술과 관련된 원칙은 메모리 주소와 데이터의 레이아웃을 이해하고, 그것을 효과적으로 활용하고자 할 때 중요합니다.