■ 포인터 스터디 [1]■
■ 1. 포인터란…■
◐ 변수와 주소
우리가 흔히 사용하는 변수. 변수를 이름 그대로 해석해 보면 「변할 수 있는 값」이라고 하네요.
그렇지만 값이라고 하기에는 조금 무리가 있다. 하나의 값이 다른 값으로 그냥 바뀔 수 는 없으니.
변수라는 것을 좀 더 구체적으로 말하자면 『값을 저장할 수 있는 메모리』라고 할 수 있다.
이미 아실테지만, 변수는 메모리의 일부입니다.
물론 모든 메모리가 변수가 되는건 아니지만 그 가능성은 가지고 있다.
우리가 변수를 선언할 때 컴파일러는 자기가 알아서 비어있는 메모리를 찾아 그 메모리를 변수로 사용하게 되는 거다.
그렇다면 컴파일러는 어떻게 각 메모리를 구분하면 우리가 집이나 사람을 구분할 때 바로 주소나 주민등록번호 같은 번호를 사용하
지요. (주소는 일부만 번호이긴 하지만… 더 쉽게 아파트의 호수를 생각하면 된다.) 컴퓨터도 마찬가지 이다.
각각의 메모리에 주소를 붙여 구별을 하는 것이다.
그리고 컴퓨터의 메모리는 한줄로 이어져 있기 때문에(이를 「선형 메모리」라 한다) 연속된 값으로 주소값이 표시 한다.
즉 컴퓨터의 메모리 크기가 100이라고 하면 0부터 시작해서 99까지의 주소가 존재하는 것이다.
그런데 컴퓨터는 일반적으로 수백만 이상의 크기를 가지므로 그만큼 주소값의 범위도 커지게 된다.
그래서 흔히 주소값은 4바이트의 long형으로 나타내는 것이다.
◐ 포인터 상수와 변수
포인터 상수는 별게 아니다. (물론 포인터 변수도 별게 아니지만…) 포인터 상수는 바로 위에서 말씀드린 주소를 말하는 겁니다.
당연히 크기도 4바이 트이다 컴퓨터의 가장 첫번째 메모리의 포인터는 0x00000000이 된다
그 다음은 0x00000001…(앞에 0x를 붙인건 16진수라는거) 그렇다면 포인터 변수는 바로 「포인터 상수를 저장할 수
있는 변수」이다.. 포인터 변수의 크기도 포인터 상수처럼 4바이트 이다.
char *p; // 포인터 변수를 선언할 때 「*」를 붙이고
이 p라는 포인터 변수는 포인터를 저장할 수 있다. char의 포인터형이 긴 하지만 포인터는 포인터 입니다.
앞에 정해진 자료형이 무엇이든 상관없이 크기는 4바이트인 거다.
char ch = 'A'; // 'A'의 코드 값은 65 이다
char *pc = &ch; // 변수의 주소값을 알아낼 때는 「&」를 사용한다.
// ch의 주소값이 0x0000001A라고 가정한다.
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃ ┏━━━━━━━━━━━━━━━━┓ ┃
┣━━┫ ▼ ┃
┃주소 ┃ 10 1A 1B 1C 1D 1E 21 22 23 24 25 ┃
┃ ┃ ┳━┳━┳━┳━┳━┳━┳━━━━━━┳━ ━━━━━━ ┳ ━┳┃
┃ 값 ┃ ┃ ┃65┃ ┃ ┃ ┃ ┃ … ┃1A┃00┃00┃00┃ ┃┃
┃ ┃ ┻━┻━┻━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름 ┃ ch pc ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
컴퓨터가 수를 저장하는 방법은 좀 특이 하다. 바이트 별로 거꾸로 저장 하는 것.
즉, 0x12345678을 첫번째 바이트에 78, 두번째 바이트에 56,세번째 바이트에 34,
네번째 바이트에 12를 저장하는 것 이유는 난중에 한번더...
한가지 더. 이 글에 사용되는 모든 주소값은 임의로 붙인 것이다. 컴파일 러가 마음대로 빈 공간을 찾아
사용하기 때문에 특별히 정해진 값이 있을수가 없기 때문이다
int ih = 0x13;
int *pi = &ih;
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소 ┃ 41 42 43 44 45 46 5A 5B 5C 5D 5E ┃
┃ ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃13┃00┃ ┃ ┃ ┃ … ┃42┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름 ┃ ih pi ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
long lh = 0x00781253;
long *pl = &lh;
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소 ┃ 65 66 67 68 69 70 7A 7B 7C 7D 7E ┃
┃ ┃ ┳━┳━━━━━━━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃53┃12┃78┃00┃ ┃ … ┃66┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━━━━━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름 ┃ lh pl ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
◐ *연산자의 쓰임새
모든 연산자가 그렇듯이 *와 &도 여러가지 쓰임새를 가지고 있다. 우선 *는 곱을 구하기도 하고 위에서
사용된 것 처럼 자료형에 붙어 포인터 변수임 을 나타내기도 한다.
(이 경우는 연산자라고 보기는 좀 어렵지요) 그리고 또 한가지. 「주어진 주소의 값을 읽어내는」 일도 한다.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ #include <stdio.h> ┃
┃ ┃
┃ void main(void) { ┃
┃ int i; ┃
┃ for (i = 0; i < 100; i++) ┃
┃ printf("%c", *(char *)i); ┃
┃ } ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
실행해 보면 화면이 이상한 문자들만 나온다. 이 문자들이 뜻하는게 무엇이나면 i 값이 0부터 99까지 증가할
테고 printf 문에서 무언가를 출력하라 한다.. 그 부분만 뜯어보면,
*(char *)i
i가 0인 경우 위의 코드는 다음과 같게된다.
*(char *)0
우선 0이라는 수(상수)를 char *형으로 캐 스팅 했다. 이제 0은 그냥 수가 아닌 char 크기(1 바이트)의 메모리를 나타
낼 수 있는 포인터 상수가 된다. 그리고 거기에 *연산자를 붙여 값을 읽어내고 있다. 이 소스 코드는 0번 메모리의 값을 읽어서 출력
하는 것. 이렇게 99번째 메모리까지 모두 100개의 메모리의 내용을 보여주는 코드인 것. 물론 우리가 알아볼 수는 없는 글자들만
잔뜩 있지 만.
만약에 (char *)로 캐스팅을 안하고 *0이라고만 쓰면 에러가 난다
*연산자는 그 다음에 오는 내용을 포인터라고 가정을 하기는 하는데, 거기서 얼마만한 크기의 메모리를 읽어와야 하는지를 모르는
일이다.. 즉 *앞의 자료형은 「그 포인터의 값을 *를 사용해서 읽어올 때 얼마만한 크기를 읽어와야 하는지를 정해주는 것」
이라 할 수 있다.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ #include <stdio.h> ┃
┃ ┃
┃ void main() { ┃
┃ int ih = 0x0506; ┃
┃ char *pp = (char *)&ih; ┃
┃ ┃
┃ long lh = 0x01020304; ┃
┃ char *pc = (char *)&lh; ┃
┃ int *pi = (int *)&lh; ┃
┃ ┃
┃ printf("- integer -\n"); ┃
┃ printf("ih=%X\n", ih); ┃
┃ printf("(char)ih=%X, *(char *)=%X\n", (char)ih, *pp); …① ┃
┃ ┃
┃ printf("\n- long integer -\n"); ┃
┃ printf("lh=%lX\n", lh); ┃
┃ printf("(char)lh=%X, *(char *)=%X\n", (char)lh, *pc); …② ┃
┃ printf("(int)lh=%X, *(int *)=%X\n", (int)lh, *pi); …③ ┃
┃ } ┃
┃ ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ <결과> ┃
┃ - integer - ┃
┃ ih=506 ┃
┃ (char)ih=6, *(char *)=6 ┃
┃ ┃
┃ - long integer - ┃
┃ lh=1020304 ┃
┃ (char)lh=4, *(char *)=4 ┃
┃ (int)lh=304, *(int *)=304 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
①의 첫번째 값은 int형 0x0506을 char형으로 캐스팅한 값이다. 당연하게 도 int는 2바이트,
char형은 1바이트이므로 char형을 벗어나는 부분이 잘려나간 6이 결과가 된다.
포인터를 사용한 연산도 마찬가지다. pp는 1바이트 크기의 변수를 포인트하는 포인터 변수이다.
이 pp 변수를 사용해 그 메모리를 읽어오게 되 면 그 첫번째 바이트 한 바이트만을 읽어오게 되는 것이다.
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소┃ 41 42 43 44 45 46 5A 5B 5C 5D 5E ┃
┃ ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃06┃05┃ ┃ ┃ ┃ … ┃42┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ ih pp ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
pp는 char *형 변수이므로 *pp는 char 만큼(1바이트)만을 읽어오게 된다는것이다.
ih를 char형으로 캐스팅한 결과와 같게 된다. 당연히 같아야 하는 거이다.
만약 수를 거꾸로 저장하지 않고 순서대로 저장을 한다면 두 결과가 틀리게 나올 것이다
. 포인터를 사용해 어떤 변수의 일정 크기만큼만을 읽어오려고 할 때 문제가 생기므로. 굉장히 복잡한 과정이 요구된다.
이 때문에 컴퓨터는 수를 거꾸로 저장하는 것이다
◐ &연산자의 쓰임새
연산자 &는 비트 연산자로써 AND 연산을 행하는 연산자이다. 그런데 여기서 address-of 연산자의 기능도 행하고 있습니다.
이름 그대로 「어떤 변수 의 주소를 알아내는」 역할을 하는 연산자 이다.
상수는 메모리에 위치하지 않으므로 주소가 있을 수 없고 당연히 &연산자도 사용하지 못하게 된다.
◐ *연산자와 &연산자의 관계
*연산자는 어떤 주소의 값을 읽어오는 연산자라고 했고 &연산자는 어떤 변 수의 주소를 알아내는 연산자라고 했다.
이 두 연산의 기능은 완전히 반 대라고 할 수 있다. 물론 변수에 대해 사용할 때 이다. (상수에서는 *는 가능하지만 &는 아예 사용을
하지 못한다
◐ 포인터 변수를 사용해 간접적으로 변수의 값 참조하기
int ih = 0x1234;
int *pi = &ih;
*pi = 0x5678;
pi는 ih의 주소를 가지고 있고 그 주소의 메모리를 *연산자를 통해 참조하고 있습니다. 그 메모리에 0x5678이라
는 값을 넣고 있다. 한 문장으로 설명한다면 「pi의 값을 주소로 하는 2바이트 크기의 메모리에 0x5678를 대입하는」 것이다.
int ih = 0x1234;
char *pc = &ih;
*pc = 0x56;
ih 값중의 char형으로 참조 가능한 부분인 첫번째 바이트 만이 바뀌게 된다.
결과적으로 ih는 0x1256이 되는 것이다.
◐ void형 포인터
포인터 변수를 선언할 때 * 앞에는 자료형을 정해준다고 했다.
int ih = 0x1234;
void *vc = (void *)&ih;
물론 아무런 에러도 나지 않다. vc도 포인터 변수이므로 당연하게도 ih변수의 포인터를 저장할 수가 있는 것이다.
*vc = 0x5678;
이 코드도 에러가 나지 않을까요? 조금만 생각해 보면 알 수 있다. 당연 히 에러가 발생 한다.
void형은 크기를 갖지 않기 때문에 얼마만한 크기 만큼의 메모리에 값을 넣어야 할지를 모르는 거이다.
*(int *)vc = 0x5678;
이렇듯 void형 포인터는 어떤 포인터 값도 가질 수 있지만, 직접적으로 참조를 할 수는 없고 반드시 캐스팅을
해 주야 하는 포인터 이다. 별로 사용될 곳이 없어 보이기도 하지만 나중에 프로그래밍을 하다보면 의외로 쓰이
는 곳이 많기도 하다.
◐ 참조에 의한 호출 (참조 호출, Call by reference)
int i = 3;
printf("%d", 3); //…①
printf("%d", i); //…②
컴파일러가 컴파일을 마친 이후에는 완전히 동일한 코드가 됩니다. 다시 말해서 ②번
의 경우 i 대신에 i가 가진 값인 3을 대치시켜서 넘겨준다는 것이다. 바로 이것이 Call by value 이다.
변수가 넘어가는 것이 아닌 변수가 가지고 있 는 「값」이 넘어가기 때문이다
void swapA(int a1, int a2) {
int ta;
ta = a1;
a1 = a2;
a2 = ta;
}
void swapB(int *b1, int *b2) {
int tb;
tb = *b1;
*b1 = *b2;
*b2 = tb;
}
int i = 3, j = 4;
swapA(i, j); //…③
swapB(&i, &j); //…④
이후의 결과는 아시다시피 i와 j값이 바뀌어 있다. 그럼 내부적으로 어떤 과정을 통하길래 이런 것이 가능할까
'[개발] DataBase Tool SAP' 카테고리의 다른 글
윈도우11 날짜 간 표시 해결 방법 (0) | 2024.09.24 |
---|---|
C에서 중요한 포인트?파해치자. (0) | 2021.10.18 |
서버_정기점검_리스트 방법 (0) | 2020.06.04 |
테이블 설계 양식 (0) | 2020.06.02 |
전산용어 정리 (0) | 2020.05.29 |