[C] 배열과 포인터
배열과 포인터 표기법 관계
포인터는 메모리 시작주소를 기준으로 크기에 따라 전체 메모리 주소를 기억한다.
배열도 다르지 않다. 배열도 시작주소를 기준으로 크기에 따라 전체 메모리 주소를 기억한다.
포인터는 *연산자를, 배열은 [ ] 연산자를 쓴다 뿐이지 둘은 본질적으로 유사하다.
따라서 다음과 같이 표현할 수 있다.
#include <stdio.h>
// ==== 코드 A : 배열 ====
void main ()
{
char data[5] ;
/* 아래 둘은 같은 표현임 */
data[1] = 5;
*(data +1) = 5;
//tip : *(data +0) 은 *(data)로 줄여서 쓸 수 있다.
}
// ==== 코드 B : 포인터 ====
#include <stdio.h>
void main ()
{
char data;
char *p = &data;
/* 아래 둘은 같은 표현임 */
*p = 3;
p[0] = 3;
}
*(data +1) =5 은 배열을 포인터처럼 사용한 예로, *p = 5 의 표현이 의미하는 바를 다시 한번 더 생각하게 만든다.
*p는 사실 포인터 p가 가리키고 있는 주소이고, 변수 data 역시 컴퓨터가 봤을때는 그 변수가 가지고 있는 메모리 주소를 나타낸다는 점에서 포인터에 직접적으로 data 를 주는 것은 사실 주소를 지정하는 것돠 다르지 않다. 따라서 *(data +1) = 5 와 data[0] = 5는 같은 표현이된다.
코드B의 경우도 같은 맥락에서 포인터는 가리키는 주소를 나타내는 변수의 [0]번째 배열을 가리키는 것이라는 것을 알 수 있다.
배열 표기법의 한계
둘이 문법적으로만 다르지 본질은 똑같다는걸 알았다. 그래서 어쩌라는걸까?
배열과 포인터가 본질적으로 같다는 것은 배열에 저장된 데이터를 더 세밀하게 조작할 수 있다는 것을 의미한다.
이는 포인터의 형변환을 통해 가능하다.
아래와 같이 선언된 array에서
#==== arr 선언
int arr = {0x12345678 , 0x12345678};
시작주소 1바이트에 저장된 0x78을 0x22로 바꾸고 싶을때 어떻게 할까?
arr[0] = 0x22;
배열은 선언된 자료형태에 따라 연산된다. 따라서 위와 같은 코드는 첫 메모리 뭉텅이, 그러니까 int에 해당하는 4바이트를 0x22로 바꾼다는 것을 의미하고, 0x12345678이 저장되어있던 4바이트는 0x00000022 가 되어버린다.
이 문제는 포인터를 활용해서 해결할 수 있다.
*(char *)(arr) = 0x22;
포인터의 형태를 1바이트인 char로 변환하고 (이는 일시적이다.) arr[0]을 주소로 주면 arr[0]의 첫 1바이트를 0x22로 변경하게된다.
포인터 표기의 번잡함
배열을 포인터로 변경함으로써 얻는 이점은 확실하다. 혹시 포인터 표기를 배열 표기로 바꿔서 얻는 이득이 있을까?
int tips = 0x12345678 ;
char *p ;
*p = (char *)&tips; // 1바이트 단위로 쪼개서 주소를 부여.
sum = *(p+0) + *(p+1) + *(p+2) + *(p+3); //각 바이트값을 합산(포인터 표기)
sum = p[0] + p[1]+ p[2] + p[3]; //각 바이트값을 합산(배열 표기)
포인터표기를 배열표기로 바꿔서 얻는 것은 간결함이다.
다만 이러한 표기는 직접 작성한 사람이 아니라면 p를 배열이라고 착각하도록 만들 수 있기 때문에 항상 권장되는 형태는 아니다.
*과 & 표기법
*과 &의 역할이 좀 헷갈린다.
&는 주소를 반환하라는 뜻이고 *는 포인터라는 뜻인가?
정확히 말하면 *는 *다음에 오는 것이 가리키는 주소를 의미한다.
char *p는 그렇다면 p라는 변수가 가리키는 주소를 의미하는 것이다.
따라서 *p = &addr로 쓰지 않던가?