#include <stdio.h>
#include <stdlib.h>
typedef unsigned char* pointer;
int main() {
unsigned int v1 = 0x1234CDEF;
pointer orig;
orig = (pointer) &v1;
printf("v1: %#x orig: %#x\n", v1, orig);
printf("orig[0]: %#x, orig[1]: %#x, orig[2]: %#x, orig[3]: %#x\n", orig[0], orig[1], orig[2], orig[3]);
return 0;
}
/* 출력 결과
v1: 0x1234cdef orig: 0x6fdff1e8
orig[0]: 0xef, orig[1]: 0xcd, orig[2]: 0x34, orig[3]: 0x12
*/
1. unsigned int 자료형 v1의 주소를 가리키는 포인터 orig를 선언했습니다.
unsigned int 자료형의 크기는 4Byte이지만, pointer (unsigned char) 타입은 1Byte이기 때문에 orig은 한번에 v1의 1Byte에 해당하는 값만 저장할 수 있습니다.
당연히 첫번째 출력에서 orig을 그대로 출력하면 0x1234CDEF가 나올 리 없습니다. orig을 그대로 출력한다는 건 orig이라는 포인터가 가리키고 있는 주소값을 (출력 포맷이 %#x이니 16진수로) 출력한다는 뜻이기 때문입니다.
2. 포인터가 가리키는 값, 즉 포인터가 담고 있는 주소를 가지고 그 주소에 맞는 메모리에 저장되어 있는 값을 참조하기 위해서는,
1) *orig으로 * 연산자를 사용하거나 2) 배열 방식으로 orig[0], orig[1], orig[2]...와 같은 방식으로 참조하면 됩니다.
이때 orig는 1Byte 포인터지만, 아까 4Byte짜리 v1이라는 변수의 주소를 담았습니다.
따라서 orig[0]부터 orig[3]까지 총 4번에 걸쳐 v1의 변수를 얻을 수 있게 됩니다.
변수 v1 = 0x1234CDEF (십진수로는 305450479이 되겠네요)이었으니까 차례로
orig[0] = 0xEF
orig[1] = 0xCD
orig[2] = 0x34
orig[3] = 0x12
가 됩니다.
16진수에서 각 자릿수는 4bit의 정보를 담고 있습니다($2^4 = 16$).
orig[0] ~ orig[3]은 따라서 각각 8bit, 즉 1Byte이며 이는 orig이라는 포인터 타입 unsigned char 자료형의 크기 1Byte와 당연히 일치하는 걸 다시 한번 확인할 수 있습니다.
3. 어떤 변수가 포인터로 정의되어 있고, 그 포인터가 담고 있는 값(엄밀히 말하자면 포인터가 담고 있는 주소가 가리키는 값)을 활용하고 싶을 땐 어떻게 해야할까요?
먼저 포인터의 복사본을 만드는 상황을 가정해봤습니다.
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char* pointer;
int main() {
unsigned int v1 = 0x1234CDEF;
pointer orig;
orig = (pointer) &v1;
pointer orig_copy;
orig_copy = orig;
for (int i=0; i<4;i++){
printf("orig_copy[%d]: %#x\n", i, orig_copy[i]);
}
// change
orig_copy[1] = 0x33;
printf("\norig_copy[1]: %#x, orig[1]: %#x\n", orig_copy[1], orig[1]);
return 0;
}
/* 출력 결과
orig_copy[0]: 0xef
orig_copy[1]: 0xcd
orig_copy[2]: 0x34
orig_copy[3]: 0x12
orig_copy[1]: 0x33, orig[1]: 0x33
*/
파이썬이나 다른 언어에서 상습적으로 하던 대로 그냥 = 대입 연산자로
orig_copy = orig; // orig_copy = &orig[0];
대입해버리면 포인터 자체를 복사하는 것이기 때문에, 참조하는 메모리도 똑같게 됩니다.
그러니까 orig_copy를 통해서 값을 수정하면 orig이 가리키던 값도 똑같이 바뀐다는 뜻입니다. (꼭 파이썬의 list 대입하고 수정하는 거랑 같은 원리죠?)
그런데 복사본은 보통 언제 무슨 상황에서 생성할까요?
원래의 변수 값을 바꾸지 않고 복사본에서만 수정을 가할 때 복사본을 만드는 게 대부분입니다.
따라서 위에 코드에서 orig이 가리키는 값을 바꾸지 않고 orig_copy를 통해서만 값을 바꾸고 싶다면, 다음과 같이 선언하면 됩니다.
*orig_copy = *orig;
여기서 전제는 포인터 orig_copy와 orig 모두 같은 타입의 포인터로 선언했다는 점입니다.
포인터 앞에 *를 붙이면, 해당 포인터가 담은 주소로 가서 그 값을 참조한다는 뜻입니다.
말이 헷갈릴 수 있는데, 굳이 풀어서 쓰자면 'orig가 가리키는 값을 orig_copy가 가리키는 값에 저장하라'라는 뜻입니다.
그러니까 무턱대고 orig_copy = orig; 처럼 선언하면 안되겠죠. (저를 비롯한) C언어 초보자들이 가장 많이 하는 실수입니다.
다시 한번 정리해보겠습니다.
포인터에 값을 대입할 땐, 포인터 그 자체를 대입해서도 안되며
1) (포인터가 아닌)변수의 주소(& 사용)를 대입하거나
2) 포인터가 가리키는 값
만을 대입해야 합니다.
'2023년 이전 > C언어' 카테고리의 다른 글
fgets 함수의 예시 (0) | 2022.06.03 |
---|---|
C언어: 공백을 구분해서 문자열 나누어 입력받기(feat. strtok 함수) (0) | 2022.03.28 |
맥북에어 M1에 C언어 개발환경 구축하기 (feat. Visual Studio code) (0) | 2022.03.06 |